2010-08-31 3 views
2

J'ai vu le code qui ressemble à ceci:objets Constructing dans __init__

class MyClass: 
    def __init__(self, someargs): 
     myObj = OtherClass() 
     myDict = {} 
     ...code to setup myObj, myDict... 
     self.myObj = myObj 
     self.myDict = myDict 

Ma première pensée quand j'ai vu était la suivante: Pourquoi ne pas utiliser self.myObj et self.myDict au début? Il semble inefficace de construire des objets locaux, puis de les assigner aux membres. Le code pour construire les objets peut-être jeter des exceptions, peut-être qu'ils l'ont fait pour ne pas laisser un objet à moitié construit? Est-ce que vous faites cela, ou simplement construisez les membres directement?

+0

Je pense que vous avez fourni la meilleure raison.Si j'ai une fonction qui effectue des effets secondaires, même si les effets secondaires ne dépassent pas self, je retarderai ces effets jusqu'à ce que la fonction soit terminée avec succès. C'est une bonne habitude dans n'importe quelle fonction où il y a la possibilité d'une exception qui arrête l'exécution à mi-chemin. –

Répondre

8

Il est plus rapide et plus lisible pour construire l'objet, puis l'attacher à self.

class Test1(object): 
    def __init__(self): 
     d = {} 
     d['a'] = 1 
     d['b'] = 2 
     d['c'] = 3 
     self.d = d 

class Test2(object): 
    def __init__(self): 
     self.d = {} 
     self.d['a'] = 1 
     self.d['b'] = 2 
     self.d['c'] = 3 

import dis 
print "Test1.__init__" 
dis.dis(Test1.__init__) 

print "Test2.__init__" 
dis.dis(Test2.__init__) 

disassemles à:

Test1.__init__ 
    4   0 BUILD_MAP    0 
       3 STORE_FAST    1 (d) 

    5   6 LOAD_CONST    1 (1) 
       9 LOAD_FAST    1 (d) 
      12 LOAD_CONST    2 ('a') 
      15 STORE_SUBSCR   

    6   16 LOAD_CONST    3 (2) 
      19 LOAD_FAST    1 (d) 
      22 LOAD_CONST    4 ('b') 
      25 STORE_SUBSCR   

    7   26 LOAD_CONST    5 (3) 
      29 LOAD_FAST    1 (d) 
      32 LOAD_CONST    6 ('c') 
      35 STORE_SUBSCR   

    8   36 LOAD_FAST    1 (d) 
      39 LOAD_FAST    0 (self) 
      42 STORE_ATTR    0 (d) 
      45 LOAD_CONST    0 (None) 
      48 RETURN_VALUE   
Test2.__init__ 
12   0 BUILD_MAP    0 
       3 LOAD_FAST    0 (self) 
       6 STORE_ATTR    0 (d) 

13   9 LOAD_CONST    1 (1) 
      12 LOAD_FAST    0 (self) 
      15 LOAD_ATTR    0 (d) 
      18 LOAD_CONST    2 ('a') 
      21 STORE_SUBSCR   

14   22 LOAD_CONST    3 (2) 
      25 LOAD_FAST    0 (self) 
      28 LOAD_ATTR    0 (d) 
      31 LOAD_CONST    4 ('b') 
      34 STORE_SUBSCR   

15   35 LOAD_CONST    5 (3) 
      38 LOAD_FAST    0 (self) 
      41 LOAD_ATTR    0 (d) 
      44 LOAD_CONST    6 ('c') 
      47 STORE_SUBSCR   
      48 LOAD_CONST    0 (None) 
      51 RETURN_VALUE 

Vous pouvez voir que STORE_ATTR seulement une fois qu'il est appelé à faire la première manière à la fin. Dans l'autre sens, STORE_ATTR est toujours appelé au début, mais LOAD_ATTR est appelé pour chaque accès au dictionnaire. Plus il y a d'affectations, plus le coût est élevé. Toutes les autres instructions sont les mêmes. C'est toujours un coût ridiculement faible.

Cette astuce peut être exploitée pour faire tourner des boucles avec de nombreuses itérations plus rapidement. Il est pas rare de voir des choses comme

foo = self.foo 
factorial = math.factorial 
for x in really_big_iterator: 
    foo(factorial(x)) 

une autre astuce consiste à passer des fonctions globales comme arguments par défaut à une fonction qui a une boucle comme ça ou est appelé un tas pour sauver quelques recherches d'attributs: il est dans la portée locale qui est le premier regardé po

def fast(iterators, sum=sum): 
    for i in iterator: 
     yield sum(i) 

maintenant sum est juste dans le champ d'application local.

1

Je ne suis pas sûr de bien comprendre votre question, mais gardez à l'esprit qu'attribuer self.myObj = myObj affectera simplement une référence de sorte qu'il ne risque pas beaucoup de ralentir les choses. Ma conjecture est que l'idiome est utilisé pour sauver le programmeur de taper le mot self un tas de fois.

3

Si vous êtes inquiet au sujet de l'implication de la performance de la copie d'une référence d'objet, vous utilisez probablement la mauvaise langue :)

faire ce qui est plus lisible pour vous. Dans ce cas, cela dépend de la durée de la méthode init.

+0

Je suis seulement inquiet dans le sens d'éviter la pessimisation prématurée :) – Colin

+0

"ce que vous pouvez lire, vous pouvez optimiser plus tard" - moi –

0

Il n'y a vraiment aucune différence significative de toute façon. Une raison de créer une variable locale est si vous allez utiliser la variable dans __init__ beaucoup, vous n'avez pas besoin de répéter autant le self.. par exemple.

def __init__(self, someargs): 
    self.myObj = OtherClass() 
    self.myDict = {} 
    self.myDict[1] = self.myObj 
    self.myDict[2] = self.myObj 
    self.myDict[3] = self.myObj 
    self.myDict[4] = self.myObj 
    self.myObj = myObj 
    self.myDict = myDict 

v.s. Je ne vais pas m'inquiéter des performances à moins que vous n'ayez une bonne raison de le faire.

0

L'attribution d'une référence ne prend pas trop de temps, mais taper «self» chaque fois vous prend du temps.

Questions connexes