Ir para o conteúdo

Métodos especiais

Métodos especiais

Métodos Especiais

class MinhaClasse:
    def __init__(self, a, b):
    ...

p1 = MinhaClasse(2, 5)
p2 = MinhaClasse(-1, 10)

p3 = p1 + p2
p4 = p1 - p2
p5 = p1*p2
p6 = p1**7 + 4*p3

Métodos especiais permitem sintaxes diretas e são reconhecidos pelos duplos _ antes e depois do nome do método:

def __init__(self, ...)
def __call__(self, ...)
def __add__(self, outro)

## sintaxe Python
y = Y(4)
print y(2)
z = Y(6)
print y + z

## O que realmente está acontecendo
Y.__init__(y, 4)
print Y.__call__(y, 2)
Y.__init__(z, 6)
print Y.__add__(y, z)

Vamos aprender mais sobre eles!

Chamando um método especial

Substitua o método valor pelo método especial __call__:

class Y:
    def __init__(self, v0):
        self.v0 = v0
        self.g = 9.81

    def __call__(self, t):
        return self.v0*t - 0.5*self.g*t**2

Agora podemos escrever:

y = Y(3)
v = y(0.1) ## o mesmo que v = y.__call__(0.1) ou Y.__call__(y, 0.1)

Nota:

  • A instância y se comporta e parece como uma função; O método valor(t) faz a mesma coisa, mas __call__ permite uma sintaxe mais simples e limpa para calcular o valor da função.

Um método especial para a saída

  • Em Python, normalmente obtemos uma saída em formato de texto usando o objeto print, que funciona para tipos de objetos já embutidos (strings, listas, floats...);
  • Python não sabe como imprimir objetos de uma classe definida pelo usuário, mas se a classe definir um método __str__, o Python vai utilizar esse método para converter um objeto em cadeia de caracteres.

Exemplo:

class Y:
    ...
    def __call__(self, t):
        return self.v0*t - 0.5*self.g*t**2

    def __str__(self):
        return 'v0*t - 0.5*g*t**2; v0=%g' % self.v0

Uso:

>>> y = Y(1.5)
>>> y(0.2)
0.1038
>>> print y
v0*t - 0.5*g*t**2; v0=1.5

Definição dos métodos especiais

Esté a cargo do programador definir a ação dos métodos especiais. Como deveria __add__(self, outro) ser definido? Isso depende completamente do programador, dependendo do significado de objeto1 + objeto2.

Um antropoligista estava perguntando a um homem das cavernas primitivo sobre aritmética. Quando o antropologista perguntava, O que dois e dois fazem? o homem das cavernas respondia, cinco. Perguntado para explicar, o homem das cavernas disse, se eu tenho uma corda com dois nós, e outra corda com dois nós, e eu unir as duas cordas para fazer uma, então eu tenho cinco nós.

Métodos especiais para operações aritméticas

c = a + b    ##  c = a.__add__(b)

c = a - b    ##  c = a.__sub__(b)

c = a*b      ##  c = a.__mul__(b)

c = a/b      ##  c = a.__div__(b)

c = a**e     ##  c = a.__pow__(e)

Métodos especiais para comparação

a == b       ##  a.__eq__(b)

a != b       ##  a.__ne__(b)

a < b        ##  a.__lt__(b)

a <= b       ##  a.__le__(b)

a > b        ##  a.__gt__(b)

a >= b       ##  a.__ge__(b)

O método especial __repr__

O método especial __repr__ cria p de eval(repr(p))

class MinhaClasse:
    def __init__(self, a, b):
        self.a, self.b = a, b

    def __str__(self):
        """Retorna uma cadeia de caracteres formatada com a saída"""
        return 'a=%s, b=%s' % (self.a, self.b)

    def __repr__(self):
        """Retorna uma cadeia de caracteres tal que eval(s) cria self."""
        return 'MinhaClasse(%s, %s)' % (self.a, self.b)

Uso

>>> m = MinhaClasse(1, 5)
>>> print m      ## chama m.__str__()
a=1, b=5
>>> str(m)       ## chama m.__str__()
'a=1, b=5'
>>> s = repr(m)  ## chama m.__repr__()
>>> s
'MinhaClasse(1, 5)'
>>> m2 = eval(s) ## o mesmo que m2 = MinhaClasse(1, 5)
>>> m2           ## chama m.__repr__()
'MinhaClasse(1, 5)'

