如何在Python中创build一个只读的类属性?
基本上我想要做这样的事情:
class foo: x = 4 @property @classmethod def number(cls): return x
那么我想下面的工作:
>>> foo.number 4
不幸的是,上述不起作用。 而不是给我4
它给我<property object at 0x101786c58>
。 有没有办法达到上述目的?
property
描述符在从类中访问时总是返回自身(即__get__
方法中的instance
为None
)。
如果这不是你想要的,你可以编写一个总是使用类对象( 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,如果它没有按照你的预期工作,可以随意注意。