在类方法上使用property()

时间:2020-03-06 14:40:38  来源:igfitidea点击:

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

解决方案

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

严重地。

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

如果我们只是想更改值,那么属性不是真的很有帮助吗?只需设置属性值并完成操作即可。

仅在有某些要隐藏的地方(可能在将来的实现中更改的地方)时才使用属性。

也许示例被精简了,但我们还没有进行一些令人讨厌的计算。但是,看起来该属性并没有带来可观的价值。

受Java影响的"隐私"技术(在Python中,以_开头的属性名称)并不是很有帮助。私人来自谁?当我们拥有源代码时(就像在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所谓的"描述符"。没有简单快捷的方法来解释这种元编程的工作原理,因此我必须向我们介绍描述符方法。

如果要实现相当高级的框架,则只需要了解这种情况即可。就像透明的对象持久性或者RPC系统,或者一种特定于域的语言。

但是,在评论先前的答案时,我们说的是

need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

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

Because I need to modify an attribute that in such a way that is seen by all instances of a class, and in the scope from which these class methods are called does not have references to all instances of the class.

我们是否有权访问该课程的至少一个实例?我可以想出一种方法来做到这一点:

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

属性函数需要两个可调用的参数。给他们lambda包装器(它将实例作为第一个参数传递),一切都很好。

阅读Python 2.2发行说明,可以发现以下内容。

The get method [of a property] won't be called when
  the property is accessed as a class
  attribute (C.x) instead of as an
  instance attribute (C().x). If you
  want to override the __get__ operation
  for properties when used as a class
  attribute, you can subclass property -
  it is a new-style type itself - to
  extend its __get__ method, or you can
  define a descriptor type from scratch
  by creating a new-style class that
  defines __get__, __set__ and
  __delete__ methods.

注意:以下方法实际上不适用于setter方法,仅适用于getter方法。

因此,我相信规定的解决方案是创建一个ClassProperty作为属性的子类。

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

但是,设置员实际上不起作用:

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

属性是在类上创建的,但会影响实例。因此,如果要使用classmethod属性,请在元类上创建该属性。

>>> class foo(object):
    _var = 5
    class __metaclass__(type):
        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):
        @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

如果我们想通过实例化的对象访问class属性,则仅在meta类上设置它无济于事,在这种情况下,我们还需要在该对象上安装一个常规属性(该属性将分派到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