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.