How to make a class property? [duplicate] How to make a class property? [duplicate] python python

How to make a class property? [duplicate]


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 selfdef 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 instantiationfoo = Bar()assert foo.bar == 1baz = Bar()assert baz.bar == 1# test static variablebaz.bar = 5assert foo.bar == 5# test setting variable on the classBar.bar = 50assert baz.bar == 50assert foo.bar == 50

The setter didn't work at the time we call Bar.bar, because we are callingTypeOfBar.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._thingyclass 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.