Personalizando o Comportamento Dinâmico do Python

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Várias partes do comportamento do Python podem ser personalizadas através de métodos especiais ou os chamados métodos "mágicos" (magic methods). Esta seção introduz essa ideia. Além disso, o acesso dinâmico a atributos e métodos vinculados (bound methods) são discutidos.

Introdução

Classes podem definir métodos especiais. Estes têm um significado especial para o interpretador Python. Eles são sempre precedidos e seguidos por __. Por exemplo, __init__.

class Stock(object):
    def __init__(self):
        ...
    def __repr__(self):
        ...

Existem dezenas de métodos especiais, mas analisaremos apenas alguns exemplos específicos.

Métodos Especiais para Conversões de String

Objetos têm duas representações de string.

>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>

A função str() é usada para criar uma saída imprimível agradável:

>>> str(d)
'2012-12-21'
>>>

A função repr() é usada para criar uma representação mais detalhada para programadores.

>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>

Essas funções, str() e repr(), usam um par de métodos especiais na classe para produzir a string a ser exibida.

class Date(object):
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    ## Used with `str()`
    def __str__(self):
        return f'{self.year}-{self.month}-{self.day}'

    ## Used with `repr()`
    def __repr__(self):
        return f'Date({self.year},{self.month},{self.day})'

Nota: A convenção para __repr__() é retornar uma string que, quando fornecida a eval(), recriará o objeto subjacente. Se isso não for possível, algum tipo de representação facilmente legível é usado em vez disso.

Métodos Especiais para Matemática

Operadores matemáticos envolvem chamadas aos seguintes métodos.

a + b       a.__add__(b)
a - b       a.__sub__(b)
a * b       a.__mul__(b)
a / b       a.__truediv__(b)
a // b      a.__floordiv__(b)
a % b       a.__mod__(b)
a << b      a.__lshift__(b)
a >> b      a.__rshift__(b)
a & b       a.__and__(b)
a | b       a.__or__(b)
a ^ b       a.__xor__(b)
a ** b      a.__pow__(b)
-a          a.__neg__()
~a          a.__invert__()
abs(a)      a.__abs__()

Métodos Especiais para Acesso a Itens

Estes são os métodos para implementar containers.

len(x)      x.__len__()
x[a]        x.__getitem__(a)
x[a] = v    x.__setitem__(a,v)
del x[a]    x.__delitem__(a)

Você pode usá-los em suas classes.

class Sequence:
    def __len__(self):
        ...
    def __getitem__(self,a):
        ...
    def __setitem__(self,a,v):
        ...
    def __delitem__(self,a):
        ...

Invocação de Método

Invocar um método é um processo de duas etapas.

  1. Lookup (Pesquisa): O operador .
  2. Chamada de método: O operador ()
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost  ## Lookup
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()         ## Method call
49010.0
>>>

Métodos Vinculados (Bound Methods)

Um método que ainda não foi invocado pelo operador de chamada de função () é conhecido como um método vinculado (bound method). Ele opera na instância onde se originou.

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>

Métodos vinculados são frequentemente uma fonte de erros descuidados e não óbvios. Por exemplo:

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>

Ou um comportamento traiçoeiro que é difícil de depurar.

f = open(filename, 'w')
...
f.close     ## Oops, Didn't do anything at all. `f` still open.

Em ambos os casos, o erro é causado por esquecer de incluir os parênteses finais. Por exemplo, s.cost() ou f.close().

Acesso a Atributos

Existe uma forma alternativa de acessar, manipular e gerenciar atributos.

getattr(obj, 'name')          ## Same as obj.name
setattr(obj, 'name', value)   ## Same as obj.name = value
delattr(obj, 'name')          ## Same as del obj.name
hasattr(obj, 'name')          ## Tests if attribute exists

Exemplo:

if hasattr(obj, 'x'):
    x = getattr(obj, 'x'):
else:
    x = None

*Nota: getattr() também possui um valor padrão útil *arg*.

x = getattr(obj, 'x', None)

Exercício 4.9: Melhor saída para impressão de objetos

Modifique o objeto Stock que você definiu em stock.py para que o método __repr__() produza uma saída mais útil. Por exemplo:

>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>

Veja o que acontece quando você lê um portfólio de ações e visualiza a lista resultante após fazer essas alterações. Por exemplo:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... see what the output is ...
>>>

Exercício 4.10: Um exemplo de uso de getattr()

getattr() é um mecanismo alternativo para ler atributos. Ele pode ser usado para escrever código extremamente flexível. Para começar, experimente este exemplo:

>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name', 'shares']
>>> for colname in columns:
        print(colname, '=', getattr(s, colname))

name = GOOG
shares = 100
>>>

Observe cuidadosamente que os dados de saída são determinados inteiramente pelos nomes dos atributos listados na variável columns.

No arquivo tableformat.py, use essa ideia e expanda-a em uma função generalizada print_table() que imprime uma tabela mostrando atributos especificados pelo usuário de uma lista de objetos arbitrários. Assim como com a função print_report() anterior, print_table() também deve aceitar uma instância de TableFormatter para controlar o formato de saída. Veja como deve funcionar:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
      name     shares
---------- ----------
        AA        100
       IBM         50
       CAT        150
      MSFT        200
        GE         95
      MSFT         50
       IBM        100

>>> print_table(portfolio, ['name','shares','price'], formatter)
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44
>>>

Resumo

Parabéns! Você concluiu o laboratório de Métodos Especiais. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.