Quando chiamiamo super()
per risolvere la versione di un genitore di un metodo di classe, metodo di istanza o metodo statico, vogliamo passare la classe corrente il cui ambito ci troviamo come primo argomento, per indicare a quale ambito del genitore stiamo cercando di risolvere e come un secondo argomento l'oggetto di interesse per indicare a quale oggetto stiamo cercando di applicare tale ambito.
Si consideri una gerarchia di classi A
, B
e C
dove ogni classe è il genitore di quello seguente, e a
, b
e c
rispettive istanze di ciascuno.
super(B, b)
# resolves to the scope of B's parent i.e. A
# and applies that scope to b, as if b was an instance of A
super(C, c)
# resolves to the scope of C's parent i.e. B
# and applies that scope to c
super(B, c)
# resolves to the scope of B's parent i.e. A
# and applies that scope to c
Usando super
con un metodo statico
ad es. usando super()
dall'interno del __new__()
metodo
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return super(A, cls).__new__(cls, *a, **kw)
Spiegazione:
1- anche se è usuale per __new__()
prendere come primo parametro un riferimento alla classe chiamata, è non è implementato in Python come classmethod, ma piuttosto uno staticmethod. Cioè, un riferimento a una classe deve essere passato esplicitamente come primo argomento quando si chiama __new__()
direttamente:
# if you defined this
class A(object):
def __new__(cls):
pass
# calling this would raise a TypeError due to the missing argument
A.__new__()
# whereas this would be fine
A.__new__(A)
2- quando si chiama super()
per accedere alla classe genitore, si passa la classe figlio A
come primo argomento, quindi si passa un riferimento all'oggetto di interesse, in questo caso è il riferimento di classe che è stato passato quando è A.__new__(cls)
stato chiamato. Nella maggior parte dei casi capita anche di essere un riferimento alla classe del bambino. In alcune situazioni potrebbe non esserlo, ad esempio nel caso di eredità di più generazioni.
super(A, cls)
3- poiché come regola generale __new__()
è un metodo statico, super(A, cls).__new__
restituirà anche un metodo statico e in questo caso deve essere fornito esplicitamente tutti gli argomenti, incluso il riferimento all'oggetto di più interessante cls
.
super(A, cls).__new__(cls, *a, **kw)
4- fare la stessa cosa senza super
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return object.__new__(cls, *a, **kw)
Usando super
con un metodo di istanza
es. usando super()
dall'interno__init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Spiegazione:
1- __init__
è un metodo di istanza, nel senso che prende come primo argomento un riferimento a un'istanza. Quando viene chiamato direttamente dall'istanza, il riferimento viene passato in modo implicito, ovvero non è necessario specificarlo:
# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...
# you create an instance
a = A()
# you call `__init__()` from that instance and it works
a.__init__()
# you can also call `__init__()` with the class and explicitly pass the instance
A.__init__(a)
2- quando chiamiamo super()
all'interno __init__()
, passiamo alla classe figlio come primo argomento e l'oggetto di interesse come secondo argomento, che in generale è un riferimento a un'istanza della classe figlio.
super(A, self)
3- La chiamata super(A, self)
restituisce un proxy che risolverà l'ambito e lo applicherà self
come se ora fosse un'istanza della classe genitore. Chiamiamo quel proxy s
. Poiché __init__()
è un metodo di istanza, la chiamata s.__init__(...)
passerà implicitamente un riferimento self
come primo argomento al genitore __init__()
.
4- per fare lo stesso senza super
che sia necessario passare esplicitamente un riferimento a un'istanza alla versione parent di __init__()
.
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
object.__init__(self, *a, **kw)
Usando super
con un metodo di classe
class A(object):
@classmethod
def alternate_constructor(cls, *a, **kw):
print "A.alternate_constructor called"
return cls(*a, **kw)
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return super(B, cls).alternate_constructor(*a, **kw)
Spiegazione:
1- Un metodo di classe può essere chiamato direttamente dalla classe e prende come primo parametro un riferimento alla classe.
# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()
2- quando si chiama super()
all'interno di un metodo di classe per risolvere la versione del suo genitore, vogliamo passare l'attuale classe figlio come primo argomento per indicare a quale ambito del genitore stiamo cercando di risolvere e l'oggetto di interesse come secondo argomento per indicare a quale oggetto vogliamo applicare tale ambito, che in generale è un riferimento alla classe figlio stessa o ad una delle sue sottoclassi.
super(B, cls_or_subcls)
3- La chiamata si super(B, cls)
risolve nell'ambito A
e la applica a cls
. Poiché alternate_constructor()
è un metodo di classe, la chiamata super(B, cls).alternate_constructor(...)
passerà implicitamente un riferimento cls
come primo argomento alla A
versione dialternate_constructor()
super(B, cls).alternate_constructor()
4- per fare lo stesso senza usare super()
è necessario ottenere un riferimento alla versione non associata di A.alternate_constructor()
(ovvero la versione esplicita della funzione). Fare semplicemente questo non funzionerebbe:
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
return A.alternate_constructor(cls, *a, **kw)
Quanto sopra non funzionerebbe perché il A.alternate_constructor()
metodo prende un riferimento implicito A
come primo argomento. L' cls
essere passato qui sarebbe il suo secondo argomento.
class B(A):
@classmethod
def alternate_constructor(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
print "B.alternate_constructor called"
# first we get a reference to the unbound
# `A.alternate_constructor` function
unbound_func = A.alternate_constructor.im_func
# now we call it and pass our own `cls` as its first argument
return unbound_func(cls, *a, **kw)