Classe Y reescrita com o método repr

class Y:
    """Classe para a função y(t; v0, g) = v0*t - 0.5*g*t**2."""

    def __init__(self, v0):
        """Armazenando parâmetros."""
        self.v0 = v0
        self.g = 9.81

    def __call__(self, t):
        """Avaliando a função."""
        return self.v0*t - 0.5*self.g*t**2

    def __str__(self):
        """Saída formatada."""
        return 'v0*t - 0.5*g*t**2; v0=%g' % self.v0

    def __repr__(self):
        """Mostre o código para gerar essa instância."""
        return 'Y(%s)' % self.v0

Classe para números complexos

O Python já possui a classe complex para números complexos, mas implementar tal classe é um bom exemplo pedagógico especialmente com métodos especiais.

Uso esperado:

>>> u = Complexo(2,-1)
>>> v = Complexo(1)     ## parte imaginária zero
>>> w = u + v
>>> print w
(3, -1)
>>> w != u
True
>>> u*v
Complexo(2, -1)
>>> u < v
illegal operation "<" for complex numbers
>>> print w + 4
(7, -1)
>>> print 4 - w
(1, 1)

Implementação

class Complexo:
    def __init__(self, real, imag=0.0):
        self.real = real
        self.imag = imag

    def __add__(self, outro):
        return Complexo(self.real + outro.real,
                       self.imag + outro.imag)

    def __sub__(self, outro):
        return Complexo(self.real - outro.real,
                       self.imag - outro.imag)

    def __mul__(self, outro):
        return Complexo(self.real*outro.real - self.imag*outro.imag,
                       self.imag*outro.real + self.real*outro.imag)

    def __div__(self, outro):
        ar, ai, br, bi = self.real, self.imag, \
                         outro.real, outro.imag ## forma curta
        r = float(br**2 + bi**2)
        return Complexo((ar*br+ai*bi)/r, (ai*br-ar*bi)/r)

Métodos adicionais

    def __abs__(self):
        return sqrt(self.real**2 + self.imag**2)

    def __neg__(self):   ## defines -c (c is Complexo)
        return Complexo(-self.real, -self.imag)

    def __eq__(self, outro):
        return self.real == outro.real and \
               self.imag == outro.imag

    def __ne__(self, outro):
        return not self.__eq__(outro)

    def __str__(self):
        return '(%g, %g)' % (self.real, self.imag)

    def __repr__(self):
        return 'Complexo' + str(self)

    def __pow__(self, potencia):
        raise NotImplementedError(
          'self**potencia ainda não implementado para Complexo')

Refinando os métodos especiais para aritmética

Podemos adicionar um número real a um número complexo?

>>> u = Complexo(1, 2)
>>> w = u + 4.5
...
AttributeError: 'float' object has no attribute 'real'

Problema: vamos assumir que o outro é Complexo. Solução:

class Complexo:
    ...
    def __add__(self, outro):
        if isinstance(outro, (float,int)):
            outro = Complexo(outro)
        return Complexo(self.real + outro.real,
                       self.imag + outro.imag)

## ou

    def __add__(self, outro):
        if isinstance(outro, (float,int)):
            return Complexo(self.real + outro, self.imag)
        else:
            return Complexo(self.real + outro.real,
                           self.imag + outro.imag)

Métodos especiais para adição

E se tentarmos:

>>> u = Complexo(1, 2)
>>> w = 4.5 + u
...
TypeError: unsupported operand type(s) for +:
    'float' and 'instance'

Problema: o objeto float do Python não pode ser adicionado a um objeto Complexo.

Solução: se a classe tem o método especial __radd__(self, other), Python o aplica para outro + self.

class Complexo:
    ...
    def __radd__(self, other):
        """Retorna outro + self."""
        ## outro + self = self + outro:
        return self.__add__(outro)

Métodos especiais para subtração

Operadores para subtração são um pouco mais complicados pois

class Complexo:
    ...
    def __sub__(self, outro):
        if isinstance(outro, (float,int)):
            outro = Complexo(outro)
        return Complexo(self.real - outro.real,
                       self.imag - outro.imag)

    def __rsub__(self, outro):
        if isinstance(outro, (float,int)):
            outro = Complexo(outro)
        return outro.__sub__(self)