Как создать свойство класса только для чтения в Python?
по сути, я хочу сделать что-то вроде этого:
class foo:
x = 4
@property
@classmethod
def number(cls):
return x
тогда я хотел бы, чтобы работали следующие:
>>> foo.number
4
к сожалению, выше не работает. Вместо того, чтобы дать мне 4
это дает мне <property object at 0x101786c58>
. Есть ли способ достичь вышеуказанного?
5 ответов:
The
property
дескриптор всегда возвращает себя при обращении из класса (т. е.. когдаinstance
иNone
в своем__get__
метод).если это не то, что вы хотите, вы можете написать новый дескриптор, который всегда использует объект класса (
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
a только для чтения свойства: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-это то, с чем я боролся). Вот как нужно сформировать шаблон под Python 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
проблема с решениями выше заключается в том, что он не будет работать для доступа к переменным класса из переменной экземпляра:
print(Foo.number) # 4 f = Foo() print(f.number) # 'Foo' object has no attribute 'number'
более того, использование метакласса explicit не так приятно, как использование обычного
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
это работает так же, как@property
для переменных класса. Единственное, что нам понадобится-это дополнительные@classproperty_support
класс оформитель.решение также работает для свойств только для чтения класса.
вот: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
Примечание: код не тестируется много, не стесняйтесь отметить, если он не работает, как вы ожидаете.
решение Михаила Герасимова вполне закончено. К сожалению, это был один недостаток. Если у вас есть класс, использующий его classproperty, ни один дочерний класс не может использовать его из-за
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Сclass Wrapper
.к счастью, это можно исправить. Просто наследовать от класса данного класса при создании
class Meta
.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