2017-08-30 1 views
1

J'essaye de construire un système où une classe de base est utilisée pour tous les autres objets. Chaque objet de base a un dictionnaire _fields en interne où les implémentations de la classe de base peuvent stocker leurs informations.La combinaison de __setattr__ et __getattr__ provoque une boucle infinie

implémentation de la classe de base est assez simple:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

Une implémentation de la classe peut définir le champ dans l'appel __init__ à son super(). Ce que je voudrais ajouter, c'est que les champs sont accessibles en tant que propriétés sans avoir à ajouter le décorateur @property à un tas de fonctions. J'ai outrepassée le __getattr__ à cet effet dans la classe de base comme ceci:

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

maintenant une implémentation de cette classe peut fonctionner comme ceci:

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

En ce qui créant une mise en œuvre de cette classe donne vous les options pour le faire:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

qui retourne

foo 

bar 

Je peux même passer outre ce via @property décorateurs, changer la classe comme ceci:

class A_impl(A): 
    def __init__(self): 
     super(A_impl, self).__init__(
        fields=dict(
        val_1="foo", 
        val_2="", 
        val_3="bar", 
       ) 
      ) 

    @property 
    def val_1(self): 
     return self._fields.get('val_1') + "_getter" 

Ce qui me permet de manipuler les données pour le retour. Le seul problème est que si je veux pouvoir régler l'une de ces variables de champ, je dois implémenter les fonctions du seteur des descripteur qui m'oblige également à faire le descripteur de propriété qui crée beaucoup de travail en double (ie je dois définir descripteurs pour tous mes champs qui est ce que je veux éviter ici.

I mis en œuvre la fonction __setattr__ pour la classe de base pour résoudre le problème où si je mets en œuvre manuellement une fonction setter descripteur il doit être choisi sur la valeur par défaut qui est self._field[name] = value La classe de base ressemble maintenant à ceci (similaire au __getattr__):

class A(object): 
    def __init__(self, fields=dict()): 
     self._fields = fields 

    def __getattr__(self, name): 
     if hasattr(self, name): 
      return object.__getattribute__(self, name) 
     elif name in self._fields: 
      return self._fields.get(name) 
     else: 
      raise AttributeError 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      object.__setattr__(self, name, value) 
     elif name in self._fields: 
      self._fields[name] = value 
     else: 
      raise AttributeError 

Maintenant, si je lance le même test de code à nouveau:

test = A_imp() 
print test.val_1 
print test.val_2 
print test.val_3 

Il se instantanément bloqué dans une boucle infinie, il commence au __setattr__ mais saute dans le __getattr__ juste après et maintient en boucle que. J'ai lu beaucoup de questions sur stackoverflow pour cela et je ne pouvais pas le comprendre, c'est pourquoi je construis ce cas de test pour le comprendre. J'espère que quelqu'un est capable de clarifier cela pour moi et m'aider à résoudre le problème.

+0

Ce 'hasattr' est inutile. – o11c

Répondre

3

Il n'y a aucun moyen de vérifier si un objet a un attribut autre que d'essayer réellement de le récupérer. Ainsi,

hasattr(self, 'val_1') 

essaie réellement d'accéder à self.val_1.Si self.val_1 n'est pas trouvé par les moyens normaux, il revient sur __getattr__, qui appelle hasattr à nouveau dans une récursion infinie.

hasattr prises réellement une exception, y compris RuntimeError: maximum recursion depth exceeded et renvoie False, donc la manière exacte ce manifeste dépend précisément combien __getattr__ appels sont imbriqués. Certains appels __getattr__ touchent le cas object.__getattribute__ et déclenchent un AttributeError; certains ont frappé l'affaire elif name in self._fields: return self._fields.get(name). Si self._fields existe, cela renvoie une valeur, mais avec votre __setattr__, parfois self._fields n'existe pas!


Lorsque votre __setattr__ tente de gérer l'affectation self._fields dans __init__, il appelle hasattr(self, '_fields'), qui appelle __getattr__. Maintenant, certains appels __getattr__ font deux appels récursifs __getattr__, un en hasattr et un en elif name in self._fields. Puisque hasattr intercepte des exceptions, cela provoque des appels récursifs exponentiels dans la profondeur de récursivité au lieu de rapidement sembler fonctionner ou déclencher une exception.


Ne pas utiliser hasattr dans __getattr__ ou __getattribute__. En général, faites très attention à toutes les tentatives d'accès aux attributs dans les méthodes qui gèrent l'accès aux attributs.

+0

Alors, comment je vais faire à ce sujet? Je veux la précédence sur les descripteurs auto-implémentés au lieu de toujours servir les entrées self._field. – Yonathan

+0

@Yathan: '__getattr__' n'est appelé que si la recherche" normale "(via' __getattribute__') ne trouve pas de valeur, donc vous n'avez pas besoin de vérifier à nouveau. Pour ce qui est de "faire très attention" à l'accès aux attributs en général, cela implique généralement de déléguer à 'super() .__ getattribute__' ou' super() .__ setattr__' pour toute récupération ou assignation d'attribut qui doit contourner votre logique spéciale. – user2357112

+0

Mais en appliquant cela comme si "nom dans self._fields" et bien comme "super() .__ (set | set) attr __ (self, nom [, valeur])" dans l'ensemble et obtenir des fonctions attr pour la classe de base provoque les mêmes problèmes de récurrence. Je ne vois pas comment je pourrais résoudre le problème du "comportement standard en premier et vérifier autrement self._fields". En ce moment, la précendence est prise pour les fonctions '__ (get | set) attr__' au lieu des descripteurs implémentés. Désolé, je manque juste comment je pourrais résoudre ce problème. – Yonathan