Використання властивості () на classmethods


173

У мене є клас з двома методами класу (використовуючи функцію 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

Чи можливо використовувати функцію властивості () за допомогою функцій, прикрашених класним методом?

Відповіді:


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 .__ metaclass __. Var = властивість (foo.getvar.im_func, foo.setvar.im_func) на foo .__ metaclass __. Var = властивість (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, було зазначено, що, хоча getters тривіальні, сеттерам важко (неможливо?) Без метакласу
— Габріель Грант

4
@Gabriel Повністю правильно. Я не можу повірити, що ніхто не вказував на це два роки.
— agf

Я також не впевнений, чому ви не просто використовуєте self.fget(owner)та не знімаєте необхідність тут взагалі використовувати @classmethod? (ось що classmethod робить , перекладайте .__get__(instance, owner)(*args, **kwargs)на function(owner, *args, **kwargs)дзвінки через посередника; властивостям посередник не потрібен).
— Martijn Pieters

У вашій демонстрації не вистачає фактичної трансформації ні в геттер, ні в сеттер, який би чітко продемонстрував, що ваше foo.var = 3завдання насправді не проходить через властивість , а натомість просто замінив об'єкт властивості на fooціле число. Якщо ви додали assert isinstance(foo.__dict__['var'], ClassProperty)дзвінки між вашими твердженнями, ви побачите, що після foo.var = 3цього виконується помилка .
— Martijn Pieters

1
Класи Python не підтримує дескриптор прив'язки по налаштуванню на самому класі , тільки на отримання (так instance.attr, instance.attr = valueі del instance.attrвсе буде пов'язувати дескриптор знайдені на type(instance), але в той час як classobj.attrприв'язки, classobj.attr = valueі del classobj.attrнічого НЕ і замість того, щоб замінити або видалити сам об'єкт дескриптора). Вам потрібен метаклас для підтримки налаштування та видалення (роблячи об'єкт класу екземпляром, а метаклас - типом).
— Martijn Pieters

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

@Nathan Не тільки ... коли ви встановите це, ви перекриєте весь xдоступ 10. Мені подобається такий підхід, оскільки акуратний і простий, але звучить як антипатерн
— Michele d'Amico

Легко фіксується: додайте значення, __set__яке підвищує значення, ValueErrorщоб запобігти переопрацюванню.
— Кіран Джонналагада

30

Чи можливо використовувати функцію властивості () за допомогою функцій, прикрашених класним методом?

Немає.

Однак класний метод - це просто зв'язаний метод (часткова функція) для класу, доступний з екземплярів цього класу.

Оскільки екземпляр є функцією класу, і ви можете отримати клас з екземпляра, ви можете отримати будь-яку бажану поведінку, яка вам потрібна, з властивістю класу за допомогою 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
Чітка, лаконічна, ретельна: це має бути прийнята відповідь.
— pfabri

25

Пітон 3!

Старе питання, багато поглядів, дуже потребує єдиного вірного способу Python 3.

На щастя, з metaclassкваргом це легко :

class FooProperties(type):

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

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

Тоді, >>> Foo.var

"FOO!"


1
це означає, що немає простого виходу з коробки
— mehmet

@mehmet Це не просто? Fooє екземпляром його метакласу, і @propertyйого можна використовувати для своїх методів так само, як це можливо для тих примірників Foo.
— OJFord

2
вам довелося визначити інший клас для класу, тобто подвійну складність, припускаючи, що метаклас не може бути використаний повторно.
— mehmet

Метод class працює як від класу, так і з екземпляра. Ця властивість працює лише з класу. Я не думаю, що саме цього просять.
— Зал Аарона

1
@AaronHall Якщо це важливо, він легко додається Foo.__new__. Хоча в цей момент, можливо, варто застосувати замість gettttribute, або поставити під сумнів, чи існує вигляд, що існує мовна функція, це дійсно той підхід, який ви хочете взагалі взяти.
— 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 називає "дескрипторами". Немає короткого і простого способу пояснити, як працює такий вид метапрограмування, тому я повинен вказати вам на те, як описано дескриптор .

Вам потрібно коли-небудь розуміти подібні речі, лише якщо ви реалізуєте досить просунуті рамки. Як прозора система збереження об'єкта або система RPC, або якась доменна мова.

Однак у коментарі до попередньої відповіді ти кажеш, що ти

потрібно змінити атрибут, який таким чином, який бачать усі екземпляри класу, і в області, з якої ці методи класу викликаються, не має посилань на всі екземпляри класу.

Мені здається, те, що ви насправді хочете, - це модель дизайну Observer .


Мені подобається ідея прикладу коду, але, схоже, це було б трохи незграбно на практиці.
— Марк Родді

Те, що я намагаюся досягти, - це досить просте встановлення вперед і отримання єдиного атрибута, який використовується як прапор для зміни поведінки всіх екземплярів, тому я думаю, що спостерігач буде перекритий тим, що я намагаюся зробити. Якби було декілька атрибутів, про які йдеться, тоді я був би більш схильний.
— Марк Родді

Здається, що просто оприлюднення функцій та виклик їх безпосередньо було рішенням спрощеного списку. Мені було цікаво, чи роблю я щось не так або якщо намагаюся зробити це неможливо. Вибачте за багато коментарів, до речі. Ліміт 300 символів відстій.
— Марк Родді,

Чудова річ у прикладі коду полягає в тому, що ви можете реалізувати всі незграбні біти один раз, а потім успадкувати їх. Просто перемістіть _var у похідний клас. клас D1 (Foo): _var = 21 клас D2 (Foo) _var = "Привіт" D1.var 21 D2.var Привіт
— 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

Це також працює із сетером, визначеним у метакласі.


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

Інструменти стилів EJB, що впливають на Яву (часто це робиться як властивості в Python), є для того, щоб полегшити примітивну самоаналізацію Java, а також передати збирач з компілятором статичної мови. Усі ці геттери і сетери не так корисні в Python.


14
Тому що мені потрібно змінити атрибут, який відображається всіма екземплярами класу, і в області, з якої ці методи класу викликаються, немає посилань на всі екземпляри класу.
— Марк Родді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.