How to create a read-only class property in Python?
基本上我想做这样的事情:
1 2 3 4 5 6 | class foo: x = 4 @property @classmethod def number(cls): return x |
那么,我想让以下内容发挥作用:
1 2 | >>> foo.number 4 |
不幸的是,上述方法行不通。不是给我
这将使
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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时的常见情况如下:
1 2 3 4 5 | class Foo(object): @property def number(self): ... foo = Foo() |
类似地,如果希望
请注意,这种只读并不能免除黑客的攻击。通过更改foo的可写属性班级:
1 2 3 4 5 6 | class Base(type): pass Foo.__class__ = Base # makes Foo.number a normal class attribute Foo.number = 6 print(Foo.number) |
印刷品
1 | 6 |
或者,如果您希望使
1 2 3 4 5 6 7 8 9 10 11 12 | 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。
当从类访问时(即当
如果这不是您想要的,您可以编写一个始终使用类对象(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> 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 |
我同意Unubtu的回答;但是,它似乎可以工作,但是它不能与Python3上的这种精确语法一起工作(特别是,Python3.4正是我所要解决的问题)。下面是必须如何在Python3.4下形成模式才能使事情正常进行,看起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
米哈伊尔·杰拉西莫夫的解相当完整。不幸的是,这是一个缺点。如果有一个类使用其ClassProperty,则任何子类都不能使用它,因为
幸运的是,这是可以修复的。创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 """ # Use type(cls) to use metaclass of given class class Meta(type(cls)): 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 |
上述解决方案的问题在于,它无法从实例变量访问类变量:
1 2 3 4 5 6 | print(Foo.number) # 4 f = Foo() print(f.number) # 'Foo' object has no attribute 'number' |
此外,使用元类显式并不像使用常规的
我试图解决这个问题。以下是它的工作原理:
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 | @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 |
如您所见,我们有与类变量的
解决方案也适用于只读类属性。
实现方法如下:
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 48 49 50 51 52 53 54 55 56 57 | 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 |
注意:代码测试不多,如果不能如您所期望的那样工作,请随时注意。