Использование свойства () для методов класса

174

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

Марк Родди
источник

Ответы:

90

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

или, используя 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
А. Коади
источник
1
Это не работает для меня в Python 3.2. Если я изменю метакласс foo .__ __. Var = property (foo.getvar.im_func, foo.setvar.im_func) на foo .__ метакласс __. Var = property (foo.getvar .__ func__, foo.setvar .__ func__) я получу «AttributeError: type Объект 'foo' не имеет атрибута 'var' 'при выполнении "foo.var".
Майкл Келли
SIGH двойная коррекция: это работает в Python 2.7, но не в Python 3.2.
Майкл Келли
@MichaelKelley - Это потому, что синтаксис для метаклассов изменился в Python 3.x
mac
1
Я не совсем уверен, что пойму, каким будет Python 3.x способ написать это тогда?
SylvainD
8
@Josay: сначала нужно определить метакласс, а затем определить класс с использованием нового class Foo(metaclass=...)синтаксиса.
Кевин
69

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

Метод get [свойства] не будет вызываться, когда к свойству обращаются как к атрибуту класса (Cx), а не как к атрибуту экземпляра (C (). X). Если вы хотите переопределить операцию __get__ для свойств при использовании в качестве атрибута класса, вы можете создать свойство подкласса - это сам тип нового стиля - чтобы расширить его метод __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
Джейсон Р. Кумбс
источник
20
Я не думаю, что установочная часть ClassProperty на самом деле работает так, как описано: в то время как все утверждения примера проходят, в конце foo._var == 4 (не 3, как подразумевается). Установка свойства перекрывает само свойство. Когда свойства класса обсуждались на python-dev, было отмечено, что, хотя геттеры тривиальны, сеттеры сложны (невозможны?) Без метакласса
Габриэль Грант
4
@ Габриэль Совершенно верно. Я не могу поверить, что никто не указал на это в течение двух лет.
AGF
Я также не уверен, почему вы не просто используете self.fget(owner)и устраняете необходимость использовать здесь @classmethodвообще? (это то, что classmethod делает , перевести .__get__(instance, owner)(*args, **kwargs)на function(owner, *args, **kwargs)вызовы через посредника; свойства не нуждаются в посреднике).
Мартин Питерс
В вашей демонстрации отсутствует какое-либо реальное преобразование либо в методе получения, либо в установщике, который четко продемонстрировал бы, что ваше foo.var = 3назначение фактически не проходит через свойство , а вместо этого просто заменил объект свойства на fooцелое число. Если вы добавите assert isinstance(foo.__dict__['var'], ClassProperty)вызовы между вашими утверждениями, вы увидите, что сбой после foo.var = 3выполнения.
Мартин Питерс
1
Классы Python не поддерживает дескриптор привязки по настройке на самом классе , только на получение (так instance.attr, instance.attr = valueи del instance.attrвсе будет связывать дескриптор найдены на type(instance), но в то время как classobj.attrпривязки, classobj.attr = valueи del classobj.attrничего не и вместо того, чтобы заменить или удалить сам объект дескриптора). Вам нужен метакласс для поддержки установки и удаления (создание объекта класса экземпляром, а метакласса типом).
Мартин Питерс
56

Я надеюсь, что этот простой и понятный @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
Денис Рыжков
источник
2
Это работает с подклассами? (может ли подкласс переопределить свойства класса?)
zakdances
1
Хм да? class D(C): x = 2; assert D.x == 2
dtheodor
Я хотел бы, чтобы это работало, когда я использую его .formatкак "{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
2rs2ts
@ Натан Не только ... когда вы устанавливаете его, вы переопределяете все xправа доступа 10. Мне нравится этот подход, потому что он аккуратный и простой, но звучит как антипаттерн
Микеле д'Амико
Легко исправить: добавить, __set__что вызывает, ValueErrorчтобы предотвратить переопределение.
Киран Джонналагадда
30

