Using property() on classmethods
我有一个类,它有两个类方法(使用classmethod()函数),用于获取和设置基本上是静态变量的内容。我尝试将property()函数与这些函数一起使用,但它会导致一个错误。我能够在解释器中用以下代码重现错误:
1 2 3 4 5 6 7 8 9 | class Foo(object): _var = 5 @classmethod def getvar(cls): return cls._var @classmethod def setvar(cls, value): cls._var = value var = property(getvar, setvar) |
我可以演示类方法,但它们不能用作属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> 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修饰函数一起使用?
属性在类上创建,但会影响实例。因此,如果需要ClassMethod属性,请在元类上创建该属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | >>> 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 |
但是,由于您无论如何都在使用元类,所以如果您将类方法移到其中,它会读得更好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> 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的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> 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 |
在阅读Python2.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。
因此,我认为规定的解决方案是创建一个类属性作为属性的子类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |
但是,设置器实际上不工作:
1 2 | foo.var = 4 assert foo.var == foo._var # raises AssertionError |
您也可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
我希望这个死了的简单的只读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class classproperty(object): def __init__(self, fget): self.fget = fget 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 |
Is it possible to use the property() function with classmethod decorated functions?
不。
但是,ClassMethod只是类实例中可访问的类上的绑定方法(部分函数)。
由于实例是类的函数,可以从实例中派生类,因此可以从带有
1 2 3 4 5 6 7 8 9 10 11 | 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 |
此代码可用于测试-它应通过而不会引发任何错误:
1 2 3 4 5 6 7 | 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') |
请注意,我们根本不需要元类,而且您也不会通过类的实例直接访问元类。
写一个通过对
1 2 3 4 5 6 7 | 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视为结合了属性的ClassMethod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
这段代码应该不会出错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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属性"不能从类中访问,因为它只会覆盖类
但是,我们可以用在元类
1 2 3 4 5 6 7 | 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 |
然后,元类的类实例可以有一个属性,该属性使用前面章节中演示的原理访问类的属性:
1 2 3 4 5 | class FooClassProperty(metaclass=MetaWithFooClassProperty): @property def foo(self): """access the class's property""" return type(self).foo |
现在我们看到了这两个例子
1 2 | >>> FooClassProperty().foo <class '__main__.FooClassProperty'> |
和班级
1 2 | >>> FooClassProperty.foo <class '__main__.FooClassProperty'> |
有权访问类属性。
Python3!
老问题,很多视图,非常需要一个真正的python 3方式。
幸运的是,使用
1 2 3 4 5 6 7 8 | class FooProperties(type): @property def var(cls): return cls._var class Foo(object, metaclass=FooProperties): _var = 'FOO!' |
那么,
'FOO!'
没有合理的方法让这个"类属性"系统在Python中工作。
这是一个不合理的方法使它发挥作用。随着元类魔法的增加,你当然可以使它更加无缝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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所称的"描述符"。没有简单而简单的方法来解释这种元编程是如何工作的,所以我必须向您指出描述符how to。
只有在实现相当高级的框架时,才需要理解这类事情。像透明对象持久性或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.
在我看来,你真正想要的是观察者设计模式。
如果您希望通过实例化的对象访问Class属性,那么只在元类上设置它并没有帮助,在这种情况下,您还需要在对象上安装一个普通的属性(它发送到Class属性)。我认为下面的内容更清楚一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #!/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 |
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.
您是否可以访问该类的至少一个实例?我可以想出一个办法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
半个解决方案,在课堂上设置仍然不起作用。解决方案是一个自定义属性类,实现属性和静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 |
这里有一个解决方案,它可以通过类进行访问,也可以通过使用元类的实例进行访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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。
尝试一下,它可以在不需要更改/添加大量现有代码的情况下完成任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> 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 |
在搜索了不同的位置后,我找到了一个定义ClassProperty的方法对python 2和3有效。
1 2 3 4 5 6 7 8 9 10 11 | 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) |
希望这能帮助别人:)
这是我的建议。不要使用类方法。
说真的。
在这种情况下使用类方法的原因是什么?为什么没有一个普通类的普通对象呢?
如果你只是想改变这个值,一个属性并不是很有用,是吗?只需设置属性值并完成它。
只有在需要隐藏某些内容时才应使用属性,这些内容在将来的实现中可能会发生更改。
也许你的例子很简单,还有一些你遗漏了的地狱计算。但它看起来并没有增加显著的价值。
Java影响的"隐私"技术(在Python中,从属性开始的名称)并不是非常有用。谁的私人?当您拥有源代码时,private的意义有点模糊(就像在python中那样)。
Java影响的EJB风格的吸收器和定位器(通常以Python中的属性完成)有助于Java的原始自省,并通过静态语言编译器传递。所有这些getter和setter在Python中都没有那么有用。