Problem with solutions above is that it wouldn't work for accessing class variables from instance variable:
print(Foo.number) # 4 f = Foo() print(f.number) # 'Foo' object has no attribute 'number'
Moreover, using metaclass explicit is not so nice, as using regular property decorator.
I tried to solve this problems. Here how it works now:
@classproperty_support class Bar(object): _bar = 1 @classproperty def bar(cls): return cls._bar @bar.setter def bar(cls, value): cls._bar = value # @classproperty should act like regular class variable. # Asserts can be tested with it. # class Bar: # bar = 1 assert Bar.bar == 1 Bar.bar = 2 assert Bar.bar == 2 foo = Bar() baz = Bar() assert foo.bar == 2 assert baz.bar == 2 Bar.bar = 50 assert baz.bar == 50 assert foo.bar == 50
As you see, we have @classproperty that works same way as @property for class variables. Only thing we will need is additional @classproperty_support class decorator.
Solution also works for read-only class properties.
Here's implementation:
class classproperty: """ Same as property(), but passes obj.__class__ instead of obj to fget/fset/fdel. Original code for property emulation: https://docs.python.org/3.5/howto/descriptor.html#properties """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj.__class__) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj.__class__, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj.__class__) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__) def classproperty_support(cls): """ Class decorator to add metaclass to our class. Metaclass uses to add descriptors to class attributes, see: http://stackoverflow.com/a/26634248/1113207 """ class Meta(type): pass for name, obj in vars(cls).items(): if isinstance(obj, classproperty): setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel)) class Wrapper(cls, metaclass=Meta): pass return Wrapper
Note: code isn't tested much, feel free to note if it doesn't work as you expect.