如何在Python中创build一个只读的类属性?

基本上我想要做这样的事情:

class foo: x = 4 @property @classmethod def number(cls): return x 

那么我想下面的工作:

 >>> foo.number 4 

不幸的是,上述不起作用。 而不是给我4它给我<property object at 0x101786c58> 。 有没有办法达到上述目的?

property描述符在从类中访问时总是返回自身(即__get__方法中的instanceNone )。

如果这不是你想要的,你可以编写一个总是使用类对象( owner )而不是实例的新描述符:

 >>> class classproperty(object): ... def __init__(self, getter): ... self.getter= getter ... def __get__(self, instance, owner): ... return self.getter(owner) ... >>> class Foo(object): ... x= 4 ... @classproperty ... def number(cls): ... return cls.x ... >>> Foo().number 4 >>> Foo.number 4 

这将使Foo.number成为只读属性:

 class MetaFoo(type): @property def number(cls): return cls.x class Foo(object, metaclass=MetaFoo): x = 4 print(Foo.number) # 4 Foo.number = 6 # AttributeError: can't set attribute 

说明 :使用@property时的通常情况如下所示:

 class Foo(object): @property def number(self): ... foo = Foo() 

Foo定义的属性对于其实例是只读的。 也就是说, foo.number = 6会引发一个AttributeError

类似的,如果你想让Foo.number引发一个AttributeError你将需要设置一个在type(Foo)定义的属性。 因此需要一个元类。


请注意,这种只读方式并不能抵御黑客的攻击。 可以通过更改Foo的类来使该属性可写:

 class Base(type): pass Foo.__class__ = Base # makes Foo.number a normal class attribute Foo.number = 6 print(Foo.number) 

版画

 6 

或者,如果您希望将Foo.number为可设置的属性,

 class WritableMetaFoo(type): @property def number(cls): return cls.x @number.setter def number(cls, value): cls.x = value Foo.__class__ = WritableMetaFoo # Now the assignment modifies `Foo.x` Foo.number = 6 print(Foo.number) 

也打印6。

我同意unubtu的回答 。 它似乎工作,但是,它不适用于Python 3上的这个精确的语法(具体来说,Python 3.4是我所挣扎的)。 下面是如何构buildPython 3.4下的模式来使事情工作,看来:

 class MetaFoo(type): @property def number(cls): return cls.x class Foo(metaclass=MetaFoo): x = 4 print(Foo.number) # 4 Foo.number = 6 # AttributeError: can't set attribute 

上面的解决scheme的问题是,它不会从实例variables访问类variables:

 print(Foo.number) # 4 f = Foo() print(f.number) # 'Foo' object has no attribute 'number' 

而且,使用元类明确性不如使用常规property装饰器那么好。

我试图解决这个问题。 这里现在是如何工作的:

 @classproperty_support class Bar(object): _bar = 1 @classproperty def bar(cls): return cls._bar @bar.setter def bar(cls, value): cls._bar = value # @classproperty should act like regular class variable. # Asserts can be tested with it. # class Bar: # bar = 1 assert Bar.bar == 1 Bar.bar = 2 assert Bar.bar == 2 foo = Bar() baz = Bar() assert foo.bar == 2 assert baz.bar == 2 Bar.bar = 50 assert baz.bar == 50 assert foo.bar == 50 

正如你所看到的,我们有@classproperty ,它和@classproperty对于类variables的工作方式一样。 我们只需要额外的@classproperty_support类装饰器。

解决scheme也适用于只读类属性。

这里是实现:

 class classproperty: """ Same as property(), but passes obj.__class__ instead of obj to fget/fset/fdel. Original code for property emulation: https://docs.python.org/3.5/howto/descriptor.html#properties """ def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj.__class__) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj.__class__, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj.__class__) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__) def classproperty_support(cls): """ Class decorator to add metaclass to our class. Metaclass uses to add descriptors to class attributes, see: http://stackoverflow.com/a/26634248/1113207 """ class Meta(type): pass for name, obj in vars(cls).items(): if isinstance(obj, classproperty): setattr(Meta, name, property(obj.fget, obj.fset, obj.fdel)) class Wrapper(cls, metaclass=Meta): pass return Wrapper 

注意:代码没有太多的testing,如果它没有按照你的预期工作,可以随意注意。