Ao chamar super()
para resolver a versão do pai de um método de classe, método de instância ou método estático, queremos passar a classe atual cujo escopo estamos no primeiro argumento, para indicar qual escopo do pai estamos tentando resolver e como um segundo argumento, o objeto de interesse para indicar a qual objeto estamos tentando aplicar esse escopo.
Considere-se uma hierarquia de classe A
, B
e C
onde cada classe é o progenitor do um que se lhe segue, e a
, b
, e c
respectivas ocorrências de cada.
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
com um método static
por exemplo, usando super()
de dentro do __new__()
método
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return super(A, cls).__new__(cls, *a, **kw)
Explicação:
1 - embora seja usual __new__()
tomar como primeiro parâmetro uma referência à classe de chamada, ela não é implementada no Python como um método de classe, mas como um método estático. Ou seja, uma referência a uma classe deve ser passada explicitamente como o primeiro argumento ao chamar __new__()
diretamente:
# 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- Ao chamar super()
para chegar à classe pai, passamos a classe filho A
como seu primeiro argumento, depois passamos uma referência ao objeto de interesse; nesse caso, é a referência da classe que foi passada quando A.__new__(cls)
foi chamada. Na maioria dos casos, também é uma referência à classe filho. Em algumas situações, pode não ser, por exemplo, no caso de heranças de várias gerações.
super(A, cls)
3- Como, como regra geral, __new__()
é um método estático, super(A, cls).__new__
também retornará um método estático e precisará fornecer todos os argumentos explicitamente, incluindo a referência ao objeto de interesse, neste caso cls
.
super(A, cls).__new__(cls, *a, **kw)
4- fazer a mesma coisa sem 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
com um método de instância
por exemplo, usando super()
de dentro__init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Explicação:
1- __init__
é um método de instância, o que significa que ele usa como primeiro argumento uma referência a uma instância. Quando chamada diretamente da instância, a referência é passada implicitamente, ou seja, você não precisa especificá-la:
# 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- ao chamar super()
dentro __init__()
, passamos a classe filho como o primeiro argumento e o objeto de interesse como um segundo argumento, que em geral é uma referência a uma instância da classe filho.
super(A, self)
3- A chamada super(A, self)
retorna um proxy que resolverá o escopo e aplicará self
como se fosse agora uma instância da classe pai. Vamos chamar esse proxy s
. Como __init__()
é um método de instância, a chamada s.__init__(...)
passa implicitamente uma referência self
como o primeiro argumento para o pai __init__()
.
4- fazer o mesmo sem super
precisar passar uma referência a uma instância explicitamente para a versão do pai __init__()
.
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
object.__init__(self, *a, **kw)
Usando super
com um método de 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)
Explicação:
1 - Um método de classe pode ser chamado diretamente da classe e toma como primeiro parâmetro uma referência à 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- Ao chamar super()
dentro de um método de classe para resolver a versão do pai, queremos passar a classe filho atual como o primeiro argumento para indicar qual escopo do pai estamos tentando resolver e o objeto de interesse como o segundo argumento para indicar a qual objeto queremos aplicar esse escopo, que em geral é uma referência à própria classe filho ou a uma de suas subclasses.
super(B, cls_or_subcls)
3- A chamada super(B, cls)
resolve o escopo A
e aplica-a cls
. Desde aalternate_constructor()
é um método de classe, a chamada super(B, cls).alternate_constructor(...)
passará implicitamente uma referência cls
como o primeiro argumento para A
a versão dealternate_constructor()
super(B, cls).alternate_constructor()
4- fazer o mesmo sem usar super()
você precisará obter uma referência à versão não acoplada de A.alternate_constructor()
(ou seja, a versão explícita da função). Simplesmente fazer isso não funcionaria:
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)
O exemplo acima não funcionaria porque o A.alternate_constructor()
método usa uma referência implícita A
como seu primeiro argumento. O que cls
foi aprovado aqui seria seu segundo argumento.
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)