我有一个带有两个类方法的类(使用classmethod()函数),用于获取和设置本质上是静态变量的类。我尝试使用property()函数来处理这些,但它会导致错误。我能够在解释器中重现以下错误:
class Foo(object):
_var = 5
@classmethod
def getvar(cls):
return cls._var
@classmethod
def setvar(cls, value):
cls._var = value
var = property(getvar, setvar)
我可以演示类方法,但它们不能作为属性:
>>> 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
是否可以使用属性()函数与@classmethod装饰函数?
这是我的解决方案,也缓存类属性
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
3.8 < Python < 3.11
可以同时使用这两种装饰器。请看这个答案。
Python < 3.9
属性在类上创建,但会影响实例。所以如果你想要一个classmethod属性,在元类上创建这个属性。
>>> 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
但由于您使用的是元类,因此如果您将类方法移到那里,它会读起来更好。
>>> 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
或者,使用Python 3的元类=…语法,foo类体之外定义的元类,以及负责设置_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
在读Python 2.2发布说明时,我发现了以下内容。
属性的get方法不会被调用
属性作为类访问
属性(C.x)而不是作为一个
实例属性(C().x)。如果你
想要重写__get__操作
当属性用作类时
属性,可以子类化属性-
它本身就是一种新型的字体
扩展它的__get__方法,或者你可以
从头定义描述符类型
通过创建一个新样式的类
定义__get__, __set__和
__delete__方法。
注意:下面的方法实际上并不适用于setter,只适用于getter。
因此,我认为指定的解决方案是创建一个ClassProperty作为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
然而,setter实际上不起作用:
foo.var = 4
assert foo.var == foo._var # raises AssertionError
foo。_var没有改变,您只是用一个新值重写了属性。
你也可以使用ClassProperty作为装饰器:
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