Python Fluente

Propaganda
Python Fluente
Luciano Ramalho
Novatec
capítulo 1
Modelo de dados do Python
O senso estético de Guido no design da linguagem é incrível. Conheci muitos bons
designers de linguagem que poderiam ter criado belas linguagens em teoria, mas que
ninguém jamais usaria, porém Guido é uma dessas raras pessoas capazes de criar uma
linguagem apenas um pouco menos bela teoricamente, mas com a qual é um prazer
escrever programas.1
— Jim Hugunin
Criador do Jython, cocriador do AspectJ, arquiteto do DLR do .Net
Uma das melhores qualidades de Python é a sua consistência. Depois de trabalhar com
Python por um tempo, você começará a ter palpites corretos e bem fundamentados
sobre recursos que você ainda nem estudou na documentação.
No entanto, se você aprendeu outra linguagem orientada a objetos antes de Python,
poderá achar estranho escrever len(collection) em vez de collection.len(). Essa aparente
estranheza é a ponta de um iceberg que, quando bem compreendido, é o segredo de
tudo que consideramos pythônico. O iceberg é chamado de Python data model (modelo de dados de Python) e descreve a API que você pode usar para fazer com que
seus próprios objetos interajam bem com os recursos mais idiomáticos da linguagem.
Podemos pensar no modelo de dados como uma descrição de Python na forma de um
framework. Ele formaliza as interfaces dos blocos de construção da própria linguagem,
por exemplo, as sequências, os iteradores, as funções, as classes, os gerenciadores de
contexto e assim por diante.
Ao codificar com qualquer framework, você investe bastante tempo implementando
métodos que são chamados pelo framework. O mesmo acontece quando você aproveita o modelo de dados do Python. O interpretador Python chama métodos especiais
para realizar operações básicas em objetos, geralmente acionados por uma sintaxe
especial. Os nomes dos métodos especiais são sempre escritos com underscores duplos
1 História do Jython (http://hugunin.net/story_of_jython.html), escrita como prefácio do livro Jython
Essentials (http://bit.ly/jython-essentials) (O’Reilly, 2002), por Samuele Pedroni e Noel Rappin.
28
Capítulo 1 ■ Modelo de dados do Python
29
no início e no fim (ou seja, __getitem__). Por exemplo, a sintaxe obj[key] é tratada pelo
método especial __getitem__. Para avaliar my_collection[key], o interpretador chama
my_collection.__getitem__(key).
Os nomes dos métodos especiais permitem que seus objetos implementem, tratem
e interajam com as construções básicas da linguagem, tais como:
•Iteração
•Coleções
• Acesso a atributos
• Sobrecarga de operadores
• Chamada de funções e de métodos
• Criação e destruição de objetos
• Representação e formatação de strings
• Contextos gerenciados (ou seja, blocos with)
“mágico” e “dunder”
O termo método mágico é uma gíria para método especial, porém,
quando falamos de um método específico como __getitem__, alguns
desenvolvedores Python simplesmente dizem “under-under-getitem”,
que é ambíguo, pois a sintaxe __x tem outro significado especial (*). Ser
exato e pronunciar “under-under-getitem-under-under” é cansativo,
portanto sigo a dica do autor e professor Steve Holden e digo “dundergetitem”. Todos os pythonistas experientes entendem essa forma
abreviada. Por isso os métodos especiais também são conhecidos como
métodos dunder (dunder methods). (**)
Veja a seção “Atributos privados e “protegidos” em Python” na página 304.
Ouvi pessoalmente o termo “dunder” pela primeira vez de Steve Holden. A
Wikipedia atribui (http://bit.ly/1Vm72Mf) os primeiros registros escritos do termo
“dunder” a Mark Johnson e a Tim Hochberg em respostas à pergunta “How do
you pronounce __ (double underscore)?” (Como você pronuncia __ (underscores
duplos)?) na python-list em 26 de setembro de 2002: mensagem de Johnson
(https://mail.python.org/pipermail/python-list/2002-September/); mensagem de
Hochberg (https://mail.python.org/pipermail/python-list/2002-September/) – onze
minutos depois.
(*)
(**)
Um baralho pythônico
A seguir, apresentamos um exemplo bem simples, mas que mostra a eficácia da implementação de apenas dois métodos especiais, __getitem__ e __len__.
30
Python Fluente
O exemplo 1.1 mostra uma classe que representa um conjunto de cartas de baralho.
Exemplo 1.1 – Um baralho como uma sequência de cartas
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
O primeiro aspecto a ser observado é o uso de collections.namedtuple para criar uma
classe simples que representa as cartas individuais. Desde Python 2.6, namedtuple pode
ser usada para criar classes de objetos que sejam apenas grupos de atributos, sem
métodos próprios, como um registro de banco de dados. No exemplo, ela foi usado
para fornecer uma bela representação das cartas do baralho, conforme mostrado
nesta sessão do console:
>>> beer_card = Card('7', 'diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')
No entanto o ponto principal desse exemplo é a classe FrenchDeck. Ela é concisa, porém
bastante funcional. Para começar, como qualquer coleção Python padrão, o baralho
responde à função len(), devolvendo o número de cartas que ele contém:
>>> deck = FrenchDeck()
>>> len(deck)
52
Ler cartas específicas do baralho – por exemplo, a primeira ou a última – é simples;
basta usar deck[0] ou deck[-1], e é isso o que o método __getitem__ faz acontecer:
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
Capítulo 1 ■ Modelo de dados do Python
31
Devemos criar um método para selecionar uma carta aleatória? Não é necessário.
O Python já tem uma função para obter um item aleatório de uma sequência:
random.choice. Podemos simplesmente usá-la em uma instância do baralho:
>>> from random import choice
>>> choice(deck)
Card(rank='3', suit='hearts')
>>> choice(deck)
Card(rank='K', suit='spades')
>>> choice(deck)
Card(rank='2', suit='clubs')
Acabamos de ver duas vantagens de usar os métodos especiais para tirar proveito do
modelo de dados do Python:
• Os usuários de suas classes não precisarão memorizar nomes arbitrários de
métodos para realizar operações comuns (“Como obter a quantidade de itens?
Devo usar .size(), .length() ou o quê?”).
• É mais fácil se beneficiar da rica biblioteca-padrão do Python do que reinventar
a roda, como no caso da função random.choice.
Mas o que já temos é ainda melhor.
Como nosso __getitem__ delega a responsabilidade ao operador [] de self._cards,
nosso baralho já suporta fatiamento (slicing). Veja como podemos inspecionar as
três primeiras cartas de um baralho novo e, em seguida, escolher somente os ases,
começando no índice 12 e avançando treze cartas de cada vez:
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
Só por implementar o método especial __getitem__, nosso baralho também é iterável:
>>> for card in deck: # doctest: +ELLIPSIS
... print(card)
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
...
32
Python Fluente
A iteração também pode ser feita no baralho na ordem inversa:
>>> for card in reversed(deck): # doctest: +ELLIPSIS
... print(card)
Card(rank='A', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='Q', suit='hearts')
...
Reticências em doctests
Sempre que possível, as listagens do console do Python neste livro
foram extraídas de doctests para garantir a exatidão. Quando a saída
for longa demais, a parte omitida será marcada com reticências (...),
como na última linha do código anterior. Em casos como esse, usamos a
diretiva #doctest: +ELLIPSIS para fazer o doctest passar. Se estiver testando
esses exemplos no console interativo, você poderá omitir totalmente as
diretivas do doctest.
A iteração muitas vezes é implícita. Se uma coleção não tiver nenhum método
__contains__, o operador in fará uma varredura sequencial. Em nosso caso em particular, in funciona com a classe FrenchDeck porque ela é iterável. Veja só:
>>> Card('Q', 'hearts') in deck
True
>>> Card('7', 'beasts') in deck
False
E o que dizer da ordenação? Um sistema comum de classificação de cartas é pelo
valor (com os ases sendo as cartas mais altas) e, em seguida, pelo naipe na seguinte
ordem: espadas (o maior), depois copas, ouros e paus (o menor). Eis uma função
que classifica as cartas de acordo com essa regra, retornando 0 para o 2 de paus e 51
para o ás de espadas:
suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
def spades_high(card):
rank_value = FrenchDeck.ranks.index(card.rank)
return rank_value * len(suit_values) + suit_values[card.suit]
Após ter definido spades_high, podemos agora listar o nosso baralho em ordem crescente de classificação:
>>> for card in sorted(deck, key=spades_high): # doctest: +ELLIPSIS
...
print(card)
Card(rank='2', suit='clubs')
Card(rank='2', suit='diamonds')
Card(rank='2', suit='hearts')
Capítulo 1 ■ Modelo de dados do Python
33
... (46 cards ommitted)
Card(rank='A', suit='diamonds')
Card(rank='A', suit='hearts')
Card(rank='A', suit='spades')
Embora FrenchDeck herde implicitamente de object2, sua funcionalidade mais importante não é herdada, porém resulta de aproveitarmos o modelo de dados e composição.
Ao implementar os métodos especiais __len__ e __getitem__, nosso FrenchDeck se comporta como uma sequência Python padrão, permitindo que se beneficie de recursos
essenciais da linguagem (por exemplo, iteração e fatiamento) e da biblioteca-padrão,
conforme mostrado nos exemplos que usaram random.choice, reversed e sorted. Graças
à composição, as implementações de __len__ e de __getitem__ podem passar todo o
trabalho para um objeto list, ou seja, self._cards.
E o que dizer de embaralhar?
Conforme implementado até agora, um FrenchDeck não pode ser
embaralhado, pois ele é imutável: as cartas e suas posições não poderão
ser alteradas pelos clientes, exceto se violarem o encapsulamento e
manipularem o atributo _cards diretamente. No capítulo 11, isso será
corrigido por meio da adição de um método __setitem__ de uma linha.
Como os métodos especiais são usados
A primeira coisa a saber sobre os métodos especiais é que eles foram criados
para serem chamados pelo interpretador Python, e não por você. Não escrevemos
my_object.__len__(). Escrevemos len(my_object) e, se my_object for uma instância de
uma classe definida por você, Python chamará o método de instância __len__ que
você implementou.
Porém, para os tipos embutidos (built-in) como list, str, bytearray e outros, o interpretador usará um atalho: a implementação de len() do CPython, na verdade,
retorna o valor do campo ob_size da struct C PyVarObject que representa qualquer
objeto embutido de tamanho variável na memória. Isso é muito mais rápido que
chamar um método.
Na maioria das vezes, a chamada ao método especial será implícita. Por exemplo, a
instrução for i in x: provoca a chamada de iter(x), que, por sua vez, poderá chamar
x.__iter__() se ele estiver disponível.
Normalmente, seu código não deverá ter muitas chamadas diretas aos métodos especiais. A menos que você esteja usando bastante metaprogramação, será mais comum
2 Em Python 2, você deverá ser explícito e escrever FrenchDeck(object), porém esse é o default em Python 3.
34
Python Fluente
você implementar métodos especiais do que invocá-los diretamente. O único método
especial chamado frequentemente de forma direta pelo usuário é __init__, para invocar o inicializador da superclasse quando você implementa seu próprio __init__.
Se for preciso chamar um método especial, normalmente é melhor chamar a função
embutida relacionada (por exemplo, len, iter, str etc.). Essas funções embutidas invocam o método especial correspondente, porém, com frequência, oferecem outros
serviços e – para os tipos embutidos – são mais rápidas que as chamadas de métodos.
Por exemplo, dê uma olhada na seção “Uma visão mais detalhada da função iter” na
página 488 do capítulo 14.
Evite criar atributos arbitrários personalizados com a sintaxe __foo__, pois esses
nomes poderão adquirir significados especiais no futuro, mesmo se não estiverem
em uso atualmente.
Emulando tipos numéricos
Diversos métodos especiais permitem que os objetos do usuário respondam a operadores, como +. Veremos esse assunto com mais detalhes no capítulo 13, porém
nosso objetivo aqui é ilustrar melhor o uso dos métodos especiais por meio de outro
exemplo simples.
Implementaremos uma classe para representar vetores bidimensionais – ou seja,
vetores euclidianos como aqueles usados em matemática e em física (veja a figura 1.1).
Figura 1.1 – Exemplo de soma de vetores bidimensionais; Vector(2, 4) + Vector(2, 1) resulta em
Vector(4, 5).
O tipo embutido complex pode ser usado para representar vetores
bidimensionais, porém nossa classe pode ser estendida para representar
vetores n-dimensionais. Faremos isso no capítulo 14.
Capítulo 1 ■ Modelo de dados do Python
35
Começaremos pelo design da API de uma classe desse tipo escrevendo uma sessão
simulada de console que poderá ser usada posteriormente como um doctest. O trecho
de código a seguir testa a soma de vetores representada na figura 1.1:
>>> v1 = Vector(2, 4)
>>> v2 = Vector(2, 1)
>>> v1 + v2
Vector(4, 5)
Observe como o operador + gera um resultado Vector, exibido de forma amigável no
console.
A função embutida abs retorna o valor absoluto de inteiros e de números de ponto
flutuante e a magnitude de números complexos (complex); sendo assim, para sermos
consistentes, nossa API também usará abs para calcular a magnitude de um vetor:
>>> v = Vector(3, 4)
>>> abs(v)
5.0
Também podemos implementar o operador * para realizar uma multiplicação por
escalar (ou seja, multiplicar um vetor por um número de modo a gerar um novo vetor
com a mesma direção e uma magnitude multiplicada):
>>> v * 3
Vector(9, 12)
>>> abs(v * 3)
15.0
O exemplo 1.2 apresenta uma classe Vector que implementa as operações que acabaram
de ser descritas, por meio dos métodos especiais __repr__, __abs__, __add__ e __mul__.
Exemplo 1.2 – Uma classe simples de vetor bidimensional
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
36
Python Fluente
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
Observe que, apesar de termos implementado quatro métodos especiais (além de
__init__), nenhum deles é chamado diretamente na classe ou no uso típico da classe
ilustrado pelas listagens do console. Conforme mencionamos anteriormente, o interpretador Python é o único a chamar a maioria dos métodos especiais com frequência.
Nas seções a seguir, discutiremos o código de cada método especial.
Representação em string
O método especial __repr__ é chamado pela função embutida repr para obtermos a
representação em string do objeto para inspeção. Se __repr__ não for implementado, as
instâncias dos vetores serão exibidas no console como <Vector object at 0x10e100070>.
O console interativo e o debugger (depurador) chamam repr nos resultados das
expressões avaliadas, como faz a marcação %r na formatação clássica com o operador % e o marcador de conversão !r na nova sintaxe de formatação de strings
(Format String Syntax, http://bit.ly/1Vm7gD1) usada no método str.format.
Falando no operador % e no método str.format, você perceberá que uso
ambos neste livro, como faz a comunidade Python em geral. Cada vez
mais, prefiro o str.format, mais versátil, porém sei que muitos pythonistas
preferem o %, mais simples; sendo assim, é provável que vejamos ambos
em códigos-fonte Python por muito tempo.
Observe que, em nossa implementação de __repr__, usamos %r para obter a representação-padrão dos atributos a serem exibidos. Essa é uma boa prática, pois mostra a diferença
fundamental entre Vector(1, 2) e Vector('1', '2') – a última não funcionará no contexto
desse exemplo, pois os argumentos do construtor devem ser números, e não str.
A string retornada por __repr__ não deve ser ambígua e, se possível, deverá corresponder ao código-fonte necessário para recriar o objeto sendo representado. É por
isso que a representação que escolhemos se parece com a chamada ao construtor da
classe (por exemplo, Vector(3, 4)).
Compare __repr__ com __str__, que é chamada pelo construtor str() e usada implicitamente pela função print. __str__ deve retornar uma string adequada para exibição
aos usuários finais.
Capítulo 1 ■ Modelo de dados do Python
37
Se você implementar somente um desses métodos especiais, escolha __repr__ porque,
quando um __str__ personalizado não estiver disponível, o Python chamará __repr__
como alternativa.
“Difference between __str__ and __repr__ in Python” (Diferença entre
__str__ e __repr__ em Python, http://bit.ly/1Vm7j1N) é uma pergunta do
Stack Overflow com excelentes contribuições dos pythonistas Alex
Martelli e Martijn Pieters.
Operadores aritméticos
O exemplo 1.2 implementa dois operadores, + e *, para mostrar o uso básico de __add__
e de __mul__. Observe que, em ambos os casos, os métodos criam e devolvem uma
nova instância de Vector e não modificam nenhum operando – self ou other são simplesmente lidos. Esse é o comportamento esperado dos operadores infixos: novos
objetos são criados e os operandos não são alterados. Terei muito mais a dizer sobre
esse assunto no capítulo 13.
Conforme implementado, o exemplo 1.2 permite multiplicar um Vector
por um número, mas não um número por um Vector, o que viola a
propriedade comutativa da multiplicação. Corrigiremos isso com o
método especial __rmul__ no capítulo 13.
Valor booleano de um tipo definido pelo usuário
Embora Python tenha um tipo bool, qualquer objeto é aceito em um contexto
booleano, por exemplo, na expressão que controla uma instrução if ou while ou como
operandos de and, or e not. Para determinar se um valor x é verdadeiro ou falso, Python
aplica bool(x), que sempre devolve True ou False.
Por padrão, as instâncias das classes definidas pelo usuário são consideradas verdadeiras, a menos que __bool__ ou __len__ esteja implementado. Basicamente, bool(x)
chama x.__bool__() e utiliza o resultado. Se __bool__ não estiver implementado, Python
tentará chamar x.__len__() e, se o resultado for zero, bool retornará False. Caso contrário, bool retornará True.
Nossa implementação de __bool__ é conceitualmente simples: ele devolverá False se a
magnitude do vetor for zero e True, caso contrário. Convertemos a magnitude em um
valor booleano usando bool(abs(self)), pois espera-se que __bool__ devolva um booleano.
38
Python Fluente
Note como o método especial __bool__ permite que seus objetos sejam consistentes
com as regras de testes para contextos booleanos definidas no capítulo “Built-in Types”
(Tipos embutidos, https://docs.python.org/3/library/stdtypes.html#truth) da documentação
da biblioteca-padrão de Python (The Python Standard Library).
Uma implementação mais rápida de Vector.__bool__ é:
def __bool__(self):
return bool(self.x or self.y)
É mais difícil de ler, porém evita a passagem por abs, __abs__ , os
quadrados e a raiz quadrada. A conversão explícita para bool é necessária
porque __bool__ deve retornar um booleano e or retorna qualquer um dos
operandos como ele é: x or y é avaliado com x se ele for verdadeiro, caso
contrário, o resultado será y, independentemente de seu valor.
Visão geral dos métodos especiais
O capítulo “Data Model” (Modelo de dados, http://docs.python.org/3/reference/datamodel.
html) de The Python Language Reference (Guia de referência à linguagem Python) lista
os nomes de 83 métodos especiais; 47 dos quais são usados para implementar operadores aritméticos, bit a bit (bitwise) e de comparação.
Para ter uma visão geral do que está disponível, veja as tabelas 1.1 e 1.2.
O agrupamento mostrado nas tabelas a seguir não é exatamente o mesmo
que está na documentação oficial.
Tabela 1.1 – Nomes dos métodos especiais (não inclui operadores)
Categoria
Nomes dos métodos
Representação em string/ __repr__, __str__, __format__, __bytes__
bytes
__abs__, __bool__, __complex__, __int__, __float__, __hash__, __index__
Conversão para número
Emulação de coleções
__len__, __getitem__, __setitem__, __delitem__, __contains__
Iteração
__iter__, __reversed__, __next__
Emulação de invocáveis
__call__
Gerenciamento de contexto __enter__, __exit__
Criação e destruição de __new__, __init__, __del__
instâncias
Capítulo 1 ■ Modelo de dados do Python
Categoria
39
Nomes dos métodos
Gerenciamento de atributos __getattr__, __getattribute__, __setattr__, __delattr__, __dir__
Descritores de atributos
__get__, __set__, __delete__
Serviços de classes
__prepare__, __instancecheck__, __subclasscheck__
Tabela 1.2 – Nomes dos métodos especiais para operadores
Categoria
Nomes dos métodos e operadores relacionados
Operadores numéricos unários
__neg__ -, __pos__ +, __abs__ abs()
Operadores de comparação rica
__lt__ >, __le__ <=, __eq__ ==, __ne__ !=, __gt__ >, __ge__ >=
Operadores aritméticos
__add__ +, __sub__ -, __mul__ *, __truediv__ /, __floordiv__ //,
__mod__%, __divmod__ divmod() , __pow__ ** ou pow(), __round__
round()
__radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __
Operadores aritméticos reversos
rmod__, __rdivmod__, __rpow__
Operadores aritméticos de atribuição __iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__,
__imod__, __ipow__
combinada
Operadores bit a bit (bitwise)
Operadores bit a bit reversos
__invert__ ~, __lshift__ <<, __rshift__ >>, __and__ &, __or__
|, __xor__ ^
__rlshift__, __rrshift__, __rand__, __rxor__, __ror__
Operadores bit a bit de atribuição __ilshift__, __irshift__, __iand__, __ixor__, __ior__
combinada
Os operadores reversos são alternativas usadas quando os operandos são
trocados (b * a no lugar de a * b), enquanto as atribuições combinadas são
formas abreviadas que combinam um operador infixo com a atribuição
a uma variável (a = a * b torna-se a *= b). O capítulo 13 explica tanto
os operadores reversos quanto as atribuições combinadas em detalhes.
Por que len não é um método?
Fiz essa pergunta ao core developer Raymond Hettinger em 2013,e a chave de sua resposta foi
uma citação de The Zen of Python (https://www.python.org/doc/humor/#the-zen-of-python):
“practicality beats purity” (a praticidade supera a pureza). Na seção “Como os métodos
especiais são usados” na página 33, descrevo como len(x) é executado rapidamente
quando x é uma instância de um tipo embutido. Nenhum método é chamado para
os objetos embutidos do CPython: o tamanho é simplesmente lido de um campo
de uma struct em C. Obter a quantidade de itens de uma coleção é uma operação
comum e precisa funcionar de modo eficiente para tipos básicos e tão diversificados
como str, list, memoryview e assim por diante.
40
Python Fluente
Em outras palavras, len não é chamado como um método porque ele recebe um
tratamento especial como parte do modelo de dados do Python, assim como abs.
Porém, graças ao método especial __len__, você pode fazer len funcionar também
com seus objetos personalizados. Esse é um compromisso justo entre a necessidade de ter objetos embutidos eficientes e a consistência da linguagem. Também de
The Zen of Python: “Special cases aren’t special enough to break the rules” (Casos
especiais não são especiais o suficiente para infringir as regras).
Se pensarmos em abs e em len como operadores unários, poderemos nos
sentir mais inclinados a desculpar sua aparência funcional, em oposição
à sintaxe da chamada de método esperada em uma linguagem orientada
a objetos. De fato, a linguagem ABC – ancestral direta de Python que foi
pioneira em muitas de suas funcionalidades – tinha um operador # que
era equivalente a len (escrito como #s). Quando usado como operador
infixo, escrito como x#s, as ocorrências de x em s eram contadas, o que,
em Python, fazemos com s.count(x) para qualquer sequência s.
Resumo do capítulo
Implementando métodos especiais, seus objetos podem se comportar como os tipos
embutidos, possibilitando o estilo expressivo de programação considerado pythônico
pela comunidade.
Um requisito básico para um objeto Python é produzir representações úteis de si
mesmo em forma de string, uma para debugging ou logging e outra para a apresentação aos usuários finais. É por isso que os métodos especiais __repr__ e __str__ fazem
parte do modelo de dados.
Emular sequências, conforme mostrado no exemplo com FrenchDeck, é uma das aplicações mais comuns dos métodos especiais. Tirar o maior proveito dos tipos para
sequências é o assunto do capítulo 2, e a implementação de suas próprias sequências
será discutida no capítulo 10, em que criaremos uma extensão multidimensional da
classe Vector.
Graças à sobrecarga de operadores, Python oferece um conjunto rico de tipos numéricos que vão dos tipos embutidos até decimal.Decimal e fractions.Fraction; todos
aceitam operadores aritméticos infixos. A implementação de operadores, incluindo
os operadores reversos e as atribuições combinadas, será mostrada no capítulo 13
por meio de melhorias ao exemplo com Vector.
O uso e a implementação da maioria dos demais métodos especiais do modelo de
dados de Python serão discutidos ao longo deste livro.
Capítulo 1 ■ Modelo de dados do Python
41
Leituras complementares
O capítulo “Data Model” (Modelo de dados, http://docs.python.org/3/reference/datamodel.
html) de The Python Language Reference (Guia de referência à linguagem Python) é a
fonte canônica para o assunto deste capítulo e de boa parte deste livro.
O livro Python in a Nutshell, 2ª edição (O’Reilly, http://bit.ly/Python-IAN) de Alex Martelli
contém uma discussão excelente sobre o modelo de dados. Quando escrevi este livro,
a edição mais recente do livro Nutshell era de 2006 e focava o Python 2.5, porém houve
poucas mudanças no modelo de dados desde então, e a descrição de Martelli do funcionamento do acesso aos atributos é a mais bem fundamentada que já vi, sem considerar
o código-fonte C propriamente dito do CPython. Martelli também é um colaborador
assíduo do Stack Overflow, com mais de cinco mil respostas postadas. Consulte o perfil
de seu usuário no Stack Overflow (http://stackoverflow.com/users/95810/alex-martelli).
David Beazley escreveu dois livros que abordam o modelo de dados em detalhes
no contexto do Python 3 – Python Essential Reference, 4ª edição (Addison-Wesley
Professional) e Python Cookbook, 3ª edição (O’Reilly)3 –, este último, escrito em conjunto com Brian K. Jones.
O livro The Art of the Metaobject Protocol (AMOP, MIT Press), de Gregor Kiczales, Jim
des Rivieres e Daniel G. Bobrow, explica o conceito de MOP (Metaobject Protocol,
ou Protocolo de Metaobjetos), do qual o modelo de dados do Python é um exemplo.
Ponto de vista
Modelo de dados ou modelo de objetos?
O que a documentação do Python chama de Python data model (“Modelo de
dados do Python”) a maioria dos autores chama de Python object model (“Modelo
de objetos do Python”). Os livros Python in a Nutshell, 2ª edição, de Alex Martelli,
e Python Essential Reference, 4ª edição, de David Beazley, são os melhores livros que
discutem o “modelo de dados do Python”, porém eles sempre se referem a ele como
o “modelo de objetos”. Na Wikipedia, a primeira definição de modelo de objetos
(http://en.wikipedia.org/wiki/Object_model) é: “The properties of objects in general in a
specific computer programming language.” (As propriedades dos objetos em geral
em uma linguagem específica de programação de computadores.) É a isso que
o “modelo de dados do Python” se refere. Neste livro, usarei “modelo de dados”
porque a documentação favorece esse termo ao referir-se ao modelo de objetos
3 N.T.: Tradução brasileira publicada pela Novatec (http://novatec.com.br/livros/python-cookbook/).
42
Python Fluente
do Python e porque é o título do capítulo de The Python Language Reference (Guia
de referência à linguagem Python, https://docs.python.org/3/reference/datamodel.html),
mais relevante às nossas discussões.
Métodos mágicos
A comunidade Ruby chama seu equivalente aos métodos especiais de métodos
mágicos (magic methods). Muitas pessoas da comunidade Python adotam esse
termo também. Acredito que os métodos especiais, na realidade, são o oposto de
mágicos. O Python e o Ruby são iguais quanto a este aspecto: ambos oferecem aos
usuários um protocolo rico de metaobjetos que não é mágico, mas permite aos
usuários tirar proveito das mesmas ferramentas disponíveis aos core developers.
Para comparar, considere o JavaScript. Os objetos nessa linguagem têm recursos
que são mágicos no sentido que não é possível emulá-los nos objetos definidos
pelo próprio usuário. Por exemplo, antes do JavaScript 1.8.5, não era possível definir
atributos somente para leitura (read-only) em seus objetos JavaScript, porém alguns
objetos embutidos sempre tinham atributos somente para leitura. Em JavaScript,
os atributos somente para leitura eram “mágicos”, exigindo poderes sobrenaturais
que um usuário da linguagem não tinha até surgir o ECMAScript 5.1 em 2009. O
protocolo de metaobjetos do JavaScript está evoluindo, porém, historicamente, tem
sido mais limitado do que aquele usado pelo Python e pelo Ruby.
Metaobjetos
The Art of the Metaobject Protocol (AMOP) é o meu título favorito entre os livros de
computação. Falando de modo menos subjetivo, o termo protocolo de metaobjetos é
útil para pensarmos no modelo de dados do Python e em recursos semelhantes de
outras linguagens. A palavra metaobjeto diz respeito aos objetos que constituem os
blocos de construção da própria linguagem. Nesse contexto, protocolo é sinônimo
de interface. Portanto protocolo de metaobjetos é um sinônimo elegante para modelo
de objetos: uma API para as construções essenciais da linguagem.
Um protocolo de metaobjetos rico permite estender uma linguagem para que
ela aceite novos paradigmas de programação. Gregor Kiczales, o primeiro autor
do livro AMOP, posteriormente tornou-se pioneiro da programação orientada a
aspectos e o primeiro autor do AspectJ, uma extensão de Java que implementa
esse paradigma. A programação orientada a aspectos é muito mais simples de
implementar em uma linguagem dinâmica como o Python, e diversos frameworks
fazem isso, porém o mais importante é o zope.interface, que será brevemente discutido na seção “Leituras complementares” na página 389 do capítulo 11.
Download