在类方法上使用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

