2016-01-22 1 views
2

Eh bien, j'ai une classe qui se composent de (partie pertinente):récursion infinie pour __getattr __() - mais pourquoi est-il même appelé une fois?

class satellite(object): 
    def __init__(self, orbit, payload=None, structural_mass=0, structural_price=0): 
     self.__all_parts = [] 
     self.orbit = orbit 
     self.payload = payload 
     self.structural_mass = structural_mass 
     self.structural_price = structural_price 

    def __getattr__(self, item): 
     found = False 
     v = 0 
     for i in self.__all_parts: 
      t = getattr(i, item, None) 
      if t is not None: 
       v += t 
       found = True 
     if found: 
      return v 
     else: 
      raise AttributeError(item) 

En fait ce que je veux faire est de propager tous (somme) les attributs des « parties » dans le satellite. C'est-à-dire que si j'ai 10 parties qui ont une masse, la masse du satellite est la somme de celles-ci. - Si j'ajoute ensuite une autre pièce qui a du stockage d'énergie - je peux immédiatement regarder ça aussi. - Si aucune partie n'a l'attribut, l'attribut est considéré comme "mauvais"/"inexistant" et il soulève l'erreur normale.

Maintenant cela fonctionne, sauf quand je fais:

s = satellite(None) #most simplistic one 
ss = copy.copy(s) 

L'ensemble des bugs chose, donnant une erreur de récursion infinie dans __getattr__().

Maintenant inspection (débogueur de PyCharm) me montre qu'il garde itérer le getattr avec comme argument:

item = _satellite__all_parts Et il commence sa prochaine itération à la ligne for i in self.__all_parts:

Maintenant, je suis surpris par ceci: pourquoi cette ligne va-t-elle même à __getattr_() - autant que je sache, __getattr__ est seulement appelé pour les attributs qui ne sont pas existants? - Mais self.__all_parts est évidemment déclaré dans l'événement __init__ de l'objet, alors pourquoi __getattr__ est même activé? Et de plus: pourquoi ne comprend-il plus l'objet?

Et bien sûr: comment puis-je faire ce travail?

EDIT: juste pour la clarté - cela se produit UNIQUEMENT EN RAISON DE COPIE, et il est (était grâce à martijn) spécifique au comportement de copie. La question liée ne gère pas le cas de copie.

+1

Je ne sais pas mais j'attendre qu'il doit faire avec le nom mutiler des éléments à double underscore-préfixés. Cela fonctionne-t-il si vous utilisez '_all_parts' à la place? –

+0

Je ne suis pas sûr que l'implémentation '__getattr__' soit une bonne idée. Que faire si l'attribut n'ajoute pas bien? Je ne sais pas si c'est le cas dans votre scénario, mais il pourrait y avoir des attributs qui (a) ne peuvent pas être ajoutés, ou (b) où l'addition n'a pas de sens (par exemple, la vitesse de la sattelite n'est pas la somme des vitesses de ses parties). –

+0

@DanielRoseman: Eh bien cela corrige pour l'appeler directement - mais toujours en utilisant "' copy.copy (t) '" donne la même erreur de récursivité. – paul23

Répondre

4

copy.copy() tente d'accéder à l'attribut __setstate__ sur un vide exemple, pas d'attributs définis encore, parce que __init__ n'a pas été appelé (et ne sera pas, copy.copy() est responsable de définir les nouveaux attributs sur elle).

Le __setstate__ methodcopy.copy() recherche est optionnel, et votre classe n'a en effet pas un tel attribut. Parce qu'il est manquant, __getattr__ est appelé pour cela, et parce qu'il n'y a pas __all_parts attribut encore soit, la ligne for i in self.__all_parts: finit par appeler à nouveau __getattr__. Et encore et encore et encore.

L'astuce consiste à couper cette boucle tôt en la testant. La meilleure façon de le faire est de cas particulier tous les attributs qui commencent par un trait de soulignement:

if item.startswith('_'): 
    # bail out early 
    raise AttributeError(item)