Recently, with the change of the @classmethod
decorator no longer being able to wrap the @property
decorator (Python >= 3.11), there has been significant interest in how to create a @classproperty
decorator. The discussion thus far only talks about getter
methods, rather than setters.
I want to create a descriptor decorator @classproperty
that operates the same on both the class type as well as a class instance. For example:
class classproperty:
def __init__(self, fget=None, fset=None):
self.fget = fget
self.fset = fset
super().__init__()
def __get__(self, instance, owner):
return self.fget(owner)
def __set__(self, instance, value):
owner = type(instance)
self.fset(owner, value)
def setter(self, fset):
self.fset = fset
return self
class A():
_x = 0
@classproperty
def x(cls):
return cls._x
@x.setter
def x(cls, value):
cls._x = value
I'm stuck that whenever I set the classproperty
through the class (i.e. A.x=1
) instead of the object (i.e. A().x = 1
), the method is overriden rather than calling classproperty.__set__()
. For example:
>>> a = A()
>>> a.x # Calls A.__getattribute__, A.x.__get__
0
>>> a.x = 1 # Calls A.__setattr__, A.x.__set__
>>> a.x # Calls A.__getattribute__, A.x.__get__
1
>>> a._x # Calls A.__getattribute__
1
and
>>> a = A
>>> a.x # Calls A.x.__get__
0
>>> a.x = 1 # Doesn't call __set__, __setattr__, __setattribute__ ## last one fictitious?
>>> a.x # Doesn't call __get__, __getattr__, __getattribute__
1
>>> a._x # Doesn't call __get__, __getattr__, __getattribute__
0
It seems this stems from the implementation of dotted lookups on instances versus classes. This documentation mostly deals with __get__
methods, rather than __set__
. This leads me to my main question - how does the dotted lookup and assignment work for class types? Is there any way to make the class type to recognize descriptor setter before overriding the class attribute?
I suspect there's something useful in the pure python equivalents for properties and class methods that might provide a path forwards, but I haven't been able to figure it out despite many hours of tinkering.