在类方法上使用property()

我有一个类,有两个类方法(使用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

是否可以将property()函数与@classmethod装饰函数一起使用?

135940 次浏览

这是我的建议。不要使用类方法。

认真对待。

在这种情况下使用类方法的原因是什么?为什么不使用普通类的普通对象呢?


如果你只是想改变值,属性并不是很有用,不是吗?只需要设置属性值就可以了。

只有在需要隐藏某些内容时才应该使用属性——这些内容在未来的实现中可能会改变。

也许你的例子被简化了,你漏掉了一些可怕的计算。但看起来这处房产并没有增加多少价值。

受java影响的“隐私”技术(在Python中,属性名以_开头)并不是很有用。谁的隐私?当您拥有源代码时,private的意义有点模糊(就像在Python中那样)。

受Java影响的ejb风格的getter和setter(通常在Python中作为属性完成)是为了方便Java的基本内省以及通过静态语言编译器的检查。所有这些getter和setter在Python中都没有那么有用。

一半的解决方案,类上的__set__仍然不起作用。解决方案是一个同时实现属性和静态方法的自定义属性类

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

没有合理的方法使这个“类属性”系统在Python中工作。

这里有一个不合理的方法。当然,您可以通过增加元类魔法使其更加无缝。

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

问题的症结在于,属性就是Python所说的“描述符”。没有简单的方法来解释这种元编程是如何工作的,所以我必须指向描述符howto

只有当您正在实现一个相当高级的框架时,才需要了解这类事情。比如透明对象持久化或RPC系统,或者一种领域特定的语言。

然而,在对之前答案的评论中,你说你

需要修改一个属性,使其能够被类的所有实例看到,并且在调用这些类方法的作用域中没有对类的所有实例的引用。

在我看来,你真正想要的是观察者设计模式。

因为我需要修改一个属性,以一种可以被类的所有实例看到的方式,并且在调用这些类方法的作用域中没有对类的所有实例的引用。

您是否至少可以访问该类的一个实例?我可以想到一个办法:

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

尝试一下,它可以在不更改/添加大量现有代码的情况下完成工作。

>>> 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

property函数需要两个callable参数。给它们lambda包装器(它将实例作为第一个参数传递),一切都很好。

阅读Python 2.2发布注释,我发现以下内容。

属性的get方法不会被调用 属性作为类访问 属性(C.x)而不是作为一个 实例属性(C().x)。如果你 想要重写__get__操作 当属性用作类时 属性,可以子类化属性- 它本身就是一种新型的字体 扩展它的__get__方法,或者你可以 从头定义描述符类型 通过创建一个新样式的类 定义__get__, __set__和 __delete__方法。< / p >

注意:下面的方法实际上并不适用于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

3.8 & lt;Python & lt;3.11

可以同时使用这两种装饰器。看到这个答案

Python & lt;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的metaclass=...语法,以及在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

如果你想通过一个实例化的对象访问class属性,只在元类上设置它是没有帮助的,在这种情况下,你需要在对象上安装一个普通的属性(它分配给class属性)。我认为以下几点说得更清楚一些:

#!/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

我希望这个非常简单的只读@classproperty装饰器能帮助人们查找类属性。

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

是否可以使用属性()函数与类方法装饰函数?

不。

然而,类方法只是一个类的绑定方法(部分函数),可从该类的实例访问。

因为实例是类的函数,你可以从实例中派生类,你可以通过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

这段代码可以用来测试-它应该会通过而不会引发任何错误:

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')

请注意,我们根本不需要元类——无论如何,您都不能通过类的实例直接访问元类。

编写@classproperty装饰器

实际上,你可以通过继承property的子类,在几行代码中创建classproperty装饰器(它是用C实现的,但你可以看到等效的Python 在这里):

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))

然后将decorator视为结合了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

这段代码应该没有错误:

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()

但我不确定这样做是否明智。一个旧的邮件列表文章表明它不应该工作。

让属性在类上工作:

上面的缺点是“类属性”;不能从类中访问,因为它会简单地覆盖类__dict__中的数据描述符。

但是,我们可以用元类__dict__中定义的属性来覆盖它。例如:

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

然后,元类的类实例可以有一个属性,使用前面已经演示过的原则访问类的属性:

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

现在我们看到了两个例子

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

这门课

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

拥有对class属性的访问权。

这里有一个解决方案,它应该既适用于通过类访问,也适用于通过使用元类的实例访问。

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

这也适用于元类中定义的setter。

在搜索了不同的地方之后,我找到了一个定义classproperty的方法

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)

希望这能帮助到一些人:)

Python 3 !

在python >= 3.9中有一个更简洁的方法,请参见@Amit Portnoy的回答


老问题,很多观点,迫切需要一个唯一正确的Python 3方式。

幸运的是,使用metaclass kwarg很简单:

class FooProperties(type):
    

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


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

然后,>>> Foo.var

“喷火!”

这是我的解决方案,也缓存类属性

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

Python 3.9中,你可以将它们一起使用,但是(正如在@xgt的评论中所指出的那样)在Python 3.11中是删除,因此不建议使用

查看此处的版本备注:

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

然而,它过去是这样工作的:

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

顺序很重要——由于描述符的交互方式,@classmethod必须在最上面。

我找到了一个解决这个问题的简单方法。它是一个名为classutilities (pip install classutilities)的包,参见文档这里是PyPi

考虑的例子:

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

你可以在类级和实例级使用它:

# 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

如您所见,它在所有情况下都能正常工作。

基于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)

对于Python 3.9之前的函数式方法,您可以使用以下方法:

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

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)