303

I have a class with two class methods (using the classmethod() function) for getting and setting what is essentially a static variable. I tried to use the property() function with these, but it results in an error. I was able to reproduce the error with the following in the interpreter:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

I can demonstrate the class methods, but they don't work as properties:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

Is it possible to use the property() function with @classmethod decorated functions?

martineau
  • 119,623
  • 25
  • 170
  • 301
Mark Roddy
  • 27,122
  • 19
  • 67
  • 71

19 Answers19

192

3.8 < Python < 3.11

Can use both decorators together. See this answer.

Python 2 and python 3 (works in 3.9-3.10 too)

A property is created on a class but affects an instance. So if you want a classmethod property, create the property on the metaclass.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

But since you're using a metaclass anyway, it will read better if you just move the classmethods in there.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

or, using Python 3's metaclass=... syntax, and the metaclass defined outside of the foo class body, and the metaclass responsible for setting the initial value of _var:

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
STerliakov
  • 4,983
  • 3
  • 15
  • 37
A. Coady
  • 54,452
  • 8
  • 34
  • 40
  • 1
    This doesn't seem to work for me in Python 3.2. If I change foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func) to foo.__metaclass__.var = property(foo.getvar.__func__, foo.setvar.__func__) I get "AttributeError: type object 'foo' has no attribute 'var'" when executing "foo.var". – Michael Kelley Dec 16 '11 at 03:29
  • *SIGH* double correction: this works in Python 2.7, but not Python 3.2. – Michael Kelley Dec 17 '11 at 04:14
  • @MichaelKelley - That's because the syntax for [metaclasses has changed in Python 3.x](http://www.python.org/dev/peps/pep-3115/) – mac Feb 06 '14 at 10:17
  • 2
    I am not quite sure to understand, what would be the Python 3.x way to write this then ? – SylvainD Jun 24 '14 at 07:29
  • 9
    @Josay: You'd need to define the metaclass first, then define the class using the new `class Foo(metaclass=...)` syntax. – Kevin Dec 09 '14 at 00:21
  • @Josay: See Viktor Haag's answer here: http://stackoverflow.com/questions/3203286/how-to-create-a-read-only-class-property-in-python – sage88 Oct 12 '15 at 19:41
  • 8
    It's worth noting that these properties won't be available on instances of your class (`foo` in this case), just on the class itself -- by virtue of how metaclasses work. – nedned May 07 '20 at 02:07
  • @nedned, I found this out the hard way, but can't find any explanation on why. Do you know why this happens? – Devyzr Sep 28 '21 at 17:50
  • 3
    @Devyzr It happens because... well, actually, it's right in the name, though it's a non-obvious consequence. A class Foo's metaclass Meta is the class _of the class Foo_, and Foo itself will therefore be an instance of Meta — but instances of Foo will **NOT** be instances of Meta. (They're instances of Foo, and Foo does not _inherit from_ Meta, it _instantiates_ Meta.) So, any properties Foo gains by virtue of being an instance of Meta are not passed along to its instances. If they were, Meta would be a parent class of Foo, not a meta class for Foo. – FeRD Oct 05 '21 at 09:07
  • See [Denis's answer](https://stackoverflow.com/a/13624858/2251364) if you want properties to be accessible on class instances as well – Hritik Feb 05 '22 at 21:39
  • More about [metaclasses](https://stackoverflow.com/q/100003) – djvg Feb 23 '22 at 10:02
  • "Python < 3.9" ... did you mean Python == 3.9? Your section headers are really confusing IMO – ringo Mar 15 '23 at 18:57
140

In Python 3.9 You could use them together, but (as noted in @xgt's comment) it was deprecated in Python 3.11, so it is not longer supported (but it may work for a while or reintroduced at some point).

Check the version remarks here:

https://docs.python.org/3.11/library/functions.html#classmethod

However, it used to work like so:

class G:
    @classmethod
    @property
    def __doc__(cls):
        return f'A doc for {cls.__name__!r}'

Order matters - due to how the descriptors interact, @classmethod has to be on top.

Amit Portnoy
  • 5,957
  • 2
  • 29
  • 30
  • 1
    Looks like dont work with `functools.cached_property` – Pycz Apr 22 '21 at 14:27
  • 9
    @Pycz just stack `@classmethod @property @functools.cache` to get the same behavior – Amit Portnoy Apr 23 '21 at 05:19
  • 8
    What about the setter? – Paul Sep 30 '21 at 00:04
  • mypy 0.910 doesn't like such properties. For this piece of code: `variable = get_variable(FooBar.get_version)` it outputs the following: `main.py:36: error: Argument 1 to "get_variable" has incompatible type "Callable[[], str]"; expected "str" [arg-type]` – Akenolt Oct 12 '21 at 15:15
  • Why does it work with 3.9? What changed? – Vladoski Oct 14 '21 at 15:47
  • 2
    @Vladoski _this_ changed. In 3.9 `@classmethod` implementation was extended to support the `@property` descriptor. – Amit Portnoy Oct 17 '21 at 10:19
  • Note that setting this so-called **method** to a value works; which in most cases isn't desired. – ImportError Jan 29 '22 at 10:50
  • 1
    @ImportError well that's just a python thing (probably a bad thing, but not related only to properties on classes or otherwise) – Amit Portnoy Jan 30 '22 at 08:16
  • Oops, yh you're right – ImportError Jan 30 '22 at 15:08
  • @Paul did you find a solution for the setter? – studioj Apr 05 '22 at 11:14
  • 1
    @studioj -- Python is not C. The internals of Python are hidden, mysterious, and subject to change. Therefore, I decided it best to only do what the Python developers directly intended and keep it simple. When I wanted to set a class variable via a member function, then I used setattr(self.__class__, 'attribute_name', new_value) in the member setter. It is simple to use member_variable.__class__ to access class attributes directly. getattr and setattr are the Python way. Decorators add convolution. I chose a simple solution which was directly intended to be used by the Python developers. – Paul Apr 05 '22 at 14:37
  • 17
    classproperty chain was removed with Python 3.11. You cannot use `@classproperty` together with `@property` any longer: https://docs.python.org/3.11/library/functions.html?highlight=classmethod#classmethod – xqt May 29 '22 at 09:59
  • 23
    @xqt WHHHYyyy!? The documentation says nothing about the rationale! People _wrote code relying on stuff like this_! What are they doing!? What's the justification? Any links to mailing list or bug tracker discussions or PEP where this was decided? – mtraceur Jun 10 '22 at 09:31
  • 19
    Looks like at least some of the rationale is [here](https://github.com/python/cpython/issues/89519). – mtraceur Jun 10 '22 at 09:38
  • 3
    That looks like *all* the rationale. Honestly it makes sense, even though it makes me very sad. I hope a replacement will be found. – theberzi Sep 02 '22 at 05:28
  • @xqt I'm using Python 3.11.1 and it still works for me – Zack Plauché Feb 03 '23 at 13:58
  • @ZackPlauché I think it was deprecated not actively removed – Amit Portnoy Feb 05 '23 at 11:27
  • @AmitPortnoy what does this mean exactly? – Zack Plauché Feb 06 '23 at 12:29
  • 2
    @ZackPlauché that it may work but may be buggy until (if) it is officially reintroduced. – Amit Portnoy Feb 06 '23 at 13:00
  • 1
    @ZackPlauché unless you start using mypy. Then you get stuff like "error: Only instance methods can be decorated with property [misc]" – Maarten Derickx May 03 '23 at 15:21
98

I hope this dead-simple read-only @classproperty decorator would help somebody looking for classproperties.

class classproperty(property):
    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1
Denis Ryzhkov
  • 2,321
  • 19
  • 12
  • 3
    Does this work with subclasses? (can a subclass override classproperties?) – zakdances Mar 03 '13 at 00:43
  • 1
    Umm yes? `class D(C): x = 2; assert D.x == 2` – dtheodor Sep 30 '14 at 20:23
  • I wish this worked when I use it in `.format` like `"{x}".format(**dict(self.__class__.__dict__, **self.__dict__))` :( – 2rs2ts Jan 21 '15 at 04:01
  • @Nathan Not only... when you set it you override all `x` access by `10`. I like this approach because is neat and simple but sounds like an antipattern – Michele d'Amico Dec 18 '15 at 10:33
  • 2
    Easily fixed: add a `__set__` that raises an `ValueError` to prevent override. – Kiran Jonnalagadda Feb 24 '18 at 16:52
  • 1
    … `AttributeError`, and that only works to block `C().x = val`, not `C.x = val` – Kiran Jonnalagadda Feb 24 '18 at 16:59
  • 1
    Thanks for this awesome snippet! I've created a pip installable package with this and a cached_classproperty here: https://github.com/hottwaj/classproperties – jcdude Jan 22 '21 at 11:54
  • 1
    This works well, but it would be a good idea to inherit from `property`. Then tools handling properties specially would handle these like normal properties. It also makes it unnecessary to implement `__init__`. – Pekka Klärck Oct 10 '22 at 22:17
  • 1
    Thanks @PekkaKlärck! Now it is even more simple and still works both in python2 and python3. – Denis Ryzhkov Oct 11 '22 at 14:54
  • @jcdude Nice package, see however the implementation in [astropy.utils.decorators](https://docs.astropy.org/en/stable/_modules/astropy/utils/decorators.html#classproperty) for comparison. – Gribouillis Jul 01 '23 at 17:03
76

Reading the Python 2.2 release notes, I find the following.

The get method [of a property] won't be called when the property is accessed as a class attribute (C.x) instead of as an instance attribute (C().x). If you want to override the __get__ operation for properties when used as a class attribute, you can subclass property - it is a new-style type itself - to extend its __get__ method, or you can define a descriptor type from scratch by creating a new-style class that defines __get__, __set__ and __delete__ methods.

NOTE: The below method doesn't actually work for setters, only getters.

Therefore, I believe the prescribed solution is to create a ClassProperty as a subclass of property.

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

However, the setters don't actually work:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var is unchanged, you've simply overwritten the property with a new value.

You can also use ClassProperty as a decorator:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5
Jason R. Coombs
  • 41,115
  • 10
  • 83
  • 93
  • 21
    I don't think the setter part of the ClassProperty actually works as described: while the example's assertions all pass, at the end foo._var == 4 (not 3, as implied). Setting the property clobbers the property itself. When class-properties were discussed on [python-dev](http://mail.python.org/pipermail/python-ideas/2011-January/008950.html) [it was pointed out that, while getters are trivial, setters are difficult (impossible?) without a metaclass](http://mail.python.org/pipermail/python-ideas/2011-January/008959.html) – Gabriel Grant Aug 15 '11 at 00:14
  • 4
    @Gabriel Totally correct. I can't believe no one pointed that out for two years. – agf Sep 13 '11 at 19:48
  • I'm also not sure why you don't not just use `self.fget(owner)` and remove the need to have to use a `@classmethod` at all here? (that's what `classmethod` *does*, translate `.__get__(instance, owner)(*args, **kwargs)` to `function(owner, *args, **kwargs)` calls, via an intermediary; properties don't need the intermediary). – Martijn Pieters Oct 19 '18 at 11:58
  • Your demonstration is lacking any actual transformation in either the getter or the setter that would neatly demonstrate that your `foo.var = 3` assignment **doesn't actually go through the property**, and instead has simply replaced the property object on `foo` with an integer. If you added `assert isinstance(foo.__dict__['var'], ClassProperty)` calls between your assertions you'd see that fail after `foo.var = 3` is executed. – Martijn Pieters Oct 19 '18 at 12:14
  • 4
    Python classes don't support descriptor binding on setting *on the class itself*, only on getting (so `instance.attr`, `instance.attr = value` and `del instance.attr` will all bind the descriptor found on `type(instance)`, but while `classobj.attr` binds, `classobj.attr = value` and `del classobj.attr` do *not* and instead replace or delete the descriptor object itself). You need a metaclass to support setting and deleting (making the class object the instance, and the metaclass the type). – Martijn Pieters Oct 19 '18 at 12:14
49

Is it possible to use the property() function with classmethod decorated functions?

No.

However, a classmethod is simply a bound method (a partial function) on a class accessible from instances of that class.

Since the instance is a function of the class and you can derive the class from the instance, you can can get whatever desired behavior you might want from a class-property with property:

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

This code can be used to test - it should pass without raising any errors:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

And note that we didn't need metaclasses at all - and you don't directly access a metaclass through its classes' instances anyways.

writing a @classproperty decorator

You can actually create a classproperty decorator in just a few lines of code by subclassing property (it's implemented in C, but you can see equivalent Python here):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

Then treat the decorator as if it were a classmethod combined with property:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

And this code should work without errors:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

But I'm not sure how well-advised this would be. An old mailing list article suggests it shouldn't work.

Getting the property to work on the class:

The downside of the above is that the "class property" isn't accessible from the class, because it would simply overwrite the data descriptor from the class __dict__.

However, we can override this with a property defined in the metaclass __dict__. For example:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

And then a class instance of the metaclass could have a property that accesses the class's property using the principle already demonstrated in the prior sections:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

And now we see both the instance

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

and the class

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

have access to the class property.

Community
  • 1
  • 1
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
36

Python 3!

See @Amit Portnoy's answer for an even cleaner method in python >= 3.9


Old question, lots of views, sorely in need of a one-true Python 3 way.

Luckily, it's easy with the metaclass kwarg:

class FooProperties(type):
    
    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

Then, >>> Foo.var

'FOO!'

Neuron
  • 5,141
  • 5
  • 38
  • 59
OJFord
  • 10,522
  • 8
  • 64
  • 98
  • 2
    that is to say there is no simple way out of the box – mehmet Dec 07 '18 at 17:41
  • @mehmet Is this not simple? `Foo` is an instance of its metaclass, and `@property` can be used for its methods just as it can for those of instances of `Foo`. – OJFord Dec 09 '18 at 20:39
  • 4
    you had to define another class for a class, that is double the complexity assuming the metaclass is not reusable. – mehmet Dec 09 '18 at 21:44
  • A classmethod works from both the class and the instance. This property only works from the class. I don't think this is what is being asked for. – Russia Must Remove Putin Feb 22 '20 at 21:05
  • 3
    @AaronHall If that's important, it's easily added in `Foo.__new__`. Though at that point it might be worth either using getattribute instead, or questioning if pretending a language feature exists is really the approach you want to take at all. – OJFord Feb 24 '20 at 09:42
  • @בנימיןכהן It works on _all_ 3.x.y versions. My update at the top is a suggestion that people using 3.9 or later consider the answer below instead. (This one still works, but it's sort of a workaround for the clearer more deliberate/readable/intuitive way not being possible in older versions.) – OJFord Aug 03 '21 at 09:06
18

There is no reasonable way to make this "class property" system to work in Python.

Here is one unreasonable way to make it work. You can certainly make it more seamless with increasing amounts of metaclass magic.

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

The knot of the issue is that properties are what Python calls "descriptors". There is no short and easy way to explain how this sort of metaprogramming works, so I must point you to the descriptor howto.

You only ever need to understand this sort of things if you are implementing a fairly advanced framework. Like a transparent object persistence or RPC system, or a kind of domain-specific language.

However, in a comment to a previous answer, you say that you

need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

It seems to me, what you really want is an Observer design pattern.

ddaa
  • 52,890
  • 7
  • 50
  • 59
  • I like the idea of the code example, but it seems like it would be a little clunky in practice. – Mark Roddy Sep 24 '08 at 21:16
  • What I'm trying to accomplish is pretty straight forward setting and getting of a single attribute that's used as a flag to modify the behavior of all instances so I think Observer would be overblown for what I'm trying to do. If there were multiple attributes in question then I'd be more inclined. – Mark Roddy Sep 24 '08 at 21:16
  • It seems that just making the functions public and calling them directly was the simplist solution. I was curious if I was doing something wrong or if I was trying to do was impossible. Sorry about the multiple comments by the way. 300 character limit sucks. – Mark Roddy Sep 24 '08 at 21:19
  • The nifty thing about the code example is that you can implement all the clunky bits once and then inherit them. Just move the _var into the derived class. class D1(Foo): _var = 21 class D2(Foo) _var = "Hello" D1.var 21 D2.var Hello – Thomas L Holaday Dec 27 '08 at 16:29
7

Setting it only on the meta class doesn't help if you want to access the class property via an instantiated object, in this case you need to install a normal property on the object as well (which dispatches to the class property). I think the following is a bit more clear:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo
4

Half a solution, __set__ on the class does not work, still. The solution is a custom property class implementing both a property and a staticmethod

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not
Florian Bösch
  • 27,420
  • 11
  • 48
  • 53
3

Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

Do you have access to at least one instance of the class? I can think of a way to do it then:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var
John Millikin
  • 197,344
  • 39
  • 212
  • 226
3

Give this a try, it gets the job done without having to change/add a lot of existing code.

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

The property function needs two callable arguments. give them lambda wrappers (which it passes the instance as its first argument) and all is well.

Sufian
  • 8,627
  • 4
  • 22
  • 24
2

Here's a solution which should work for both access via the class and access via an instance which uses a metaclass.

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

This also works with a setter defined in the metaclass.

papercrane
  • 672
  • 5
  • 12
2

I found one clean solution to this problem. It's a package called classutilities (pip install classutilities), see the documentation here on PyPi.

Consider example:

import classutilities

class SomeClass(classutilities.ClassPropertiesMixin):
    _some_variable = 8  # Some encapsulated class variable

    @classutilities.classproperty
    def some_variable(cls):  # class property getter
        return cls._some_variable

    @some_variable.setter
    def some_variable(cls, value):  # class property setter
        cls._some_variable = value

You can use it on both class level and instance level:

# Getter on class level:
value = SomeClass.some_variable
print(value)  # >>> 8
# Getter on instance level
inst = SomeClass()
value = inst.some_variable
print(value)  # >>> 8

# Setter on class level:
new_value = 9
SomeClass.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
# Setter on instance level
inst = SomeClass()
inst.some_variable = new_value
print(SomeClass.some_variable)   # >>> 9
print(SomeClass._some_variable)  # >>> 9
print(inst.some_variable)        # >>> 9
print(inst._some_variable)       # >>> 9

As you can see, it works correctly under all circumstances.

Emma Brown
  • 175
  • 1
  • 8
1

Based on https://stackoverflow.com/a/1800999/2290820


class MetaProperty(type):

    def __init__(cls, *args, **kwargs):
        super()

    @property
    def praparty(cls):
        return cls._var

    @praparty.setter
    def praparty(cls, val):
        cls._var = val


class A(metaclass=MetaProperty):
    _var = 5


print(A.praparty)
A.praparty = 6
print(A.praparty)
user2290820
  • 2,709
  • 5
  • 34
  • 62
1

For a functional approach pre Python 3.9 you can use this:

def classproperty(fget):
  return type(
      'classproperty',
      (),
      {'__get__': lambda self, _, cls: fget(cls), '__module__': None}
  )()
  
class Item:
  a = 47

  @classproperty
  def x(cls):
    return cls.a

Item.x
0

After searching different places, I found a method to define a classproperty valid with Python 2 and 3.

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

Hope this can help somebody :)

Andrew Myers
  • 2,754
  • 5
  • 32
  • 40
alex
  • 651
  • 1
  • 9
  • 11
  • 2
    If this method is something you found somewhere, it would be good to give a link (see [How to reference material written by others](/help/referencing)) – Andrew Myers Oct 18 '17 at 21:11
0

A code completion friendly solution for Python < 3.9

from typing import (
    Callable,
    Generic,
    TypeVar,
)


T = TypeVar('T')


class classproperty(Generic[T]):
    """Converts a method to a class property.
    """

    def __init__(self, f: Callable[..., T]):
        self.fget = f

    def __get__(self, instance, owner) -> T:
        return self.fget(owner)

eugene-bright
  • 381
  • 3
  • 9
-1

Here is my solution that also caches the class property

class class_property(object):
    # this caches the result of the function call for fn with cls input
    # use this as a decorator on function methods that you want converted
    # into cached properties

    def __init__(self, fn):
        self._fn_name = fn.__name__
        if not isinstance(fn, (classmethod, staticmethod)):
            fn = classmethod(fn)
        self._fn = fn

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if (
            self._fn_name in vars(cls) and
            type(vars(cls)[self._fn_name]).__name__ != "class_property"
        ):
            return vars(cls)[self._fn_name]
        else:
            value = self._fn.__get__(obj, cls)()
            setattr(cls, self._fn_name, value)
            return value
spacether
  • 2,136
  • 1
  • 21
  • 28
-47

Here's my suggestion. Don't use class methods.

Seriously.

What's the reason for using class methods in this case? Why not have an ordinary object of an ordinary class?


If you simply want to change the value, a property isn't really very helpful is it? Just set the attribute value and be done with it.

A property should only be used if there's something to conceal -- something that might change in a future implementation.

Maybe your example is way stripped down, and there is some hellish calculation you've left off. But it doesn't look like the property adds significant value.

The Java-influenced "privacy" techniques (in Python, attribute names that begin with _) aren't really very helpful. Private from whom? The point of private is a little nebulous when you have the source (as you do in Python.)

The Java-influenced EJB-style getters and setters (often done as properties in Python) are there to facilitate Java's primitive introspection as well as to pass muster with the static language compiler. All those getters and setters aren't as helpful in Python.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 21
    Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class. – Mark Roddy Sep 24 '08 at 17:50