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
Answer from A. Coady on Stack Overflow3.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
Update: The ability to chain @classmethod and @property was removed in Python 3.13 .
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.
So I have a class that tracks all of its instances in a class-level dictionary that maps the ID numbers of the class, assigned at creation, to the class objects themselves:
import uuid
class Node(object):
_all_nodes = dict()
def __init__(self):
self.id = str(uuid.uuid4())
self._all_nodes[self.id] = selfSo the class-level dictionary points at all instances of Nodes. Referencing the _all_nodes dictionary has become quite common in my code; it's the global store of data that the program is working with.
But all of the data stored in the _all_nodes class-level dictionary is actually instances of subclasses of Node, not direct instantiations of Node itself:
class Person(Node):
...
# [many methods overridden]It's frequently helpful to get a list, not of all Nodes, but of all Persons, so I wind up doing this a lot:
all_people = {k: v for k, v in Node._all_nodes.items() if isinstance(k, Person)}
# do something with all_peopleIn fact, I do it often enough that recreating that line has stopped involving thinking about anything other than how tedious it is to re-type, or hunt down again to copy and paste. It seems like the obvious thing to do is to bundle it into the Person class:
class Person(Node):
def _all_people(self):
return {k: v for k, v in Node._all_nodes.items() if isinstance(k, Person)}The inconvenience of having to remember that Person._all_people is a function call (as are similar definitions in other subclasses), while Node._all_nodes refers directly to an attribute of an object, can be ameliorated by making _all_people a property to obscure the fact that it's a function call:
class Person(Node):
@property
def _all_people(self):
return {k: v for k, v in Node._all_nodes.items() if isinstance(k, Person)}This works, but there's a larger problem that prevents it from being useful: it requires an instance, rather than just the name of the class, in order to get access to the _all_people attribute. But if I really want a list of _all_people, I'm probably working on a higher-level task and don't happen to have an instance of Person ready to hand so that I can examine the property's returned value!
What I'd really like is to make the _all_people property callable from the class definition, without an instance, like it is with Node. But when I try this:
class Person(Node):
@classmethod
@property
def _all_people(cls):
return {p: cls._all_nodes[p] for p in cls._all_nodes if isinstance(cls._all_nodes[p], Person)}I get code that runs, but doesn't effectively access the data: p = Person(); print(p._all_people) prints, not the dictionary, but rather <bound method ? of <class '__main__.Person'>>, which is not at all helpful.
Inverting the order of the @property and @classmethod decorators gives me the error TypeError: 'classmethod' object is not callable.
Is there some productive way that I can use @property and @classmethod to decorate the same method?
This is Python 3.5 under x64 Linux.
Videos
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.
Like I always did before 3.9, nonetheless: a custom "property" rewrite.
The problem is, "property" does a lot of things, and if one will need everything its in there, it is a lot of code.
I guess it is possible to just subclass property itself, so that we can get an extra .class_getter decorator.
A class setter, obviously, would involve either a custom metaclass or an especialisation of __setattr__.
Let's see if I can come with a reasonably short classproperty.
[after tinkering a bit]
So, it turns out simply inheriting property and adding a decorator for a "class getter" is not easily feasible - "property" is not written with subclassing and expanding its functionality in mind.
Therefore, the "easy" thing, and subset is to write a custom descriptor decorator, which will just convert a single method into a classgetter - and no set, del or inheritance support at all.
On the other hand, the code is short and simple:
class classproperty:
def __init__(self, func):
self.fget = func
def __get__(self, instance, owner):
return self.fget(owner)
And this simply works as expected:
In [19]: class A:
...: @classproperty
...: def test(cls):
...: return f"property of {cls.__name__}"
...:
In [20]: A.test
Out[20]: 'property of A'
Another way, if one wants to go all the way to have a class attribute setter, it is a matter of writing a plain property on a custom metaclass (which can exist just for holding the desired properties). This approach however will render the properties invisible on the instances - they will work only on the class itself:
In [22]: class MetaA(type):
...: @property
...: def test(cls):
...: return cls._test
...: @test.setter
...: def test(cls, value):
...: cls._test = value.upper()
...:
In [23]: class A(metaclass=MetaA):
...: pass
...:
In [24]: A.test = "hello world!"
In [25]: A.test
Out[25]: 'HELLO WORLD!'
In [26]: A().test
--------------------------------------------------------------
...
AttributeError: 'A' object has no attribute
I think this is a very good question, and I wish it had a better answer. My favourite approach that I was able to find was using __init_subclass__ from this answer.