По сути, я хочу сделать что-то вроде этого:
class foo:
x = 4
@property
@classmethod
def number(cls):
return x
Затем я хотел бы, чтобы следующее работало:
>>> foo.number
4
К сожалению, указанное выше не работает. Вместо того, чтобы дать мне 4
, он дает мне <property object at 0x101786c58>
. Есть ли способ достичь вышеуказанного?
Ответ 1
Дескриптор 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
Ответ 2
Это сделает 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's
класс:
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.
Ответ 3
Я согласен с unubtu answer; он, похоже, работает, однако, он не работает с этим точным синтаксисом на 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
Ответ 4
Проблема с решениями выше заключается в том, что она не будет работать для доступа к переменным класса из переменной экземпляра:
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
, который работает так же, как @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
Примечание: код не проверен много, не стесняйтесь, если он не работает так, как вы ожидаете.