web-dev-qa-db-fra.com

Puis-je obtenir une référence à une propriété Python?

Si j'ai ceci:

class foo(object):
    @property
    def bar(self):
        return 0

f = foo()

Comment obtenir une référence à f.bar sans invoquer réellement la méthode, si cela est même possible?

Édité pour ajouter: Ce que je veux faire, c’est écrire une fonction qui itère sur les membres de f et fait quelque chose avec eux (ce qui n’a pas d’importance). Les propriétés me font trébucher parce que les nommer simplement dans getattr () appelle leur méthode __get __ ().

43
kindall

get_dict_attr (ci-dessous) recherche attr dans le __dict__ d'un objet donné et renvoie la valeur associée, le cas échéant. Si attr n'est pas une clé dans ce __dict__, la recherche de __dict__s de l'objet MRO est effectuée. Si la clé n'est pas trouvée, une AttributeError est générée.

def get_dict_attr(obj, attr):
    for obj in [obj] + obj.__class__.mro():
        if attr in obj.__dict__:
            return obj.__dict__[attr]
    raise AttributeError

Par exemple,

class Foo(object):
    x=1
    def bar(self):
        pass
    @property
    def baz(self):
        return 0

foo=Foo()
print(get_dict_attr(foo,'x'))
# 1
print(get_dict_attr(foo,'bar'))
# <unbound method Foo.bar>
print(get_dict_attr(foo,'baz'))
# <property object at 0xb77c0dc4>
print(get_dict_attr(foo,'y'))
# AttributeError

Notez que ceci est très différent des règles normales de recherche d'attribut. D'une part, les descripteurs de données dans obj.__class__.__dict__ (les descripteurs avec les deux méthodes __get__ et __set__) ont normalement priorité sur les valeurs dans obj.__dict__. Dans get_dict_attr, obj.__dict__ a la priorité.

get_dict_attr n'essaie pas d'appeler __getattr__

Enfin, get_dict_attr fonctionnera uniquement avec les objets obj qui sont des instances de classes de style nouveau. 

Néanmoins, j'espère que cela vous aidera.


class Foo(object):
    @property
    def bar(self):
        return 0

f = Foo()

Ceci fait référence à la propriété bar:

print(Foo.bar)
# <property object at 0xb76d1d9c>

bar est une clé dans Foo.__dict__:

print(Foo.__dict__['bar'])
# <property object at 0xb775dbbc>

Toutes les propriétés sont des descripteurs, ce qui implique une méthode __get__:

print(Foo.bar.__get__)
# <method-wrapper '__get__' of property object at 0xb76d7d74>

Vous pouvez appeler la méthode en passant l'objet f et la classe f en tant qu'arguments:

print(Foo.bar.__get__(f,Foo))
# 0

J'aime le schéma suivant. Les lignes verticales indiquent la relation entre un objet et la classe de l'objet.

Quand vous avez cette situation:

   Foo                                B
   | Foo.__dict__={'bar':b}           | B.__dict__={'__get__':...}
   |                      \           |      
   f                       `--------> b

f.bar provoque l'appel de b.__get__(f,Foo).

Ceci est expliqué en détail ici .

35
unutbu

J'ai rencontré une situation similaire à celle-ci et aucune des autres réponses ne m'a aidé. Voici donc une autre approche.

Ma situation était légèrement différente, car au lieu de simplement avoir un @property, je mélangeais un décorateur personnalisé qui ajoute des attributs à la méthode encapsulée. Donc, par exemple, normalement, j'aurais quelque chose comme ceci:

class Foo:
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

Mon @my_decorator me donnerait alors accès à self.bar.my_attribute qui contenait certaines données auxquelles je devais parfois avoir accès. Cela fonctionne très bien pour les fonctions et les méthodes, mais j’ai eu du mal à le mélanger avec @property car la propriété masque la référence à la méthode (self.bar.my_attribute échoue car self.bar renvoie la valeur de retour de la méthode, qui ne possède pas le my_attribute qui Je voudrais).

J'utiliserais @property comme décorateur extérieur comme ceci:

class Foo:
    @property
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

Et ensuite, la solution consiste à accéder à my_attribute en tant que Foo.bar.fget.my_attribute (le détail important ici est que l'accès à la propriété depuis la classe renvoie l'objet property, alors qu'accéder à la propriété depuis une instance appelle simplement la méthode et renvoie la valeur, sans accès à la référence à la méthode originale).

TL; DR: Si vous avez besoin d'une référence à la méthode fournissant votre @property, vous devez utiliser YourClassName.your_property.fget.

1
robru

Dans ton cas,

class foo(object):
    @property
    def bar(self):
        return 0
f = foo()

Vous pouvez accéder à la propriété à travers le dictionnaire de la classe:

f.__class__.__dict__['bar']
=> <property at 0x4920c28>

Comme vous pouvez le constater, il s’agit d’une instance property.

isinstance(f.__class__.__dict__['bar'], property)
=> True

Vous pouvez accéder à sa méthode getter (qui return 0) à fget comme ceci:

f.__class__.__dict__['bar'].fget(f)
=> 0

Notez que vous devez fournir l'instance f à fget() car il s'agit d'une méthode d'instance (non statique) et vous y accédez via le __class__.

1
florisla
class A:
    prop = property(lambda self: 'get', lambda self, x: print('set', x))

setter = A.prop.fset
getter = A.prop.fget


my_a = A()

setter(my_a, 5)
print(getter(my_a))
0
ADR

Vous devez savoir que les descripteurs de données (propriétés) ne fonctionnent que s’ils sont appliqués à des classes (nouveau style) (voir http://docs.python.org/reference/datamodel.html#implementing-descriptors ). Les copier dans un objet ne créera pas la propriété sur cet objet. Vous devez les copier dans une classe (nouveau style) pour prendre effet.

0
Nathan Davis