7
class A(object): 
    def __init__(self, a, b, c): 
     #super(A, self).__init__() 
     super(self.__class__, self).__init__() 


class B(A): 
    def __init__(self, b, c): 
     print super(B, self) 
     print super(self.__class__, self) 
     #super(B, self).__init__(1, b, c) 
     super(self.__class__, self).__init__(1, b, c) 

class C(B): 
    def __init__(self, c): 
     #super(C, self).__init__(2, c) 
     super(self.__class__, self).__init__(2, c) 
C(3) 

Nel codice precedente, il commentate __init__ chiamate vengono visualizzate a essere il modo comunemente accettato "intelligente" di fare l'inizio super-classe. Tuttavia, nel caso in cui la gerarchia delle classi possa cambiare, ho utilizzato il modulo non commentato, fino a poco tempo fa.Implicitamente invocando genitore classe di inizializzazione

Sembra che nella chiamata al costruttore di super per B nella gerarchia di cui sopra, che B.__init__ si chiama ancora una volta, è in realtà self.__class__C, non B come avevo sempre pensato.

C'è qualche modo in Python-2.x che posso mantenere la corretta MRO (rispetto a inizializzare tutte le classi genitore nell'ordine corretto) quando si chiama costruttori di super pur non nominando la classe corrente (il B in in super(B, self).__init__(1, b, c))?

risposta

3

Risposta breve: no, non c'è modo per richiamare implicitamente il diritto __init__ con i giusti argomenti della classe genitore destra in Python 2.x.

Per inciso, il codice come mostrato qui non è corretto: se si utilizza super(). __init__, quindi tutte le classi nella gerarchia devono avere la stessa firma nei loro metodi __init__. Altrimenti il ​​codice può smettere di funzionare se si introduce una nuova sottoclasse che utilizza l'ereditarietà multipla.

Vedere http://fuhm.net/super-harmful/ per una descrizione più lunga del problema (con immagini).

+0

cominciavo a chiedermi le firme di init e l'ereditarietà multipla –

1

Forse quello che stai cercando è metaclassi?

class metawrap(type): 
    def __new__(mcs,name, bases, dict): 
     dict['bases'] = bases 
     return type.__new__(mcs,name,bases,dict) 

class A(object): 
    def __init__(self): 
     pass 
    def test(self): 
     print "I am class A" 

class B(A): 
    __metaclass__ = metawrap 
    def __init__(self): 
     pass 
    def test(self): 
     par = super(self.bases[0],self) 
     par.__thisclass__.test(self) 
foo = B() 
foo.test() 

stampe "Io sono di classe A"

