Python: inconsistance dans la façon dont vous définissez la fonction __setattr__?

StackOverflow https://stackoverflow.com/questions/6305267

  •  26-10-2019
  •  | 
  •  

Question

Considérez ce code:

class Foo1(dict):
    def __getattr__(self, key): return self[key]
    def __setattr__(self, key, value): self[key] = value

class Foo2(dict):
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

o1 = Foo1()
o1.x = 42
print(o1, o1.x)

o2 = Foo2()
o2.x = 42
print(o2, o2.x)

J'attendre la même sortie. Cependant, avec CPython 2.5, 2.6 (de la même 3.2) je reçois:

({'x': 42}, 42)
({}, 42)

Avec PyPy 1.5.0, je reçois le résultat attendu:

({'x': 42}, 42)
({'x': 42}, 42)

Quelle est la sortie « droit »? (Ou quelle devrait être la sortie en fonction de la documentation Python?)


est le rapport de bogue pour CPython.

Était-ce utile?

La solution

Je soupçonne qu'il a à faire avec une optimisation de la recherche. À partir du code source:

 /* speed hack: we could use lookup_maybe, but that would resolve the
       method fully for each attribute lookup for classes with
       __getattr__, even when the attribute is present. So we use
       _PyType_Lookup and create the method only when needed, with
       call_attribute. */
    getattr = _PyType_Lookup(tp, getattr_str);
    if (getattr == NULL) {
        /* No __getattr__ hook: use a simpler dispatcher */
        tp->tp_getattro = slot_tp_getattro;
        return slot_tp_getattro(self, name);
    }

Le chemin rapide ne ne regarde pas sur le dictionnaire de classe.

Par conséquent, la meilleure façon d'obtenir la fonctionnalité souhaitée est de placer une méthode de remplacement dans la classe.

class AttrDict(dict):
    """A dictionary with attribute-style access. It maps attribute access to
    the real dictionary.  """
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))

    def __setitem__(self, key, value):
        return super(AttrDict, self).__setitem__(key, value)

    def __getitem__(self, name):
        return super(AttrDict, self).__getitem__(name)

    def __delitem__(self, name):
        return super(AttrDict, self).__delitem__(name)

    __getattr__ = __getitem__
    __setattr__ = __setitem__

     def copy(self):
        return AttrDict(self)

Ce que je trouve fonctionne comme prévu.

Autres conseils

Il est un connu (et peut-être pas si bien) différence documentée. PyPy ne fait pas de distinction entre les fonctions et les fonctions builtin. Dans les fonctions CPython se binded comme méthodes non liées lorsqu'elles sont stockées sur la classe (l'ont __get__), tandis que les fonctions builtin ne pas (ils sont différents).

Dans le cadre PyPy Cependant, les fonctions sont builtin exactement les mêmes que les fonctions de python, de sorte que l'interprète ne peut pas leur dire d'eux à part et traite comme des fonctions de niveau python. Je pense que cela a été défini comme les détails de mise en œuvre, bien qu'il y ait eu des discussions sur python-dev sur la suppression de cette différence particulière.

Cheers,
Fijal

Notez les points suivants:

>>> dict.__getitem__ # it's a 'method'
<method '__getitem__' of 'dict' objects> 
>>> dict.__setitem__ # it's a 'slot wrapper'
<slot wrapper '__setitem__' of 'dict' objects> 

>>> id(dict.__dict__['__getitem__']) == id(dict.__getitem__) # no bounding here
True
>>> id(dict.__dict__['__setitem__']) == id(dict.__setitem__) # or here either
True

>>> d = {}
>>> dict.__setitem__(d, 1, 2) # can be called directly (since not bound)
>>> dict.__getitem__(d, 1)    # same with this
2

Maintenant, nous pouvons simplement les envelopper (et __getattr__ travaillerons même sans cela):

class Foo1(dict):
    def __getattr__(self, key): return self[key]
    def __setattr__(self, key, value): self[key] = value

class Foo2(dict):
    """
    It seems, 'slot wrappers' are not bound when present in the __dict__ 
    of a class and retrieved from it via instance (or class either).
    But 'methods' are, hence simple assignment works with __setitem__ 
    in your original example.
    """
    __setattr__ = lambda *args: dict.__setitem__(*args)
    __getattr__ = lambda *args: dict.__getitem__(*args) # for uniformity, or 
    #__getattr__ = dict.__getitem__                     # this way, i.e. directly


o1 = Foo1()
o1.x = 42
print(o1, o1.x)

o2 = Foo2()
o2.x = 42
print(o2, o2.x)

Ce qui donne:

>>>
({'x': 42}, 42)
({'x': 42}, 42)

Le mécanisme derrière le comportement en question est (probablement, je ne suis pas un expert) en dehors du sous-ensemble « propre » de Python (comme documenté dans les livres complets comme « Learning Python » ou « Python en un mot » et un peu vaguement spécifié à python.org) et se rapporte à la partie de la langue qui est décrite « en l'état » par la mise en œuvre (et est soumise à (plutôt) des changements fréquents).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top