在类方法上使用property()

我有一个类与两个类方法(使用classmethod()函数)获取和设置本质上是一个静态variables。 我试图用这些属性()函数,但它会导致一个错误。 我可以在翻译中重现以下错误:

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装饰function的property()函数?

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

当属性作为类属性(Cx)而不是作为实例属性(C()。x)访问时,不会调用get [方法]属性。 如果要在用作类属性时覆盖属性的__get__操作,则可以inheritance属性(它本身就是一种新型的types)来扩展其__get__方法,或者可以通过创build新的属性来从头开始定义描述符types定义__get__,__set__和__delete__方法的types类。

注意:下面的方法实际上并不适用于setter,只有getter。

因此,我相信规定的解决scheme是创build一个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 

属性在类上创build,但会影响实例。 所以,如果你想要一个classmethod属性,在元类上创build属性。

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

但是,既然你使用的是元类,如果你只是把classmethods移到那里,它会更好。

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

我希望这个简单的只读@classproperty装饰器可以帮助寻找类属性的人。

 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 Cx == 1 assert C().x == 1 

没有合理的方法来使这个“类属性”系统在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系统,或者一种特定于领域的语言。

但是,在对先前答复的评论中,你说你

需要修改一个属性,这个属性被一个类的所有实例看到,并且在这个类方法被调用的范围内没有引用类的所有实例。

在我看来,你真正想要的是Observerdevise模式。

是否有可能使用classmethod装饰function的property()函数?

没有。

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

由于实例是类的一个function,您可以从实例中派生类,您可以从具有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 

这个代码可以用来testing – 它应该通过而不会产生任何错误:

 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装饰器

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

然后,把装饰器当作一个classmethod与属性结合使用:

 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() 

但我不确定这将是多么好的build议。 一个旧的邮件列表文章表明它不应该工作。

如果你想通过一个实例化的对象来访问类属性,那么仅仅在元类上设置它是没有帮助的,在这种情况下,你也需要在对象上安装一个普通的属性(调度到类的属性)。 我认为以下几点更加清楚:

 #!/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 

因为我需要修改一个属性,这个属性可以被一个类的所有实例看到,并且在这个类的方法被调用的范围内没有对类的所有实例的引用。

你有没有至less有一个类的实例? 那么我可以想办法做到这一点:

 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 

一半的解决scheme,__set__在课堂上仍然不起作用。 解决scheme是实现属性和静态方法的自定义属性类

 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 

试一试,它可以完成工作,而无需更改/添加大量现有的代码。

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

property函数需要两个callable参数。 给他们lambda包装(它通过实例作为第一个参数),一切都很好。

这是一个解决scheme,应该通过类访问和访问通过使用元类的实例。

 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。

在search不同的地方后,我find了一个方法来定义一个Python 2和3有效的类属性。

 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) 

希望这可以帮助某人:)

这不是一个额外的答案,它只是上面的一些示例的扩展,以certificate“setter”行为不适用于类属性。 我写了这本书来帮助我理解发生了什么,并认为这可能会帮助别人理解这个问题。 (感谢这些答案的作者,尤其是@Jason R. Coombs,@Denis Ryzhkov和@Aaron Hall)。

 # @Aaron Hall's example 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)) class Demo: base_value = 100 via_property_value = None @classmethod def calc_val(cls): return cls.base_value + 1 @classproperty def calc_via_property(cls): if cls.via_property_value: return cls.base_value + cls.via_property_value return cls.base_value @calc_via_property.setter def set_via_property_value(cls, value): # This NEVER gets called cls.via_property_value = value # test it works via a classmethod assert Demo.calc_val() == 101 assert Demo().calc_val() == 101 # test it works via a 'classproperty' assert Demo.calc_via_property == 100 assert Demo().calc_via_property == 100 # test is works via a 'classproperty' if the internal value is changed Demo.via_property_value = 100 assert Demo.calc_via_property == 200 assert Demo().calc_via_property == 200 # test it sets the internal value correctly # this actually overrides the property definition instead of the internal value Demo.calc_via_property = 50 assert Demo.via_property_value == 50 # remains unchanged at 100 assert Demo.calc_via_property == 150 # is now the value 50 rather than a function assert Demo().calc_via_property == 150 # is now the value 50 rather than a function 

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

认真。

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


如果你只是想改变价值,一个属性是不是真的很有帮助呢? 只需设置属性值并完成它。

只有在有隐瞒的情况下才能使用财产 – 这在未来的实施中可能会发生变化。

也许你的榜样被剥夺了,而且你还有一些遗憾的计算。 但它看起来并不像财产增加显着的价值。

受Java影响的“隐私”技术(在Python中,以_开头的属性名称)并不十分有用。 私人从谁? 当你拥有源代码的时候,私密性是有点模糊的(就像你在Python中那样)。

受Java影响的EJB风格的获取器和设置器(通常在Python中作为属性完成)可以促进Java的原始自省,并且可以通过静态语言编译器。 所有这些getter和setter在Python中都没有那么有用。