web-dev-qa-db-fra.com

Utilisation de property () sur les méthodes de classe

J'ai une classe avec deux méthodes de classe (en utilisant la fonction classmethod ()) pour obtenir et définir ce qui est essentiellement une variable statique. J'ai essayé d'utiliser la fonction property () avec ceux-ci, mais il en résulte une erreur. J'ai pu reproduire l'erreur avec ce qui suit dans l'interprète:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

Je peux démontrer les méthodes de classe, mais elles ne fonctionnent pas en tant que propriétés:

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

Est-il possible d'utiliser la fonction property () avec des fonctions classmethod?

150
Mark Roddy

Une propriété est créée sur une classe mais affecte une instance. Donc, si vous voulez une propriété classmethod, créez-la sur la métaclasse.

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

Mais puisque vous utilisez quand même une métaclasse, elle se lira mieux si vous ne faites que déplacer les méthodes de classe.

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

ou, en utilisant Python) la syntaxe de metaclass=..., et la métaclasse définie en dehors du corps de la classe foo, et la métaclasse responsable de la définition de la valeur initiale de _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
75
A. Coady

En lisant les notes version Python 2.2 , je trouve ce qui suit.

La méthode get [d'une propriété] ne sera pas appelée lors de l'accès à la propriété en tant qu'attribut de classe (C.x) plutôt qu'en tant qu'attribut d'instance (C (). X). Si vous souhaitez remplacer l'opération __get__ pour les propriétés utilisées en tant qu'attribut de classe, vous pouvez sous-classer la propriété - il s'agit d'un type de style nouveau - pour étendre sa méthode __get__ ou vous pouvez définir un type de descripteur à partir de rien en créant un nouveau classe de style qui définit les méthodes __get__, __set__ et __delete__.

REMARQUE: La méthode ci-dessous ne fonctionne pas pour les setters, mais seulement pour les accesseurs.

Par conséquent, j'estime que la solution préconisée consiste à créer un ClassProperty en tant que sous-classe d'un bien.

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

Cependant, les setters ne travaillent pas réellement:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var est inchangé, vous avez simplement écrasé la propriété avec une nouvelle valeur.

Vous pouvez également utiliser ClassProperty en tant que décorateur:

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
70
Jason R. Coombs

J'espère que ce mort-simple lecture seule @classproperty décorateur aiderait quelqu'un à la recherche de propriétés de classe.

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
47
Denis Ryzhkov

Est-il possible d'utiliser la fonction property () avec des fonctions classmethod?

Non.

Cependant, une méthode de classe est simplement une méthode liée (une fonction partielle) sur une classe accessible à partir d'instances de cette classe.

Etant donné que l'instance est une fonction de la classe et que vous pouvez dériver la classe de l'instance, vous pouvez obtenir le comportement souhaité par une propriété de classe avec 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

Ce code peut être utilisé pour tester - il devrait passer sans générer d'erreurs:

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

Et notez que nous n’avons pas du tout besoin de métaclasses - et vous n’avez de toute façon pas directement accès à une métaclasse via les instances de ses classes.

écrire un @classproperty décorateur

Vous pouvez créer un décorateur classproperty en quelques lignes de code en sous-classant property (implémenté en C, mais vous pouvez voir l’équivalent Python - ici ):

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

Ensuite, traitez le décorateur comme s'il s'agissait d'une méthode de classe associée à la propriété:

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

Et ce code devrait fonctionner sans erreur:

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 __== '__main__':
    main()

Mais je ne sais pas à quel point cela serait bien conseillé. Une ancienne liste de diffusion article suggère que cela ne devrait pas fonctionner.

Obtenir la propriété pour travailler sur la classe:

L'inconvénient de ce qui précède est que la "propriété de classe" n'est pas accessible depuis la classe, car elle écraserait simplement le descripteur de données de la classe __dict__.

Cependant, nous pouvons remplacer cette propriété par une propriété définie dans la métaclasse __dict__. Par exemple:

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

Et puis une instance de classe de la métaclasse pourrait avoir une propriété qui accède à la propriété de la classe en utilisant le principe déjà démontré dans les sections précédentes:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

Et maintenant nous voyons à la fois l'instance

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

et la classe

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

avoir accès à la propriété de classe.

23
Aaron Hall

Python 3!

Vieille question, beaucoup de points de vue, ayant cruellement besoin d’une seule vérité Python 3 voies.

Heureusement, c'est facile avec le metaclass kwarg:

class FooProperties(type):

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

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

Ensuite, >>> Foo.var

'FOO!'

19
OJFord

Il n’existe aucun moyen raisonnable de faire fonctionner ce système "propriété de classe" en Python.

Voici un moyen déraisonnable de le faire fonctionner. Vous pouvez certainement le rendre plus transparent avec des quantités croissantes de magie de métaclasse.

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

Le nœud du problème est que les propriétés sont ce que Python appelle des "descripteurs". Il n’existe aucun moyen simple et rapide d’expliquer comment fonctionne ce type de métaprogrammation. Je dois donc vous indiquer - descripteur howto .

Vous n'avez besoin de comprendre ce genre de choses que si vous implémentez un framework assez avancé. Comme une persistance d'objet transparent ou un système RPC, ou une sorte de langage spécifique à un domaine.

Cependant, dans un commentaire à une réponse précédente, vous dites que vous

il est nécessaire de modifier un attribut qui, de manière visible par toutes les instances d'une classe, et dans la portée à partir de laquelle ces méthodes de classe sont appelées ne comporte pas de références à toutes les instances de la classe.

Il me semble que vous voulez vraiment un modèle Observer .

16
ddaa

Le fait de le définir uniquement sur la méta-classe n'aide pas si vous souhaitez accéder à la propriété de la classe via un objet instancié. Dans ce cas, vous devez également installer une propriété normale sur l'objet (transmise à la propriété de la classe). Je pense que ce qui suit est un peu plus clair:

#!/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
6
Nils Philippsen

Une demi-solution, __set__ sur la classe ne fonctionne toujours pas. La solution est une classe de propriétés personnalisée implémentant à la fois une propriété et un procédé statique.

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
Florian Bösch

Parce que j'ai besoin de modifier un attribut qui soit vu de manière visible par toutes les instances d'une classe et dans la portée à partir de laquelle ces méthodes de classe sont appelées, il ne fait pas référence à toutes les instances de la classe.

Avez-vous accès à au moins une instance de la classe? Je peux penser à un moyen de le faire alors:

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
3
John Millikin

Essayez ceci, le travail est fait sans avoir à changer/ajouter beaucoup de code existant.

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

La fonction property nécessite deux arguments callable. donnez-leur des wrappers lambda (dont il passe l'instance comme premier argument) et tout va bien.

2
Sufian

Voici une solution qui devrait fonctionner à la fois pour l'accès via la classe et pour l'accès via une instance utilisant une métaclasse.

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

Cela fonctionne aussi avec un setter défini dans la métaclasse.

2
papercrane

Après avoir recherché différents endroits, j'ai trouvé une méthode pour définir une propriété de classe valide avec Python 2 et 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)

J'espère que cela peut aider quelqu'un :)

1
undisclosed