Можно ли использовать функцию 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декоратор всего за несколько строк кода, создав подклассы property(он реализован в 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's property"""
        return type(self).foo

И теперь мы видим как экземпляр

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

и класс

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

иметь доступ к свойству класса.

Аарон Холл
источник
3
Ясно, сжато, тщательно: это должен быть принятый ответ.
Пфабри
25

Python 3!

Старый вопрос, множество мнений, крайне нуждающихся в единственно верном Python 3 пути.

К счастью, с metaclasskwarg это легко :

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

Затем, >>> Foo.var

'Foo!

OJFord
источник
1
то есть нет простого выхода из коробки
Мехмет
@ mehmet Разве это не просто? Fooявляется экземпляром его метакласса и @propertyможет использоваться для его методов так же, как и для методов экземпляров Foo.
OJFord
2
Вы должны были определить другой класс для класса, который вдвое сложнее, предполагая, что метакласс не может быть повторно использован.
Мехмет
Метод класса работает как от класса, так и от экземпляра. Это свойство работает только из класса. Я не думаю, что об этом просят.
Аарон Холл
1
@AaronHall Если это важно, его легко добавить Foo.__new__. Хотя в этот момент, возможно, стоит либо вместо этого использовать getattribute, либо задаться вопросом, существует ли претензия в отношении языковой функции - это действительно тот подход, который вы хотите использовать вообще.
OJFord
16

Нет разумного способа заставить эту систему "свойства класса" работать в 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 .

ddaa
источник
Мне нравится идея примера кода, но кажется, что это будет немного неуклюже на практике.
Марк Родди
Я пытаюсь выполнить довольно простую настройку и получить один атрибут, который используется в качестве флага для изменения поведения всех экземпляров, поэтому я думаю, что Observer будет преувеличен из-за того, что я пытаюсь сделать. Если бы было несколько атрибутов в вопросе, то я был бы более склонен.
Марк Родди
Похоже, что простое обнародование функций и их прямой вызов было самым простым решением. Мне было любопытно, делал ли я что-то не так или пытался это сделать, было невозможно. Извините за несколько комментариев, кстати. Ограничение в 300 символов - отстой.
Марк Родди
Отличная вещь в примере кода состоит в том, что вы можете реализовать все неуклюжие биты один раз, а затем наследовать их. Просто переместите _var в производный класс. класс D1 (Foo): _var = 21 класс D2 (Foo) _var = "Hello" D1.var 21 D2.var Hello
Thomas L Holaday
6

Установка его только для метакласса не поможет, если вы хотите получить доступ к свойству класса через инстанцированный объект, в этом случае вам также необходимо установить обычное свойство для объекта (который отправляется свойству класса). Я думаю, что следующее немного более понятно:

#!/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
Нильс Филиппсен
источник
3

Половина решения, __set__ в классе не работает, по-прежнему. Решение представляет собой пользовательский класс свойств, реализующий как свойство, так и статический метод.

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
Флориан Бёш
источник
3

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

У вас есть доступ хотя бы к одному экземпляру класса? Тогда я могу придумать, как это сделать:

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
Джон Милликин
источник
2

Попробуйте, это выполнит работу без необходимости изменять / добавлять много существующего кода.

>>> 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аргумента. дать им лямбда-обертки (которые он передает экземпляр в качестве первого аргумента), и все хорошо.

Суфьян
источник
Как указывает Флориан Беш, для синтаксиса (сторонних библиотек или устаревшего кода) требуется foo.var.
Томас Л Холэдэй
2

Вот решение, которое должно работать как для доступа через класс, так и для доступа через экземпляр, который использует метакласс.

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

Это также работает с сеттером, определенным в метаклассе.

papercrane
источник
1

После поиска в разных местах я нашел метод для определения свойства класса, допустимого для 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)

Надеюсь, что это может помочь кому-то :)

не указано
источник
1
Если этот метод - то, что вы где-то нашли, было бы хорошо дать ссылку (см. Как ссылаться на материал, написанный другими )
Эндрю Майерс,
-27

Вот мое предложение. Не используйте методы класса.

Шутки в сторону.

В чем причина использования методов класса в этом случае? Почему бы не иметь обычный объект обычного класса?


Если вы просто хотите изменить значение, свойство не очень полезно, не так ли? Просто установите значение атрибута и покончите с этим.

Свойство следует использовать только в том случае, если есть что скрывать, что может измениться в будущей реализации.

Может быть, ваш пример урезан, и у вас закончились какие-то адские вычисления. Но это не похоже, что собственность добавляет значительную ценность.

Техники «конфиденциальности» под влиянием Java (в Python имена атрибутов начинаются с _) не очень полезны. Рядовой от кого? Точка приватности немного туманна, когда у вас есть источник (как вы делаете в Python.)

Получающие влияние Java-методы в стиле EJB и сеттеры (часто выполняемые как свойства в Python) предназначены для облегчения примитивного самоанализа Java, а также для прохождения проверки с помощью компилятора статического языка. Все эти методы получения и установки не так полезны в Python.

С. Лотт
источник
14
Потому что мне нужно изменить атрибут, который таким образом, который виден всем экземплярам класса, и в области, из которой вызываются эти методы класса, не имеет ссылок на все экземпляры класса.
Марк Родди