Quello che il metaclasse fa è ignorando la creazione iniziale della classe B (non l'oggetto) e fa in modo che il dizionario incorporato per ciascun oggetto B contiene ora una array di basi in cui è possibile trovare tutti i baseclasses per B

+0

Ho appena realizzato che sto sparando ai passeri con i cannoni (proverbio danese), per quello mi scuso, lo farò comunque, a meno che qualcuno non decida di eliminarlo. –

+0

preferirei non ricorrere a metaclassi per qualcosa che dovrebbe essere banale, ma grazie a –

1

Il tuo codice non ha nulla a che fare con l'ordine di risoluzione dei metodi. La risoluzione del metodo avviene nel caso di ereditarietà multipla che non è il caso del tuo esempio. Il tuo codice è semplicemente sbagliato, perché si assume che self.__class__ è in realtà la stessa classe di quella in cui è definito il metodo e questo è sbagliato:

>>> class A(object): 
...  def __init__(self): 
...   print self.__class__ 
... 
>>> 
>>> class B(A): 
...  def __init__(self): 
...   A.__init__(self) 
... 
>>> B() 
<class '__main__.B'> 
<__main__.B object at 0x1bcfed0> 
>>> A() 
<class '__main__.A'> 
<__main__.A object at 0x1bcff90> 
>>> 

in modo che quando si dovrebbe chiamare:

super(B, self).__init__(1, b, c) 

sei anzi chiamando:

# super(self.__class__, self).__init__(1, b, c) 
super(C, self).__init__(1, b, c) 

EDIT: cercare di rispondere meglio alla domanda.

class A(object): 
    def __init__(self, a): 
     for cls in self.__class__.mro(): 
      if cls is not object: 
       cls._init(self, a) 
    def _init(self, a): 
     print 'A._init' 
     self.a = a 

class B(A): 
    def _init(self, a): 
     print 'B._init' 

class C(A): 
    def _init(self, a): 
     print 'C._init' 

class D(B, C): 
    def _init(self, a): 
     print 'D._init' 


d = D(3) 
print d.a 

stampe:

D._init 
B._init 
C._init 
A._init 
3 

(Una versione modificata di template pattern).

Ora i metodi dei genitori sono realmente chiamati implicitamente, ma devo essere d'accordo con Python zen dove l'esplicito è meglio di quello implicito perché il codice è meno leggibile e il guadagno è scarso. Ma attenzione che tutti i metodi _init hanno gli stessi parametri, non è possibile dimenticare completamente i genitori e io non suggerisco di farlo.

Per l'ereditarietà singola, un approccio migliore chiama esplicitamente il metodo genitore, senza richiamare super. In questo modo non è necessario il nome della classe corrente, ma è comunque necessario preoccuparsi di chi è la classe del genitore.

buona legge sono: how-does-pythons-super-do-the-right-thing ed i collegamenti hanno suggerito in questa domanda e nella particolarità Python's Super is nifty, but you can't use it

Se gerarchia è destinata a cambiare è sintomi di cattiva progettazione e ha conseguenze in tutte le parti che utilizzano il codice e non dovrebbe essere incoraggiato.

EDIT 2

Un altro esempio mi viene in mente, ma che utilizza metaclassi. Libreria Urwid uses metaclass per memorizzare un attributo, __super, in classe in modo che sia necessario solo accedere a tale attributo.

Es:

>>> class MetaSuper(type): 
...  """adding .__super""" 
...  def __init__(cls, name, bases, d): 
...   super(MetaSuper, cls).__init__(name, bases, d) 
...   if hasattr(cls, "_%s__super" % name): 
...    raise AttributeError, "Class has same name as one of its super classes" 
...   setattr(cls, "_%s__super" % name, super(cls)) 
... 
>>> class A: 
... __metaclass__ = MetaSuper 
... def __init__(self, a): 
... self.a = a 
... print 'A.__init__' 
... 
>>> class B(A): 
... def __init__(self, a): 
... print 'B.__init__' 
... self.__super.__init__(a) 
... 
>>> b = B(42) 
B.__init__ 
A.__init__ 
>>> b.a 
42 
>>> 
0

A mia conoscenza, ciò che segue non è comunemente fatto. Ma sembra funzionare.

I metodi in una determinata definizione di classe manipolano sempre gli attributi double-underscore per includere il nome della classe in cui sono definiti. Quindi, se si memorizza un riferimento alla classe in un modulo con storto nome in cui le istanze possono vederlo , è possibile utilizzarlo nella chiamata allo super.

Un esempio stashing i riferimenti per l'oggetto in sé, implementando __new__ sul baseclass:

def mangle(cls, name): 
    if not name.startswith('__'): 
     raise ValueError('name must start with double underscore') 
    return '_%s%s' % (cls.__name__, name) 

class ClassStasher(object): 
    def __new__(cls, *args, **kwargs): 
     obj = object.__new__(cls) 
     for c in cls.mro(): 
      setattr(obj, mangle(c, '__class'), c) 
     return obj 

class A(ClassStasher): 
    def __init__(self): 
     print 'init in A', self.__class 
     super(self.__class, self).__init__() 

class B(A): 
    def __init__(self): 
     print 'init in B', self.__class 
     super(self.__class, self).__init__() 

class C(A): 
    def __init__(self): 
     print 'init in C', self.__class 
     super(self.__class, self).__init__() 

class D(B, C): 
    def __init__(self): 
     print 'init in D', self.__class 
     super(self.__class, self).__init__() 


d = D()  
print d 

E, facendo una cosa simile, ma utilizzando un meta-class e stashing le __class riferimenti sugli oggetti di classe stessi:

l'esecuzione di questo tutti insieme, come un file sorgente:

% python /tmp/junk.py 
init in D <class '__main__.D'> 
init in B <class '__main__.B'> 
init in C <class '__main__.C'> 
init in A <class '__main__.A'> 
<__main__.D object at 0x1004a4a50> 
init in D_meta <class '__main__.D_meta'> 
init in B_meta <class '__main__.B_meta'> 
init in C_meta <class '__main__.C_meta'> 
init in A_meta <class '__main__.A_meta'> 
<__main__.D_meta object at 0x1004a4bd0>