Вопрос-Ответ

Using property() on classmethods

Использование property() в classmethods

У меня есть класс с двумя методами класса (использующими 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() функцию с @classmethod оформленными функциями?

Переведено автоматически
Ответ 1

3.8 < Python < 3.11

Можно использовать оба декоратора вместе. Смотрите Этот ответ .

Python 2 и python 3 (работает и в 3.9-3.10)

Свойство создается в классе, но влияет на экземпляр. Итак, если вам нужно 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

Но поскольку вы все равно используете метакласс, он будет читаться лучше, если вы просто переместите classmethods туда.

>>> 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

или, используя metaclass=... синтаксис Python 3 и метакласс, определенный вне 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

Обновление: в @classmethodPython 3.13@property была удалена возможность связывания и 😕.

В Python 3.9 Вы могли бы использовать их вместе, но (как отмечено в комментарии @xgt) это было устаревшим в Python 3.11, поэтому оно больше не поддерживается (но оно может работать некоторое время или быть повторно введено в какой-то момент).

Проверьте примечания к версии здесь:

https://docs.python.org/3.11/library/functions.html#classmethod

Однако раньше это работало следующим образом:

class G:
@classmethod
@property
def __doc__(cls):
return f'A doc for {cls.__name__!r}'

Порядок имеет значение - из-за того, как взаимодействуют дескрипторы, @classmethod должен быть сверху.

Ответ 3

Я надеюсь, что этот предельно простой, доступный только для чтения @classproperty декоратор поможет кому-нибудь, кто ищет classproperties.

class classproperty(property):
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

Читая примечания к выпуску Python 2.2, я нахожу следующее.


Метод get [свойства] не будет вызываться, когда к свойству обращаются как к атрибуту класса (C.x), а не как к атрибуту экземпляра (C().x). Если вы хотите переопределить операцию __get__ для свойств при использовании в качестве атрибута класса, вы можете создать подкласс property - это сам тип нового стиля - для расширения его метода __get__ , или вы можете определить тип дескриптора с нуля, создав класс нового стиля, который определяет методы __get__, __set__ и __delete__ .


ПРИМЕЧАНИЕ: Приведенный ниже метод на самом деле не работает для установщиков, только для получателей.

Поэтому я считаю, что предписанным решением является создание ClassProperty в качестве подкласса property .

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
2024-01-28 13:16 python