Intuative way to inherit validating classes - python

I've been using this style of inheritance to validate values set on instances of objects, but I'm wondering if there is a more fluent way to do this.
I'm following a spec where items of a certain classification (Foo) contain elements of a certain composition (Fe).
class Typed:
def __set__(self, obj, value):
assert isinstance(value, self._type), 'Incorrect type'
class Integer(Typed):
_type = int
class Float(Typed):
_type = float
class Positive(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value >= 0, 'Positive Values Only Accepted'
class PositiveInteger(Integer, Positive):
pass
class PositiveFloat(Float, Positive):
pass
class Sized(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value <= 2**self.size-1, f'{value} is too High'
class Fe(Sized, PositiveInteger):
name = 'Integer, 8 bit unsigned'
size = 8
class Foo(Fe):
name = 'Classificaion1'
def __set__(self, obj, id):
super().__set__(obj, id)
obj._id = id
def __get__(self, obj, objType=None):
return obj._id
def __del__(self):
pass

If you really need this level of abstraction, this is possibly the best way you can do it. My suggestion bellow can maybe save one line per class.
If you can afford to have attributes like "size" and "type" to be defined
on the final class, a richer base class and a declarative structure containing the checks as "lambda functions" can be used like this.
Note the usage of __init_subclass__ to check if all the parametes
needed for the guard expressions are defined:
from typing import Sequence
GUARDS = {
"typed": ((lambda self, value: "Incorrect type" if not instance(value, self._type) else None), ("_typed",)),
"positive": ((lambda self, value: "Only positive values" if value < 0 else None), ()),
"sized": ((lambda self, value: None if value <= 2 ** self.size - 1 else f"{value} must be smaller than 2**{self.size}"), ("size",)),
}
class DescriptorBase:
guards: Sequence[str]
def __init_subclass__(cls):
_sentinel = object()
for guard_name in cls.guards:
guard = GUARDS[guard_name]
required_attrs = guard[1]
missing = []
for attr in required_attrs:
if getattr(cls, attr, _sentinel) is _sentinel:
missing.append(attr)
if missing:
raise TypeError("Guarded descriptor {cls.__name__} did not declare required attrs: {missing}")
def __set_name__(self, owner, name):
self._name = f"_{name}""
def __set__(self, instance, value):
errors = []
for guard_name in self.guards:
if (error:= GUARDS[guard_name](self, value)) is not None:
errors.append(error)
if errors:
raise ValueError("\n".join(errors))
setattr (instance, self._name, value)
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.name)
def __del__(self, instance):
delattr(instance, self._name)
class Foo(DescriptorBase):
guards = ("typed", "positive", "sized")
size = 8
type_ = int
# No other code required here: __get__, __set__, __del__ handled in superclass
class UseAttr:
# Actual smart-attr usage:
my_foo = Foo()
Actually, if you want the class hierarchy, with less lines (no need to declare a __set__ method in each class), this approach can be used as well:
just change __init_superclass__ to collect "guards" in all superclasses,
and consolidate a single guards list on the class being defined, and then
define your composable guard-classes just as:
class Positive(BaseDescriptor):
guards = ("positive",)
class Sized(BaseDescriptor):
guards = ("sized",)
size = None
class Foo(Positive, Sized):
size = 8
class Fe(Foo):
name = "Fe name"
Actually, the change needed for this to work can be as simple as:
def __init_subclass__(cls):
_sentinel = object()
all_guards = []
for supercls in cls.__mro__:
all_guards.extend(getattr(supercls, "guards", ()))
# filter unique:
seem = {}
new_guards = []
for guard in all_guards:
if guard not in seem:
new_guards.append(guard)
seem.add(guard)
cls.guards = new_guards
for guard_name in cls.guards:
Also note that you could also collect the contents of the "GUARDS" registry from each defined class, instead of having to declare everything as lambdas before hand. I think you can get the idea from here on.

Related

Correctly create a display name for attribute in Python

I'm trying to create a class that will have attributes which have a display name, i.e,
class MainClass:
def __init__(self, value):
self.ip24xs = Attribute(value = value, display_name="Attribute X")
This Attribute object I thought of implementing like a descriptor, but once I call for example mc.ip24xs.display_name I get that the str object has no attribute display_name, which makes sense, because it's the return value of the __get__ method. What is the correct way of implementing such functionality?
#dataclass
class Attribute:
value : Any = None
display_name : str = "var"
def __get__(self, obj, objtype = None):
print(f"Retrieving {self.display_name}")
return self.value
def __set__(self, obj, val):
print(f"Setting {self.display_name}")
self.value = val
I copy/pasted your code, and I don't get any errors. Only, ip24xs is not working as a descriptor but as a regular attribute or MainClass, because descriptors work in a different way. This will do, however:
#dataclass
class Attribute:
display_name : str
def __get__(self, obj, objtype = None):
print(f"Retrieving {self.display_name}")
return obj.value
def __set__(self, obj, val):
print(f"Setting {self.display_name}")
obj.value = val
class MainClass:
ip24xs = Attribute(display_name="Attribute X")
def __init__(self,value):
self.value = value
Note the difference: value is a regular attribute of MainClass, not of Attribute, so you reference it in __get__() and __set__() by obj.value, not self.value. You may also want to make it "private" as in _value just to make (more or less) sure it is not accessed directly

subclass ctypes.c_int to create an enum to use in ctypes.Structure._fields_

Thats what i came from at first place:
class DismRestartType(DismEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
and using it like:
class DismFeatureInfo(DismStructure):
_pack_ = 4
_fields_ = [
("FeatureName", c_wchar_p),
("RestartRequired", DismRestartType)
]
class DismEnum(Enum):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
return obj
Notice the additional parameter for the description. The plan is to display later the description instead of the value, so i don't have to prepare every structure itself.
The problem: I get an error because the structure expect a c type and got an enum.
I did some research and founds this i.e.:
Using an IntEnum as the type in a ctypes.Structure._fields_
So i tried:
class CEnumeration(c_int):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
print("Will never be executed")
return obj
def __repr__(self):
return self.description
Unfortunately i get *** AttributeError: 'DismRestartType' object has no attribute 'description', also the new method never gets executed. Can someone explain me why it's not executed and help me to reach the goal?
EDIT1:
I don't get it! Why does __new__ not executed in TestEnum but gets executed when i inherit from Enum? The metaclass new gets executed.
class PointlessMetaClass(type(c_int)):
def __new__(meta, classname, bases, classDict):
cls = type(c_int).__new__(meta, classname, bases, classDict)
pdb.set_trace()
return cls
class TestEnum(metaclass=PointlessMetaClass):
_type_ = "i"
def __new__(cls, value):
print("Why not executed")
pdb.set_trace()
return cls
class DismRestartType(TestEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
Solution:
I took long but now I got it:
from ctypes import c_int
from types import DynamicClassAttribute
class _EnumDict(dict):
"""Track enum member order and ensure member names are not reused.
EnumMeta will use the names found in self._member_names as the
enumeration member names.
"""
def __init__(self):
super().__init__()
self._member_names = []
def __setitem__(self, key, value):
if not isinstance(value, DynamicClassAttribute) and not key.startswith("_"):
self._member_names.append(key)
super().__setitem__(key, value)
class EnumerationType(type(c_int)):
"""Metaclass for Enum."""
#classmethod
def __prepare__(metacls, cls, bases):
edict = _EnumDict()
return edict
def __new__(metacls, classname, bases, classdict):
# save enum items into separate mapping so they don't get baked into
# the new class
enum_members = {k: classdict[k] for k in classdict._member_names}
for name in classdict._member_names:
del classdict[name]
# returns an instance of the new class, i.e. an instance of my enum
enum_class = super().__new__(metacls, classname, bases, classdict)
# Reverse value->name map for hashable values.
enum_class._value2member_map_ = {}
for member_name in classdict._member_names:
value = enum_members[member_name][0]
enum_member = c_int.__new__(enum_class)
enum_member.value = value # overwrites the value attr of c_int class
enum_member._name_ = member_name
enum_member._description_ = enum_members[member_name][1]
enum_member.__objclass__ = enum_class
# i.e DismRestartType.DismRestartNo will return an object instead of the value
setattr(enum_class, member_name, enum_member)
# i.e. {'0': <class DismRestartType:DismRestartNo: 0>}
enum_class._value2member_map_[value] = enum_member
return enum_class
def __repr__(self):
return "<Enumeration %s>" % self.__name__
class CEnumeration(c_int, metaclass=EnumerationType):
"""Generic enumeration.
Derive from this class to define new enumerations.
"""
def __new__(cls, value):
# all enum instances are actually created during class construction
# without calling this method; this method is called by the metaclass'
# __call__ (i.e. Color(3) ), and by pickle
if type(value) is cls:
# For lookups like Color(Color.RED)
return value
# by-value search for a matching enum member
# see if it's in the reverse mapping (for hashable values)
try:
if value in cls._value2member_map_:
return cls._value2member_map_[value]
except TypeError:
pass
return cls._missing_(value)
#classmethod
def _missing_(cls, value):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
# return only description
def __repr__(self):
return "<%s.%s: %r>" % (
self.__class__.__name__, self.name, self.value)
def __str__(self):
return "%s.%s" % (self.__class__.__name__, self.name)
# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`. This works because enumeration
# members are not set directly on the enum class -- __getattr__ is
# used to look them up.
#DynamicClassAttribute
def name(self):
"""The name of the Enum member."""
try:
# get name on instance
return self._name_
except AttributeError:
# get name on class
return self._value2member_map_[self.value]._name_
#DynamicClassAttribute
def description(self):
"""The description of the Enum member."""
try:
# get description on instance
return self._description_
except AttributeError:
# get description on class
return self._value2member_map_[self.value]._description_

Can decorator wrap setter and getter in python?

I want to build various setter and getter. Fot not copy and paste the code, I thought something to solve it. Can decorator do it?
#property
def !!variable_name!!(self):
return self.__!!variable_name!!
#!!variable_name!!.setter
def !!variable_name!!(self, input):
self.__!!variable_name!! = input
Is it possible like macro in C?
It's unclear why you would want to do something like this—create a property with setter that ignores its value argument—but the answer is "Yes", you can do it by creating a function that returns a custom property object:
However you can't use # syntax to apply it. Instead you have to utilize it as shown:
def attribute_property(name, input_value):
STORAGE_NAME = '_' + name
#property
def prop(self):
return getattr(self, STORAGE_NAME)
#prop.setter
def prop(self, ignored):
setattr(self, STORAGE_NAME, input_value)
return prop
# EXAMPLE USAGE
class Person(object):
name = attribute_property('name', 'Monty')
def __init__(self, name, age):
self.name = name # ignores value of passed "name" argument!
self.age = age
user = Person('Rodrigo', 42)
print('user.name: {!r}'.format(user.name))
print('user.age: {!r}'.format(user.age))
Output:
user.name: 'Monty'
user.age: 42
Simple answer: Yes, that's possible using the descriptor protocol. For example you want to save variables with a leading underscore and access them without the leading underscore such a descriptor would work:
from six import string_types
class DescriptorSingleLeadingUnderscore(object):
def __init__(self, attr, doc=""):
if not isinstance(attr, string_types):
# Not a string so take the documentation (if avaiable) and name
# from the method.
if attr.__doc__:
doc = attr.__doc__
attr = attr.__name__
self.__doc__ = doc # Set the documentation of the instance.
self.attr = '_' + attr # Add leading underscore to the attribute name
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.attr, None)
def __set__(self, instance, value):
setattr(instance, self.attr, value)
def __delete__(self, instance):
delattr(instance, self.attr)
class X(object):
someproperty = DescriptorSingleLeadingUnderscore('someproperty')
someproperty1 = DescriptorSingleLeadingUnderscore('someproperty1')
someproperty2 = DescriptorSingleLeadingUnderscore('someproperty2')
someproperty3 = DescriptorSingleLeadingUnderscore('someproperty3')
#DescriptorSingleLeadingUnderscore
def it_also_works_as_decorator(self):
pass # this code is never executed!
And a test case:
>>> x = X()
>>> x.someproperty = 100
>>> x.someproperty
100
>>> x._someproperty
100
>>> x.it_also_works_as_decorator = 100
>>> x.it_also_works_as_decorator
100
>>> x._it_also_works_as_decorator
100

pythonic way to declare multiple class instance properties as numbers?

I have a class with several properties, each of which has to be a number. After repeating the same code over and over again I think there is a more pythonic way to declare multiple class instance properties as numbers.
Right now I set each property value to None and raise a type error if the value is set to a non number type. I'd prefer to set the property type to a number when the property is initialized.
Thanks!
Example:
import numbers
class classWithNumbers(object):
def __init__(self):
self._numProp1 = None
self._numProp2 = None
#property
def numProp1(self):
return self._numProp1
#numProp1.setter
def numProp1(self,value):
if not isinstance(value, numbers.Number): #repeated test for number
raise TypeError("Must be number")
self._numProp1 = value
#property
def numProp2(self):
return self._numProp2
#numProp2.setter
def numProp(self,value):
if not isinstance(value, numbers.Number):
raise TypeError("Must be number")
self._numProp2 = value
Also, I actually have this wrapped into a method that is repeated at each property setter:
def isNumber(value):
if not isinstance(value, numbers.Number):
raise TypeError("Must be number")
If every property of this class should be a number you can implement custom __setattr__ method:
class ClassWithNumbers(object):
def __init__(self):
self.num_prop1 = 0
self.num_prop2 = 0
def __setattr__(self, name, value):
if not isinstance(value, numbers.Number):
raise TypeError("Must be number")
super(ClassWithNumbers, self).__setattr__(name, value)
From documentation: __setattr__ (is) called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.
More general approach would be to not allow type of once assigned attribute to change:
class ClassWithUnchangeableTypes(object):
def __init__(self):
self.num_prop1 = 0
self.num_prop2 = 0
def __setattr__(self, name, value):
if hasattr(self, name): # this means that we assigned value in the past
previous_value_type = type(getattr(self, name))
if not isinstance(value, previous_value_type):
raise TypeError("Must be {}".format(previous_value_type))
super(ClassWithUnchangeableTypes, self).__setattr__(name, value)
Speaking of pythonic, from pep8:
Class names should normally use the CapWords convention.
Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.
A fairly modern (python 3.5+) and pythonic way is using type hints
#property
def numProp1(self):
return self._numProp1
#numProp1.setter
def numProp1(self,value: int):
self._numProp1 = value
A more compatible way is to try to convert to int, which will then throw an exception for you if that fails. It might also have unwanted behaviour like accepting floats:
#property
def numProp1(self):
return self._numProp1
#numProp1.setter
def numProp1(self,value):
self._numProp1 = int(value)
But there's already nothing wrong with your implementation in general.
If you do not want to explicitly declare getters and setters, you could check their type when used, not when assigned.
The most Pythonic way is probably to call the int constructor and let it throw an exception:
class ClassWithNumbers(object):
def __init__(self, v1, v2):
self.numprop1 = int(v1)
self.numprop2 = int(v2)
if the numprops are part of your interface then creating #property accessors would be appropriate. You can also implement your own descriptor:
class Number(object):
def __init__(self, val=0):
self.__set__(self, val)
def __get__(self, obj, cls=None):
return self.val
def __set__(self, obj, val):
try:
self.val = int(val)
except ValueError as e:
raise TypeError(str(e))
class ClassWithNumbers(object):
numprop1 = Number(42)
numprop2 = Number(-1)
usage:
c = ClassWithNumbers()
print c.numprop1
c.numprop1 += 1
print c.numprop1
c.numprop1 = 'hello'

How to make a class property? [duplicate]

This question already has answers here:
Using property() on classmethods
(19 answers)
Closed 3 years ago.
In python I can add a method to a class with the #classmethod decorator. Is there a similar decorator to add a property to a class? I can better show what I'm talking about.
class Example(object):
the_I = 10
def __init__( self ):
self.an_i = 20
#property
def i( self ):
return self.an_i
def inc_i( self ):
self.an_i += 1
# is this even possible?
#classproperty
def I( cls ):
return cls.the_I
#classmethod
def inc_I( cls ):
cls.the_I += 1
e = Example()
assert e.i == 20
e.inc_i()
assert e.i == 21
assert Example.I == 10
Example.inc_I()
assert Example.I == 11
Is the syntax I've used above possible or would it require something more?
The reason I want class properties is so I can lazy load class attributes, which seems reasonable enough.
Here's how I would do this:
class ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
return self.fget.__get__(obj, klass)()
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
type_ = type(obj)
return self.fset.__get__(obj, type_)(value)
def setter(self, func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
self.fset = func
return self
def classproperty(func):
if not isinstance(func, (classmethod, staticmethod)):
func = classmethod(func)
return ClassPropertyDescriptor(func)
class Bar(object):
_bar = 1
#classproperty
def bar(cls):
return cls._bar
#bar.setter
def bar(cls, value):
cls._bar = value
# test instance instantiation
foo = Bar()
assert foo.bar == 1
baz = Bar()
assert baz.bar == 1
# test static variable
baz.bar = 5
assert foo.bar == 5
# test setting variable on the class
Bar.bar = 50
assert baz.bar == 50
assert foo.bar == 50
The setter didn't work at the time we call Bar.bar, because we are calling
TypeOfBar.bar.__set__, which is not Bar.bar.__set__.
Adding a metaclass definition solves this:
class ClassPropertyMetaClass(type):
def __setattr__(self, key, value):
if key in self.__dict__:
obj = self.__dict__.get(key)
if obj and type(obj) is ClassPropertyDescriptor:
return obj.__set__(self, value)
return super(ClassPropertyMetaClass, self).__setattr__(key, value)
# and update class define:
# class Bar(object):
# __metaclass__ = ClassPropertyMetaClass
# _bar = 1
# and update ClassPropertyDescriptor.__set__
# def __set__(self, obj, value):
# if not self.fset:
# raise AttributeError("can't set attribute")
# if inspect.isclass(obj):
# type_ = obj
# obj = None
# else:
# type_ = type(obj)
# return self.fset.__get__(obj, type_)(value)
Now all will be fine.
If you define classproperty as follows, then your example works exactly as you requested.
class classproperty(object):
def __init__(self, f):
self.f = f
def __get__(self, obj, owner):
return self.f(owner)
The caveat is that you can't use this for writable properties. While e.I = 20 will raise an AttributeError, Example.I = 20 will overwrite the property object itself.
[answer written based on python 3.4; the metaclass syntax differs in 2 but I think the technique will still work]
You can do this with a metaclass...mostly. Dappawit's almost works, but I think it has a flaw:
class MetaFoo(type):
#property
def thingy(cls):
return cls._thingy
class Foo(object, metaclass=MetaFoo):
_thingy = 23
This gets you a classproperty on Foo, but there's a problem...
print("Foo.thingy is {}".format(Foo.thingy))
# Foo.thingy is 23
# Yay, the classmethod-property is working as intended!
foo = Foo()
if hasattr(foo, "thingy"):
print("Foo().thingy is {}".format(foo.thingy))
else:
print("Foo instance has no attribute 'thingy'")
# Foo instance has no attribute 'thingy'
# Wha....?
What the hell is going on here? Why can't I reach the class property from an instance?
I was beating my head on this for quite a while before finding what I believe is the answer. Python #properties are a subset of descriptors, and, from the descriptor documentation (emphasis mine):
The default behavior for attribute access is to get, set, or delete the
attribute from an object’s dictionary. For instance, a.x has a lookup chain
starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing
through the base classes of type(a) excluding metaclasses.
So the method resolution order doesn't include our class properties (or anything else defined in the metaclass). It is possible to make a subclass of the built-in property decorator that behaves differently, but (citation needed) I've gotten the impression googling that the developers had a good reason (which I do not understand) for doing it that way.
That doesn't mean we're out of luck; we can access the properties on the class itself just fine...and we can get the class from type(self) within the instance, which we can use to make #property dispatchers:
class Foo(object, metaclass=MetaFoo):
_thingy = 23
#property
def thingy(self):
return type(self).thingy
Now Foo().thingy works as intended for both the class and the instances! It will also continue to do the right thing if a derived class replaces its underlying _thingy (which is the use case that got me on this hunt originally).
This isn't 100% satisfying to me -- having to do setup in both the metaclass and object class feels like it violates the DRY principle. But the latter is just a one-line dispatcher; I'm mostly okay with it existing, and you could probably compact it down to a lambda or something if you really wanted.
If you use Django, it has a built in #classproperty decorator.
from django.utils.decorators import classproperty
For Django 4, use:
from django.utils.functional import classproperty
I think you may be able to do this with the metaclass. Since the metaclass can be like a class for the class (if that makes sense). I know you can assign a __call__() method to the metaclass to override calling the class, MyClass(). I wonder if using the property decorator on the metaclass operates similarly.
Wow, it works:
class MetaClass(type):
def getfoo(self):
return self._foo
foo = property(getfoo)
#property
def bar(self):
return self._bar
class MyClass(object):
__metaclass__ = MetaClass
_foo = 'abc'
_bar = 'def'
print MyClass.foo
print MyClass.bar
Note: This is in Python 2.7. Python 3+ uses a different technique to declare a metaclass. Use: class MyClass(metaclass=MetaClass):, remove __metaclass__, and the rest is the same.
As far as I can tell, there is no way to write a setter for a class property without creating a new metaclass.
I have found that the following method works. Define a metaclass with all of the class properties and setters you want. IE, I wanted a class with a title property with a setter. Here's what I wrote:
class TitleMeta(type):
#property
def title(self):
return getattr(self, '_title', 'Default Title')
#title.setter
def title(self, title):
self._title = title
# Do whatever else you want when the title is set...
Now make the actual class you want as normal, except have it use the metaclass you created above.
# Python 2 style:
class ClassWithTitle(object):
__metaclass__ = TitleMeta
# The rest of your class definition...
# Python 3 style:
class ClassWithTitle(object, metaclass = TitleMeta):
# Your class definition...
It's a bit weird to define this metaclass as we did above if we'll only ever use it on the single class. In that case, if you're using the Python 2 style, you can actually define the metaclass inside the class body. That way it's not defined in the module scope.
def _create_type(meta, name, attrs):
type_name = f'{name}Type'
type_attrs = {}
for k, v in attrs.items():
if type(v) is _ClassPropertyDescriptor:
type_attrs[k] = v
return type(type_name, (meta,), type_attrs)
class ClassPropertyType(type):
def __new__(meta, name, bases, attrs):
Type = _create_type(meta, name, attrs)
cls = super().__new__(meta, name, bases, attrs)
cls.__class__ = Type
return cls
class _ClassPropertyDescriptor(object):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, obj, owner):
if self in obj.__dict__.values():
return self.fget(obj)
return self.fget(owner)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
return self.fset(obj, value)
def setter(self, func):
self.fset = func
return self
def classproperty(func):
return _ClassPropertyDescriptor(func)
class Bar(metaclass=ClassPropertyType):
__bar = 1
#classproperty
def bar(cls):
return cls.__bar
#bar.setter
def bar(cls, value):
cls.__bar = value
bar = Bar()
assert Bar.bar==1
Bar.bar=2
assert bar.bar==2
nbar = Bar()
assert nbar.bar==2
I happened to come up with a solution very similar to #Andrew, only DRY
class MetaFoo(type):
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.thingy})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
#property
def thingy(cls):
if not inspect.isclass(cls):
cls = type(cls)
return cls._thingy
#thingy.setter
def thingy(cls, value):
if not inspect.isclass(cls):
cls = type(cls)
cls._thingy = value
class Foo(metaclass=MetaFoo):
_thingy = 23
class Bar(Foo)
_thingy = 12
This has the best of all answers:
The "metaproperty" is added to the class, so that it will still be a property of the instance
Don't need to redefine thingy in any of the classes
The property works as a "class property" in for both instance and class
You have the flexibility to customize how _thingy is inherited
In my case, I actually customized _thingy to be different for every child, without defining it in each class (and without a default value) by:
def __new__(mc1, name, bases, nmspc):
nmspc.update({'thingy': MetaFoo.services, '_thingy': None})
return super(MetaFoo, mc1).__new__(mc1, name, bases, nmspc)
If you only need lazy loading, then you could just have a class initialisation method.
EXAMPLE_SET = False
class Example(object):
#classmethod
def initclass(cls):
global EXAMPLE_SET
if EXAMPLE_SET: return
cls.the_I = 'ok'
EXAMPLE_SET = True
def __init__( self ):
Example.initclass()
self.an_i = 20
try:
print Example.the_I
except AttributeError:
print 'ok class not "loaded"'
foo = Example()
print foo.the_I
print Example.the_I
But the metaclass approach seems cleaner, and with more predictable behavior.
Perhaps what you're looking for is the Singleton design pattern. There's a nice SO QA about implementing shared state in Python.

Categories