Introdução
Neste laboratório, você aprenderá como personalizar o comportamento de objetos redefinindo métodos especiais. Você também mudará a maneira como objetos definidos pelo usuário são impressos e tornará os objetos comparáveis.
Adicionalmente, você aprenderá a criar um gerenciador de contexto (context manager). O arquivo a ser modificado neste laboratório é stock.py.
Melhorando a Representação de Objetos com __repr__
Em Python, os objetos podem ser representados como strings de duas maneiras diferentes. Essas representações servem a propósitos diferentes e são úteis em vários cenários.
O primeiro tipo é a representação de string. Esta é criada pela função str(), que é chamada automaticamente quando você usa a função print(). A representação de string é projetada para ser legível por humanos. Ela apresenta o objeto em um formato que é fácil para nós entendermos e interpretarmos.
O segundo tipo é a representação de código. Esta é gerada pela função repr(). A representação de código mostra o código que você precisaria escrever para recriar o objeto. É mais sobre fornecer uma maneira precisa e inequívoca de representar o objeto em código.
Vamos olhar para um exemplo concreto usando a classe date embutida do Python. Isso ajudará você a ver a diferença entre as representações de string e código em ação.
>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d) ## Uses str()
2008-07-05
>>> d ## Uses repr()
datetime.date(2008, 7, 5)
Neste exemplo, quando usamos print(d), o Python chama a função str() no objeto date d, e obtemos uma data legível em formato AAAA-MM-DD. Quando simplesmente digitamos d no shell interativo, o Python chama a função repr(), e vemos o código necessário para recriar o objeto date.
Você pode obter explicitamente a string repr() de várias maneiras. Aqui estão alguns exemplos:
>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)
Agora, vamos aplicar esse conceito à nossa classe Stock. Vamos melhorar a classe implementando o método __repr__. Este método especial é chamado pelo Python quando ele precisa da representação de código de um objeto.
Para fazer isso, abra o arquivo stock.py em seu editor. Em seguida, adicione o método __repr__ à classe Stock. O método __repr__ deve retornar uma string que mostra o código necessário para recriar o objeto Stock.
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
Depois de adicionar o método __repr__, sua classe Stock completa agora deve se parecer com isto:
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
Agora, vamos testar nossa classe Stock modificada. Abra um shell interativo do Python executando o seguinte comando em seu terminal:
python3
Depois que o shell interativo estiver aberto, tente os seguintes comandos:
>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)
Você também pode ver como o método __repr__ funciona com um portfólio de ações. Aqui está um exemplo:
>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]
Como você pode ver, o método __repr__ tornou nossos objetos Stock muito mais informativos quando exibidos no shell interativo ou no depurador. Agora ele mostra o código necessário para recriar cada objeto, o que é muito útil para depurar e entender o estado dos objetos.
Quando terminar de testar, você pode sair do interpretador Python executando o seguinte comando:
>>> exit()
Tornando Objetos Comparáveis com __eq__
Em Python, quando você usa o operador == para comparar dois objetos, o Python na verdade chama o método especial __eq__. Por padrão, este método compara as identidades dos objetos, o que significa que ele verifica se eles estão armazenados no mesmo endereço de memória, em vez de comparar seus conteúdos.
Vamos dar uma olhada em um exemplo. Suponha que tenhamos uma classe Stock e criemos dois objetos Stock com os mesmos valores. Então, tentamos compará-los usando o operador ==. Veja como você pode fazer isso no interpretador Python:
Primeiro, inicie o interpretador Python executando o seguinte comando em seu terminal:
python3
Em seguida, no interpretador Python, execute o seguinte código:
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False
Como você pode ver, embora os dois objetos Stock a e b tenham os mesmos valores para seus atributos (name, shares e price), o Python os considera objetos diferentes porque eles estão armazenados em locais de memória diferentes.
Para corrigir esse problema, podemos implementar o método __eq__ em nossa classe Stock. Este método será chamado toda vez que o operador == for usado em objetos da classe Stock.
Agora, abra o arquivo stock.py novamente. Dentro da classe Stock, adicione o seguinte método __eq__:
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
Vamos detalhar o que este método faz:
- Primeiro, ele usa a função
isinstancepara verificar se o objetootheré uma instância da classeStock. Isso é importante porque só queremos comparar objetosStockcom outros objetosStock. - Se
otherfor um objetoStock, ele então compara os atributos (name,shareseprice) tanto do objetoselfquanto do objetoother. - Ele retorna
Truesomente se ambos os objetos forem instânciasStocke seus atributos forem idênticos.
Depois de adicionar o método __eq__, sua classe Stock completa deve se parecer com isto:
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, shares):
self.shares -= shares
def __repr__(self):
return f"Stock('{self.name}', {self.shares}, {self.price})"
def __eq__(self, other):
return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
(other.name, other.shares, other.price))
Agora, vamos testar nossa classe Stock aprimorada. Inicie o interpretador Python novamente:
python3
Em seguida, execute o seguinte código no interpretador Python:
>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False
Ótimo! Agora nossos objetos Stock podem ser comparados corretamente com base em seu conteúdo, em vez de seus endereços de memória.
A verificação isinstance no método __eq__ é crucial. Ela garante que estamos apenas comparando objetos Stock. Se não tivéssemos essa verificação, comparar um objeto Stock com algo que não é um objeto Stock poderia gerar erros.
Quando terminar de testar, você pode sair do interpretador Python executando o seguinte comando:
>>> exit()
Criando um Gerenciador de Contexto
Um gerenciador de contexto é um tipo especial de objeto em Python. Em Python, os objetos podem ter diferentes métodos que definem seu comportamento. Um gerenciador de contexto define especificamente dois métodos importantes: __enter__ e __exit__. Esses métodos trabalham juntos com a instrução with. A instrução with é usada para configurar um contexto específico para um bloco de código. Pense nisso como a criação de um pequeno ambiente onde certas coisas acontecem e, quando o bloco de código é finalizado, o gerenciador de contexto cuida da limpeza.
Nesta etapa, vamos criar um gerenciador de contexto que tem uma função muito útil. Ele redirecionará temporariamente a saída padrão (sys.stdout). A saída padrão é onde a saída normal do seu programa Python vai, geralmente o console. Ao redirecioná-la, podemos enviar a saída para um arquivo em vez disso. Isso é útil quando você deseja salvar a saída que, de outra forma, seria exibida apenas no console.
Primeiro, precisamos criar um novo arquivo para escrever nosso código do gerenciador de contexto. Vamos nomear este arquivo redirect.py. Você pode criá-lo usando o seguinte comando no terminal:
touch /home/labex/project/redirect.py
Agora que o arquivo foi criado, abra-o em um editor. Depois de aberto, adicione o seguinte código Python ao arquivo:
import sys
class redirect_stdout:
def __init__(self, out_file):
self.out_file = out_file
def __enter__(self):
self.stdout = sys.stdout
sys.stdout = self.out_file
return self.out_file
def __exit__(self, ty, val, tb):
sys.stdout = self.stdout
Vamos detalhar o que este gerenciador de contexto faz:
__init__: Este é o método de inicialização. Quando criamos uma instância da classeredirect_stdout, passamos um objeto de arquivo. Este método armazena esse objeto de arquivo na variável de instânciaself.out_file. Portanto, ele lembra para onde queremos redirecionar a saída.__enter__:- Primeiro, ele salva o
sys.stdoutatual. Isso é importante porque precisamos restaurá-lo mais tarde. - Em seguida, ele substitui o
sys.stdoutatual pelo nosso objeto de arquivo. A partir deste ponto, qualquer saída que normalmente iria para o console irá para o arquivo. - Finalmente, ele retorna o objeto de arquivo. Isso é útil porque podemos querer usar o objeto de arquivo dentro do bloco
with.
- Primeiro, ele salva o
__exit__:- Este método restaura o
sys.stdoutoriginal. Portanto, após o blocowithser finalizado, a saída voltará para o console como normal. - Ele recebe três parâmetros: tipo de exceção (
ty), valor da exceção (val) e traceback (tb). Esses parâmetros são exigidos pelo protocolo do gerenciador de contexto. Eles são usados para lidar com quaisquer exceções que possam ocorrer dentro do blocowith.
- Este método restaura o
Agora, vamos testar nosso gerenciador de contexto. Vamos usá-lo para redirecionar a saída de uma tabela para um arquivo. Primeiro, inicie o interpretador Python:
python3
Em seguida, execute o seguinte código Python no interpretador:
>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
... tableformat.print_table(portfolio, ['name','shares','price'], formatter)
... file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
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
Ótimo! Nosso gerenciador de contexto funcionou como esperado. Ele redirecionou com sucesso a saída da tabela para o arquivo out.txt.
Os gerenciadores de contexto são um recurso muito poderoso em Python. Eles ajudam você a gerenciar recursos adequadamente. Aqui estão alguns casos de uso comuns para gerenciadores de contexto:
- Operações de arquivo: Quando você abre um arquivo, um gerenciador de contexto pode garantir que o arquivo seja fechado corretamente, mesmo que ocorra um erro.
- Conexões de banco de dados: Ele pode garantir que a conexão do banco de dados seja fechada depois que você terminar de usá-la.
- Locks em programas encadeados: Os gerenciadores de contexto podem lidar com o bloqueio e desbloqueio de recursos de forma segura.
- Alterar temporariamente as configurações do ambiente: Você pode alterar algumas configurações para um bloco de código e, em seguida, restaurá-las automaticamente.
Este padrão é muito importante porque garante que os recursos sejam limpos adequadamente, mesmo que ocorra uma exceção dentro do bloco with.
Quando terminar de testar, você pode sair do interpretador Python:
>>> exit()
Resumo
Neste laboratório, você aprendeu como personalizar a representação de string de objetos usando o método __repr__, tornar objetos comparáveis com o método __eq__ e criar um gerenciador de contexto usando os métodos __enter__ e __exit__. Esses "métodos dunder" especiais são a pedra angular dos recursos orientados a objetos do Python.
Implementar esses métodos em suas classes permite que seus objetos se comportem como tipos embutidos e se integrem perfeitamente aos recursos da linguagem Python. Métodos especiais permitem várias funcionalidades, como representações de string personalizadas, comparação de objetos e gerenciamento de contexto. À medida que você avança em Python, descobrirá mais métodos especiais para aproveitar seu poderoso modelo de objetos.