我有一个带有两个类方法的类(使用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装饰函数?


当前回答

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

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。

其他回答

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

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

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

认真对待。

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


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

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

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

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

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

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

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

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

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

No.

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

因为实例是类的一个函数,你可以从实例中派生类,你可以通过property从class-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装饰器

你实际上可以通过子类化属性在几行代码中创建一个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()

但我不确定这样做是否明智。一篇旧的邮件列表文章认为这种方法行不通。

让属性在类上工作:

上面的缺点是“class属性”不能从类中访问,因为它会简单地覆盖类__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属性的访问权。