Использование свойства() в методах класса
У меня есть класс с двумя методами класса (с помощью функции classmethod()) для получения и установки того, что по существу является статической переменной. Я попытался использовать функцию property() с ними, но это приводит к ошибке. Я смог воспроизвести ошибку в интерпретаторе следующим образом:
class Foo(object):
_var = 5
@classmethod
def getvar(cls):
return cls._var
@classmethod
def setvar(cls, value):
cls._var = value
var = property(getvar, setvar)
Я могу продемонстрировать методы класса, но они не работают как свойства:
>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
Можно ли использовать функцию property() с классами, украшенными функциями?
Ответы
Ответ 1
Свойство создается в классе, но влияет на экземпляр. Поэтому, если вам нужно свойство classmethod, создайте свойство в метаклассе.
>>> class foo(object):
... _var = 5
... class __metaclass__(type): # Python 2 syntax for metaclasses
... pass
... @classmethod
... def getvar(cls):
... return cls._var
... @classmethod
... def setvar(cls, value):
... cls._var = value
...
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
Но так как вы в любом случае используете метакласс, будет лучше читать, если вы просто переместите туда методы класса.
>>> class foo(object):
... _var = 5
... class __metaclass__(type): # Python 2 syntax for metaclasses
... @property
... def var(cls):
... return cls._var
... @var.setter
... def var(cls, value):
... cls._var = value
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
или с использованием синтаксиса Python 3 metaclass=...
и метакласса, определенного вне тела класса foo
, и метакласса, ответственного за установку начального значения _var
:
>>> class foo_meta(type):
... def __init__(cls, *args, **kwargs):
... cls._var = 5
... @property
... def var(cls):
... return cls._var
... @var.setter
... def var(cls, value):
... cls._var = value
...
>>> class foo(metaclass=foo_meta):
... pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3
Ответ 2
Читая примечания к выпуску Python 2.2, я нахожу следующее.
Метод get [свойства] не будет вызываться, когда свойство получает доступ как атрибут класса (Cx) вместо атрибута экземпляра (C(). X). Если вы хотите переопределить операцию __get__ для свойств при использовании в качестве атрибута класса, вы можете использовать свойство subclass - это сам тип нового стиля - расширить его метод __get__ или вы можете определить тип дескриптора с нуля, создав новый класс стиля, который определяет методы __get__, __set__ и __delete__.
ПРИМЕЧАНИЕ. Метод ниже не работает для сеттеров, а только для геттеров.
Поэтому я считаю, что предписанное решение заключается в создании класса ClassProperty в качестве подкласса свойства.
class ClassProperty(property):
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()
class foo(object):
_var=5
def getvar(cls):
return cls._var
getvar=classmethod(getvar)
def setvar(cls,value):
cls._var=value
setvar=classmethod(setvar)
var=ClassProperty(getvar,setvar)
assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3
Однако сеттеры фактически не работают:
foo.var = 4
assert foo.var == foo._var # raises AssertionError
foo._var
не изменился, вы просто перезаписали свойство с новым значением.
Вы также можете использовать ClassProperty
в качестве декоратора:
class foo(object):
_var = 5
@ClassProperty
@classmethod
def var(cls):
return cls._var
@var.setter
@classmethod
def var(cls, value):
cls._var = value
assert foo.var == 5
Ответ 3
Я надеюсь, что этот простейший простой в использовании @classproperty
декоратор поможет кому-то искать классные свойства.
class classproperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class C(object):
@classproperty
def x(cls):
return 1
assert C.x == 1
assert C().x == 1
Ответ 4
Можно ли использовать функцию property() с функциями, украшенными классом?
Нет.
Тем не менее, classmethod - это просто связанный метод (частичная функция) класса, доступного из экземпляров этого класса.
Поскольку экземпляр является функцией класса, и вы можете получить класс из экземпляра, вы можете получить любое желаемое поведение, которое вы, возможно, захотите, от свойства класса с property
:
class Example(object):
_class_property = None
@property
def class_property(self):
return self._class_property
@class_property.setter
def class_property(self, value):
type(self)._class_property = value
@class_property.deleter
def class_property(self):
del type(self)._class_property
Этот код можно использовать для тестирования - он должен проходить без каких-либо ошибок:
ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')
И обратите внимание, что нам вообще не нужны метаклассы - и вы не имеете прямого доступа к метаклассу через экземпляры своих классов.
@classproperty
декоратора @classproperty
Фактически вы можете создать декоратор classproperty
только в нескольких строках кода с помощью property
classproperty
(он реализован на C, но вы можете увидеть эквивалентный Python здесь):
class classproperty(property):
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
Затем обработайте декоратор, как если бы это был классный метод в сочетании с свойством:
class Foo(object):
_bar = 5
@classproperty
def bar(cls):
"""this is the bar attribute - each subclass of Foo gets its own.
Lookups should follow the method resolution order.
"""
return cls._bar
@bar.setter
def bar(cls, value):
cls._bar = value
@bar.deleter
def bar(cls):
del cls._bar
И этот код должен работать без ошибок:
def main():
f = Foo()
print(f.bar)
f.bar = 4
print(f.bar)
del f.bar
try:
f.bar
except AttributeError:
pass
else:
raise RuntimeError('f.bar must have worked - inconceivable!')
help(f) # includes the Foo.bar help.
f.bar = 5
class Bar(Foo):
"a subclass of Foo, nothing more"
help(Bar) # includes the Foo.bar help!
b = Bar()
b.bar = 'baz'
print(b.bar) # prints baz
del b.bar
print(b.bar) # prints 5 - looked up from Foo!
if __name__ == '__main__':
main()
Но я не уверен, насколько хорошо это было бы. Старая статья списка рассылки предполагает, что она не должна работать.
Получение свойства для работы над классом:
Недостатком вышесказанного является то, что "свойство класса" недоступно из класса, поскольку оно просто перезаписывает дескриптор данных из класса __dict__
.
Однако мы можем переопределить это с помощью свойства, определенного в метаклассе __dict__
. Например:
class MetaWithFooClassProperty(type):
@property
def foo(cls):
"""The foo property is a function of the class -
in this case, the trivial case of the identity function.
"""
return cls
И тогда экземпляр класса метакласса может иметь свойство, которое обращается к свойству класса, используя принцип, уже продемонстрированный в предыдущих разделах:
class FooClassProperty(metaclass=MetaWithFooClassProperty):
@property
def foo(self):
"""access the class property"""
return type(self).foo
И теперь мы видим как экземпляр
>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>
и класс
>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>
имеют доступ к свойству класса.
Ответ 5
Python 3!
Старый вопрос, множество просмотров, очень нуждающихся в одном-правильном пути Python 3.
К счастью, легко с metaclass
kwarg:
class FooProperties(type):
@property
def var(cls):
return cls._var
class Foo(object, metaclass=FooProperties):
_var = 'FOO!'
Тогда >>> Foo.var
'Foo!
Ответ 6
Нет разумного способа заставить эту систему "свойство класса" работать в Python.
Вот один необоснованный способ заставить его работать. Вы можете сделать его более плавным с увеличением количества метаклассической магии.
class ClassProperty(object):
def __init__(self, getter, setter):
self.getter = getter
self.setter = setter
def __get__(self, cls, owner):
return getattr(cls, self.getter)()
def __set__(self, cls, value):
getattr(cls, self.setter)(value)
class MetaFoo(type):
var = ClassProperty('getvar', 'setvar')
class Foo(object):
__metaclass__ = MetaFoo
_var = 5
@classmethod
def getvar(cls):
print "Getting var =", cls._var
return cls._var
@classmethod
def setvar(cls, value):
print "Setting var =", value
cls._var = value
x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x
Суть проблемы заключается в том, что свойства - это то, что Python называет "дескрипторами". Нет никакого короткого и простого способа объяснить, как работает этот метапрограммирование, поэтому я должен указать вам на дескриптор howto.
Вам нужно когда-либо понимать такие вещи, если вы реализуете довольно продвинутую структуру. Как прозрачная устойчивость объекта или система RPC, или своего рода язык, специфичный для домена.
Однако, в комментарии к предыдущему ответу, вы говорите, что вы
необходимо изменить атрибут, который таким образом воспринимается всеми экземплярами класса, и в области, из которой эти методы класса называются, не имеет ссылок на все экземпляры класса.
Мне кажется, что вы действительно хотите создать шаблон дизайна Observer.
Ответ 7
Установка его только в мета-классе не помогает, если вы хотите получить доступ к свойству класса через экземпляр объекта, в этом случае вам также необходимо установить нормальное свойство на объект (который отправляет свойство класса), Я думаю, что следующее более ясно:
#!/usr/bin/python
class classproperty(property):
def __get__(self, obj, type_):
return self.fget.__get__(None, type_)()
def __set__(self, obj, value):
cls = type(obj)
return self.fset.__get__(None, cls)(value)
class A (object):
_foo = 1
@classproperty
@classmethod
def foo(cls):
return cls._foo
@foo.setter
@classmethod
def foo(cls, value):
cls.foo = value
a = A()
print a.foo
b = A()
print b.foo
b.foo = 5
print a.foo
A.foo = 10
print b.foo
print A.foo
Ответ 8
Половина решения, __set__ в классе не работает, все еще. Решение представляет собой собственный класс свойств, реализующий как свойство, так и staticmethod
class ClassProperty(object):
def __init__(self, fget, fset):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
return self.fget()
def __set__(self, instance, value):
self.fset(value)
class Foo(object):
_bar = 1
def get_bar():
print 'getting'
return Foo._bar
def set_bar(value):
print 'setting'
Foo._bar = value
bar = ClassProperty(get_bar, set_bar)
f = Foo()
#__get__ works
f.bar
Foo.bar
f.bar = 2
Foo.bar = 3 #__set__ does not
Ответ 9
Поскольку мне нужно изменить атрибут, который таким образом воспринимается всеми экземплярами класса, и в области, из которой вызывается эти методы класса, не имеет ссылок на все экземпляры класса.
Имеется ли у вас доступ хотя бы к одному экземпляру класса? Я могу придумать, как это сделать:
class MyClass (object):
__var = None
def _set_var (self, value):
type (self).__var = value
def _get_var (self):
return self.__var
var = property (_get_var, _set_var)
a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var
Ответ 10
Попробуйте попробовать, он выполняет свою работу без необходимости изменять/добавлять много существующего кода.
>>> class foo(object):
... _var = 5
... def getvar(cls):
... return cls._var
... getvar = classmethod(getvar)
... def setvar(cls, value):
... cls._var = value
... setvar = classmethod(setvar)
... var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3
Функция property
нуждается в двух аргументах callable
. дать им лямбда-обертки (которые он передает экземпляр в качестве первого аргумента), и все хорошо.
Ответ 11
Вот решение, которое должно работать как для доступа через класс, так и для доступа через экземпляр, который использует метакласс.
In [1]: class ClassPropertyMeta(type):
...: @property
...: def prop(cls):
...: return cls._prop
...: def __new__(cls, name, parents, dct):
...: # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
...: dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
...: dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
...: return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
...:
In [2]: class ClassProperty(object):
...: __metaclass__ = ClassPropertyMeta
...: _prop = 42
...: def __getattr__(self, attr):
...: raise Exception('Never gets called')
...:
In [3]: ClassProperty.prop
Out[3]: 42
In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1
AttributeError: can't set attribute
In [5]: cp = ClassProperty()
In [6]: cp.prop
Out[6]: 42
In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1
<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
6 # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
7 dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8 dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
9 return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
AttributeError: can't set attribute
Это также работает с установщиком, определенным в метаклассе.
Ответ 12
После поиска в разных местах, я нашел метод для определения classproperty
действителен с Python 2 и 3.
from future.utils import with_metaclass
class BuilderMetaClass(type):
@property
def load_namespaces(self):
return (self.__sourcepath__)
class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
__sourcepath__ = 'sp'
print(BuilderMixin.load_namespaces)
Надеюсь, это может помочь кому-то:)
Ответ 13
Это не дополнительный ответ, это просто расширение некоторых из приведенных выше примеров, чтобы продемонстрировать, что поведение "setter" не работает для свойств класса. Я написал это, чтобы помочь понять, что происходит, и подумал, что это может помочь кому-то понять проблему. (Спасибо авторам многих ответов, особенно @Jason R. Coombs, @Denis Ryzhkov и @Aaron Hall).
# @Aaron Hall example
class classproperty(property):
def __get__(self, obj, objtype=None):
return super(classproperty, self).__get__(objtype)
def __set__(self, obj, value):
super(classproperty, self).__set__(type(obj), value)
def __delete__(self, obj):
super(classproperty, self).__delete__(type(obj))
class Demo:
base_value = 100
via_property_value = None
@classmethod
def calc_val(cls):
return cls.base_value + 1
@classproperty
def calc_via_property(cls):
if cls.via_property_value:
return cls.base_value + cls.via_property_value
return cls.base_value
@calc_via_property.setter
def set_via_property_value(cls, value):
# This NEVER gets called
cls.via_property_value = value
# test it works via a classmethod
assert Demo.calc_val() == 101
assert Demo().calc_val() == 101
# test it works via a 'classproperty'
assert Demo.calc_via_property == 100
assert Demo().calc_via_property == 100
# test is works via a 'classproperty' if the internal value is changed
Demo.via_property_value = 100
assert Demo.calc_via_property == 200
assert Demo().calc_via_property == 200
# test it sets the internal value correctly
# this actually overrides the property definition instead of the internal value
Demo.calc_via_property = 50
assert Demo.via_property_value == 50 # remains unchanged at 100
assert Demo.calc_via_property == 150 # is now the value 50 rather than a function
assert Demo().calc_via_property == 150 # is now the value 50 rather than a function
Ответ 14
Если вы используете Django, у него есть встроенный декоратор @classproperty
.
Ответ 15
Вот мое предложение. Не используйте методы класса.
Серьезно.
В чем причина использования методов класса в этом случае? Почему бы не иметь обычный объект обычного класса?
Если вы просто хотите изменить значение, свойство действительно не очень полезно? Просто установите значение атрибута и сделайте с ним.
Свойство должно использоваться только в том случае, если есть что-то, что можно скрыть - что-то, что может измениться в будущей реализации.
Возможно, ваш пример убирается, и есть какой-то адский расчет, который вы остановили. Но это не похоже на то, что свойство добавляет значительную ценность.
Методы "конфиденциальности", основанные на Java (в Python, имена атрибутов, начинающиеся с _), на самом деле не очень полезны. Кто из них? Точка частного немного туманна, когда у вас есть источник (как и в Python.)
Гейттеры и сеттеры, ориентированные на Java (часто выполняемые как свойства в Python), предназначены для упрощения Java-примитивной интроспекции Java, а также для передачи со статическим языковым компилятором. Все эти геттеры и сеттеры не так полезны в Python.