在类方法上使用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()函数与装饰有类方法的函数一起使用?
解决方案
这是我的建议。不要使用类方法。
严重地。
在这种情况下使用类方法的原因是什么?为什么没有普通类的普通对象呢?
如果我们只是想更改值,那么属性不是真的很有帮助吗?只需设置属性值并完成操作即可。
仅在有某些要隐藏的地方(可能在将来的实现中更改的地方)时才使用属性。
也许示例被精简了,但我们还没有进行一些令人讨厌的计算。但是,看起来该属性并没有带来可观的价值。
受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