You are on page 1of 229

1

Captulo 1: A jornada do programa


O objetivo deste livro ensinar a pensar como um cientista da computao. Esta forma de pensar
combina algumas das melhores caractersticas da matemtica, da engenharia e das cincias naturais.
Assim como os matemticos, os cientistas da computao usam linguagens formais para denotar ideias
(especificamente operaes de computao). Como engenheiros, eles projetam coisas, reunindo
componentes em sistemas e avaliando as opes de melhor retorno entre as alternativas disposio.
Como cientistas, observam o comportamento de sistemas complexos, formam hipteses e testam
previses.
A habilidade especfica mais importante de um cientista da computao a resoluo de problemas.
Resoluo de problemas significa a capacidade de formular problemas, pensar criativamente em
solues e expressar uma soluo de forma clara e precisa. Assim, o processo de aprender a programar
uma oportunidade excelente para exercitar a habilidade de resolver problemas. por isso que este
captulo se chama A jornada do programa.
Em um nvel voc aprender a programar, uma habilidade til por si mesma. Em outro nvel usar a
programao como um meio para um fim. Conforme avanarmos, este fim ficar mais claro.

1.1 - O que um programa?


Um programa uma sequncia de instrues que especifica como executar uma operao de
computao. A operao de computao pode ser algo matemtico, como solucionar um sistema de
equaes ou encontrar as razes de um polinmio, mas tambm pode ser uma operao de computao
simblica, como a busca e a substituio de textos em um documento; ou algo grfico, como o
processamento de uma imagem ou a reproduo de um vdeo.
Os detalhes parecem diferentes em linguagens diferentes, mas algumas instrues bsicas aparecem em
quase todas as linguagens:
entrada
Receber dados do teclado, de um arquivo, da rede ou de algum outro dispositivo.
sada
Exibir dados na tela, salv-los em um arquivo, envi-los pela rede etc.
matemtica
Executar operaes matemticas bsicas como adio e multiplicao.
execuo condicional
Verificar a existncia de certas condies e executar o cdigo adequado.
repetio
Executar vrias vezes alguma ao, normalmente com algumas variaes.

Acredite ou no, isto basicamente tudo o que preciso saber. Cada programa que voc j usou,
complicado ou no, composto de instrues muito parecidas com essas. Podemos ento chegar
2

concluso de que programar o processo de quebrar uma tarefa grande e complexa em subtarefas cada
vez menores, at que estas sejam simples o suficiente para serem executadas por uma dessas instrues
bsicas.

1.2 - Execuo do Python


Um dos desafios de comear a usar Python ter que instalar no seu computador o prprio programa e
outros relacionados. Se tiver familiaridade com o seu sistema operacional, e especialmente se no tiver
problemas com a interface de linha de comando, voc no ter dificuldade para instalar o Python. Mas
para principiantes pode ser trabalhoso aprender sobre administrao de sistemas e programao ao
mesmo tempo.
Para evitar esse problema, recomendo que comece a executar o Python em um navegador. Depois,
quando voc j conhecer o Python um pouco mais, darei sugestes para instal-lo em seu computador.
H uma srie de sites que ajudam a usar e executar o Python. Se j tem um favorito, v em frente e use-
o. Seno, recomendo o PythonAnywhere. Apresento instrues detalhadas sobre os primeiros passos no
link http://tinyurl.com/thinkpython2e.
H duas verses do Python, o Python 2 e o Python 3. Como elas so muito semelhantes, se voc
aprender uma verso, fcil trocar para a outra. Como iniciante, voc encontrar poucas diferenas.
Este livro foi escrito para o Python 3, mas tambm inclu algumas notas sobre o Python 2.
O interpretador do Python um programa que l e executa o cdigo Python. Dependendo do seu
ambiente, possvel iniciar o interpretador clicando em um cone, ou digitando python em uma linha
de comando. Quando ele iniciar, voc dever ver uma sada como esta:
Python 3.4.0 (default, Jun 19 2015, 14:20:21)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

As trs primeiras linhas contm informaes sobre o interpretador e o sistema operacional em que est
sendo executado, portanto podem ser diferentes para voc. Mas preciso conferir se o nmero da
verso, que 3.4.0 neste exemplo, comea com 3, o que indica que voc est executando o Python 3.
Se comear com 2, voc est executando (adivinhe!) o Python 2.
A ltima linha um prompt indicando que o interpretador est pronto para voc digitar o cdigo. Se
digitar uma linha de cdigo e pressionar Enter, o interpretador exibe o resultado:
>>> 1 + 1
2

Agora voc est pronto para comear. Daqui em diante, vou supor que voc sabe como inicializar o
interpretador do Python e executar o cdigo.
3

1.3 - O primeiro programa


Tradicionalmente, o primeiro programa que se escreve em uma nova linguagem chama-se Hello,
World!, porque tudo o que faz exibir as palavras Hello, World! na tela. No Python, ele se parece
com isto:
>>> print('Hello, World!')

Este um exemplo de uma instruo print (instruo de impresso), embora na realidade ela no
imprima nada em papel. Ela exibe um resultado na tela. Nesse caso, o resultado so as palavras:
Hello, World!

As aspas apenas marcam o comeo e o fim do texto a ser exibido; elas no aparecem no resultado.
Os parnteses indicam que o print uma funo. Veremos funes no Captulo 3.
No Python 2, a instruo print ligeiramente diferente; ela no uma funo, portanto no usa
parnteses.
>>> print 'Hello, World!'

Esta distino far mais sentido em breve, mas isso o suficiente para comear.

1.4 - Operadores aritmticos


Depois do Hello, World, o prximo passo a aritmtica. O Python tem operadores, que so smbolos
especiais representando operaes de computao, como adio e multiplicao.
Os operadores +, - e * executam a adio, a subtrao e a multiplicao, como nos seguintes exemplos:
>>> 40 + 2
42
>>> 43 - 1
42
>>> 6 * 7
42
O operador / executa a diviso:
>>> 84 / 2
42.0
Pode ser que voc fique intrigado pelo resultado ser 42.0 em vez de 42. Vou
explicar isso na prxima seo.
Finalmente, o operador ** executa a exponenciao; isto , eleva um nmero a uma
potncia:
>>> 6 ** 2 + 6
42
Em algumas outras linguagens, o ^ usado para a exponenciao, mas no Python um
operador bitwise, chamado XOR. Se no tiver familiaridade com operadores bitwise, o
resultado o surpreender:
>>> 6 ^ 2
4
4

No abordarei operadores bitwise neste livro, mas voc pode ler sobre eles em
http://wiki.python.org/moin/BitwiseOperators.

1.5 - Valores e tipos


Um valor uma das coisas bsicas com as quais um programa trabalha, como uma letra ou um nmero.
Alguns valores que vimos at agora foram 2, 42.0 e Hello, World!.
Esses valores pertencem a tipos diferentes: 2 um nmero inteiro, 42.0 um nmero de ponto
flutuante e Hello, World! uma string, assim chamada porque as letras que contm esto em uma
sequncia em cadeia.
Se no tiver certeza sobre qual o tipo de certo valor, o interpretador pode dizer isso a voc:
>>> type(2)
<class 'int'>
>>> type(42.0)
<class 'float'>
>>> type('Hello, World!')
<class 'str'>

Nesses resultados, a palavra class [classe] usada no sentido de categoria; um tipo uma categoria
de valores.
Como se poderia esperar, nmeros inteiros pertencem ao tipo int, strings pertencem ao tipo str e os
nmeros de ponto flutuante pertencem ao tipo float.
E valores como 2 e 42.0? Parecem nmeros, mas esto entre aspas como se fossem strings:
>>> type('2')
<class 'str'>
>>> type('42.0')
<class 'str'>

Ento so strings.
Ao digitar um nmero inteiro grande, alguns podem usar a notao americana, com vrgulas entre
grupos de dgitos, como em 1,000,000. Este no um nmero inteiro legtimo no Python e resultar
em:
>>> 1,000,000
(1, 0, 0)

O que no de modo algum o que espervamos! O Python interpreta 1,000,000 como uma sequncia
de nmeros inteiros separados por vrgulas. Aprenderemos mais sobre este tipo de sequncia mais
adiante.
5

1.6 - Linguagens formais e naturais


As linguagens naturais so os idiomas que as pessoas falam, como ingls, espanhol e francs. Elas no
foram criadas pelas pessoas (embora as pessoas tentem impor certa ordem a elas); desenvolveram-se
naturalmente.
As linguagens formais so linguagens criadas pelas pessoas para aplicaes especficas. Por exemplo, a
notao que os matemticos usam uma linguagem formal especialmente boa para denotar relaes
entre nmeros e smbolos. Os qumicos usam uma linguagem formal para representar a estrutura
qumica de molculas. E o mais importante:
As linguagens de programao so idiomas formais criados para expressar operaes de computao.
As linguagens formais geralmente tm regras de sintaxe estritas que governam a estrutura de
declaraes. Por exemplo, na matemtica a declarao 3 + 3 = 6 tem uma sintaxe correta, mas no 3 +
= 3$6. Na qumica, H2O uma frmula sintaticamente correta, mas 2Zz no .
As regras de sintaxe vm em duas categorias relativas a smbolos e estrutura. Os smbolos so os
elementos bsicos da linguagem, como palavras, nmeros e elementos qumicos. Um dos problemas
com 3 + = 3$6 que o $ no um smbolo legtimo na matemtica (pelo menos at onde eu sei). De
forma similar, 2Zz no legtimo porque no h nenhum elemento com a abreviatura Zz.
O segundo tipo de regra de sintaxe refere-se ao modo no qual os smbolos so combinados. A equao
3 + = 3 no legtima porque, embora + e = sejam smbolos legtimos, no se pode ter um na sequncia
do outro. De forma similar, em uma frmula qumica o subscrito vem depois do nome de elemento, no
antes.
Esta um@ frase bem estruturada em portugu$, mas com s*mbolos invlidos. Esta frase todos os
smbolos vlidos tem, mas estrutura vlida sem.
Ao ler uma frase em portugus ou uma declarao em uma linguagem formal, preciso compreender a
estrutura (embora em uma linguagem natural voc faa isto de forma subconsciente). Este processo
chamado de anlise.
Embora as linguagens formais e naturais tenham muitas caractersticas em comum smbolos,
estrutura e sintaxe h algumas diferenas:
ambiguidade
As linguagens naturais so cheias de ambiguidade e as pessoas lidam com isso usando pistas
contextuais e outras informaes. As linguagens formais so criadas para ser quase ou
completamente inequvocas, ou seja, qualquer afirmao tem exatamente um significado,
independentemente do contexto.
redundncia
Para compensar a ambiguidade e reduzir equvocos, as linguagens naturais usam muita
redundncia. Por causa disso, muitas vezes so verborrgicas. As linguagens formais so menos
redundantes e mais concisas.
6

literalidade
As linguagens naturais so cheias de expresses e metforas. Se eu digo Caiu a ficha,
provavelmente no h ficha nenhuma na histria, nem nada que tenha cado (esta uma
expresso para dizer que algum entendeu algo depois de certo perodo de confuso). As
linguagens formais tm significados exatamente iguais ao que expressam.

Como todos ns crescemos falando linguagens naturais, s vezes difcil se ajustar a linguagens
formais. A diferena entre a linguagem natural e a formal semelhante diferena entre poesia e prosa,
mas vai alm:
Poesia
As palavras so usadas tanto pelos sons como pelos significados, e o poema inteiro cria um efeito
ou resposta emocional. A ambiguidade no apenas comum, mas muitas vezes proposital.
Prosa
O significado literal das palavras o mais importante e a estrutura contribui para este significado.
A prosa mais acessvel anlise que a poesia, mas muitas vezes ainda ambgua.
Programas
A significado de um programa de computador inequvoco e literal e pode ser entendido
inteiramente pela anlise dos smbolos e da estrutura.

As linguagens formais so mais densas que as naturais, ento exigem mais tempo para a leitura. Alm
disso, a estrutura importante, ento nem sempre melhor ler de cima para baixo e da esquerda para a
direita. Em vez disso, aprenda a analisar o programa primeiro, identificando os smbolos e
interpretando a estrutura. E os detalhes fazem diferena. Pequenos erros em ortografia e pontuao, que
podem no importar tanto nas linguagens naturais, podem fazer uma grande diferena em uma lngua
formal.

1.7 - Depurao
Os programadores erram. Por um capricho do destino, erros de programao so chamados de bugs
(insetos) e o processo de rastre-los chama-se depurao (debugging).
Programar, e especialmente fazer a depurao, s vezes traz emoes fortes. Se tiver dificuldade com
certo bug, voc pode ficar zangado, desesperado ou constrangido.
H evidncias de que as pessoas respondem naturalmente a computadores como se fossem pessoas.
Quando funcionam bem, pensamos neles como parceiros da equipe, e quando so teimosos ou
grosseiros, respondemos a eles do mesmo jeito que fazemos com pessoas grosseiras e teimosas (Reeves
e Nass, The Media Equation: How People Treat Computers, Television, and New Media Like Real
People and Places A equao da mdia: como as pessoas tratam os computadores, a televiso e as
novas mdias como se fossem pessoas e lugares reais).
7

Prepare-se para essas reaes, pois isso pode ajudar a lidar com elas. Uma abordagem pensar no
computador como um funcionrio com certas vantagens, como velocidade e preciso, e certas
desvantagens, como a falta de empatia e a incapacidade de compreender um contexto mais amplo.
Seu trabalho ser um bom gerente: encontrar formas de aproveitar as vantagens e atenuar as
desvantagens. E tambm encontrar formas de usar suas emoes para lidar com o problema sem deixar
suas reaes interferirem na sua capacidade de trabalho.
Aprender a depurar erros pode ser frustrante, mas uma habilidade valiosa, til para muitas atividades
alm da programao. No fim de cada captulo h uma seo como esta, com as minhas sugestes para
fazer a depurao. Espero que sejam teis!

1.8 - Glossrio
resoluo de problemas
O processo de formular um problema, encontrar uma soluo e express-la.
linguagem de alto nvel
Uma linguagem de programao como Python, que foi criada com o intuito de ser fcil para os
humanos escreverem e lerem.
linguagem de baixo nvel
Uma linguagem de programao criada para o computador executar com facilidade; tambm
chamada de linguagem de mquina ou linguagem assembly.
portabilidade
A propriedade de um programa de poder ser executado em mais de um tipo de computador.
interpretador
Um programa que l outro programa e o executa.
prompt
Caracteres expostos pelo interpretador para indicar que est pronto para receber entradas do
usurio.
programa
Conjunto de instrues que especificam uma operao de computao.
instruo print
Uma instruo que faz o interpretador do Python exibir um valor na tela.
operador
Um smbolo especial que representa uma operao de computao simples como adio,
multiplicao ou concatenao de strings.
valor
Uma das unidades bsicas de dados, como um nmero ou string, que um programa manipula.
tipo
Uma categoria de valores. Os tipos que vimos por enquanto so nmeros inteiros (tipo int),
nmeros de ponto flutuante (tipo float) e strings (tipo str).
inteiro
Um tipo que representa nmeros inteiros.
ponto flutuante
Um tipo que representa nmeros com partes fracionrias.
string
8

Um tipo que representa sequncias de caracteres.


linguagem natural
Qualquer linguagem que as pessoas falam e que se desenvolveu naturalmente.
linguagem formal
Qualquer linguagem que as pessoas criaram com objetivos especficos, como representar ideias
matemticas ou programas de computador; todas as linguagens de programao so linguagens
formais.
smbolo
Um dos elementos bsicos da estrutura sinttica de um programa, anlogo a uma palavra em
linguagem natural.
sintaxe
As regras que governam a estrutura de um programa.
anlise
Examinar um programa e sua estrutura sinttica.
bug
Um erro em um programa.
depurao
O processo de encontrar e corrigir (depurar) bugs.

1.9 - Exerccios
Exerccio 1.1
uma boa ideia ler este livro em frente a um computador para testar os exemplos durante a leitura.
Sempre que estiver testando um novo recurso, voc deve tentar fazer erros. Por exemplo, no programa
Hello, World!, o que acontece se omitir uma das aspas? E se omitir ambas? E se voc soletrar a
instruo print de forma errada?
Este tipo de experimento ajuda a lembrar o que foi lido; tambm ajuda quando voc estiver
programando, porque assim conhecer o significado das mensagens de erro. melhor fazer erros agora
e de propsito que depois e acidentalmente.
1. Em uma instruo print, o que acontece se voc omitir um dos parnteses ou ambos?
2. Se estiver tentando imprimir uma string, o que acontece se omitir uma das aspas ou ambas?
3. Voc pode usar um sinal de menos para fazer um nmero negativo como -2. O que acontece se
puser um sinal de mais antes de um nmero? E se escrever assim: 2++2?
4. Na notao matemtica, zeros esquerda so aceitveis, como em 02. O que acontece se voc
tentar usar isso no Python?
5. O que acontece se voc tiver dois valores sem nenhum operador entre eles?
9

Exerccio 1.2
Inicialize o interpretador do Python e use-o como uma calculadora.
1. Quantos segundos h em 42 minutos e 42 segundos?
2. Quantas milhas h em 10 quilmetros? Dica: uma milha equivale a 1,61 quilmetro.
3. Se voc correr 10 quilmetros em 42 minutos e 42 segundos, qual o seu passo mdio (tempo
por milha em minutos e segundos)? Qual a sua velocidade mdia em milhas por hora?

Captulo 2: Variveis, expresses e instrues


Um dos recursos mais eficientes de uma linguagem de programao a capacidade de manipular
variveis. Uma varivel um nome que se refere a um valor.

2.1 - Instrues de atribuio


Uma instruo de atribuio cria uma nova varivel e d um valor a ela:
>>> message = 'And now for something completely different'
>>> n = 17
>>> pi = 3.141592653589793

Esse exemplo faz trs atribuies. A primeira atribui uma string a uma nova varivel chamada message;
a segunda d o nmero inteiro 17 a n; a terceira atribui o valor (aproximado) de a pi.
Uma forma comum de representar variveis por escrito colocar o nome com uma flecha apontando
para o seu valor. Este tipo de nmero chamado de diagrama de estado porque mostra o estado no qual
cada uma das variveis est (pense nele como o estado de esprito da varivel). A Figura 2.1 mostra o
resultado do exemplo anterior.

Figura 2.1 Diagrama de estado.

2.2 - Nomes de variveis


Os programadores geralmente escolhem nomes significativos para as suas variveis eles documentam
o uso da varivel.
10

Nomes de variveis podem ser to longos quanto voc queira. Podem conter tanto letras como
nmeros, mas no podem comear com um nmero. legal usar letras maisculas, mas a conveno
usar apenas letras minsculas para nomes de variveis.
O caractere de sublinhar (_) pode aparecer em um nome. Muitas vezes usado em nomes com vrias
palavras, como your_name ou airspeed_of_unladen_swallow.
Se voc der um nome ilegal a uma varivel, recebe um erro de sintaxe:
>>> 76trombones = 'big parade'
SyntaxError: invalid syntax
>>> more@ = 1000000
SyntaxError: invalid syntax
>>> class = 'Advanced Theoretical Zymurgy'
SyntaxError: invalid syntax

76trombones ilegal porque comea com um nmero. more@ ilegal porque contm um caractere
ilegal, o @. Mas o que h de errado com class?

A questo que class uma das palavras-chave do Python. O interpretador usa palavras-chave para
reconhecer a estrutura do programa e elas no podem ser usadas como nomes de varivel.
O Python 3 tem estas palavras-chave:
and del from None True
as elif global nonlocal try
assert else if not while
break except import or with
class False in pass yield
continue finally is raise
def for lambda return

Voc no precisa memorizar essa lista. Na maior parte dos ambientes de desenvolvimento, as palavras-
chave so exibidas em uma cor diferente; se voc tentar usar uma como nome de varivel, vai perceber.

2.3 - Expresses e instrues


Uma expresso uma combinao de valores, variveis e operadores. Um valor por si mesmo
considerado uma expresso, assim como uma varivel, portanto as expresses seguintes so todas
legais:
>>> 42
42
>>> n
17
>>> n + 25
42

Quando voc digita uma expresso no prompt, o interpretador a avalia, ou seja, ele encontra o valor da
expresso. Neste exemplo, o n tem o valor 17 e n + 25 tem o valor 42.
11

Uma instruo uma unidade de cdigo que tem um efeito, como criar uma varivel ou exibir um
valor.
>>> n = 17
>>> print(n)

A primeira linha uma instruo de atribuio que d um valor a n. A segunda linha uma instruo de
exibio que exibe o valor de n.
Quando voc digita uma instruo, o interpretador a executa, o que significa que ele faz o que a
instruo diz. Em geral, instrues no tm valores.

2.4 - Modo script


At agora executamos o Python no modo interativo, no qual voc interage diretamente com o
interpretador. O modo interativo uma boa forma de comear, mas se estiver trabalhando com mais do
que algumas linhas do cdigo, o processo pode ficar desorganizado.
A alternativa salvar o cdigo em um arquivo chamado script e ento executar o interpretador no modo
script para execut-lo. Por conveno, os scripts no Python tm nomes que terminam com .py.
Se souber como criar e executar um script no seu computador, voc est pronto. Seno, recomendo usar
o PythonAnywhere novamente. Inseri instrues sobre como executar programas no modo script em
http://tinyurl.com/thinkpython2e.
Como o Python oferece os dois modos, voc pode testar pedaos do cdigo no modo interativo antes de
coloc-los em um script. Mas h diferenas entre o modo interativo e o modo script que podem
confundir as pessoas.
Por exemplo, se estiver usando o Python como uma calculadora, voc poderia digitar:
>>> miles = 26.2
>>> miles * 1.61
42.182

A primeira linha atribui um valor a miles, mas no tem efeito visvel. A segunda linha uma expresso,
ento o interpretador a avalia e exibe o resultado. No fim, chega-se ao resultado de que uma maratona
tem aproximadamente 42 quilmetros.
Mas se voc digitar o mesmo cdigo em um script e execut-lo, no recebe nenhuma sada. Uma
expresso, por conta prpria, no tem efeito visvel no modo script. O Python, na verdade, avalia a
expresso, mas no exibe o valor a menos que voc especifique:
miles = 26.2
print(miles * 1.61)

Este comportamento pode confundir um pouco no incio.


12

Um script normalmente contm uma sequncia de instrues. Se houver mais de uma instruo, os
resultados aparecem um aps o outro, conforme as instrues sejam executadas.
Por exemplo, o script
print(1)
x = 2
print(x)

produz a sada
1
2

A instruo de atribuio no produz nenhuma sada.


Para verificar sua compreenso, digite as seguintes instrues no interpretador do Python e veja o que
fazem:
5
x = 5
x + 1

Agora ponha as mesmas instrues em um script e o execute. Qual a sada? Altere o script
transformando cada expresso em uma instruo de exibio e ento o execute novamente.

2.5 - Ordem das operaes


Quando uma expresso contm mais de um operador, a ordem da avaliao depende da ordem das
operaes. Para operadores matemticos, o Python segue a conveno matemtica. O acrnimo
PEMDAS pode ser til para lembrar das regras:
Os Parnteses tm a precedncia mais alta e podem ser usados para forar a avaliao de uma
expresso na ordem que voc quiser. Como as expresses em parnteses so avaliadas primeiro,
2 * (3-1) 4, e (1+1)**(5-2) 8. Tambm possvel usar parnteses para facilitar a
leitura de uma expresso, como no caso de (minute * 100) / 60, mesmo se o resultado
no for alterado.
A Exponenciao tem a prxima precedncia mais alta, ento 1 + 2**3 9, no 27, e 2 *
3**2 18, no 36.

A Multiplicao e a Diviso tm precedncia mais alta que a Adio e a Subtrao. Assim, 2 *


3 - 1 5, no 4, e 6 + 4 / 2 8, no 5.

Os operadores com a mesma precedncia so avaliados da esquerda para a direita (exceto na


exponenciao). Assim, na expresso degrees / 2 * pi, a diviso acontece primeiro e o
13

resultado multiplicado por pi. Para dividir por 2, voc pode usar parnteses ou escrever
degrees / 2 / pi.

Eu no fico sempre tentando lembrar da precedncia de operadores. Se a expresso no estiver clara


primeira vista, uso parnteses para fazer isso.

2.6 - Operaes com strings


Em geral, no possvel executar operaes matemticas com strings, mesmo se elas parecerem
nmeros, ento coisas assim so ilegais:
'2'-'1' 'eggs'/'easy' 'third'*'a charm'

Mas h duas excees, + e *.

O operador + executa uma concatenao de strings, ou seja, une as strings pelas extremidades. Por
exemplo:
>>> first = 'throat'
>>> second = 'warbler'
>>> first + second
throatwarbler

O operador * tambm funciona em strings; ele executa a repetio. Por exemplo, Spam*3
SpamSpamSpam. Se um dos valores for uma string, o outro tem de ser um nmero inteiro.
Este uso de + e * faz sentido por analogia com a adio e a multiplicao. Tal como 4 * 3
equivalente a 4 + 4 + 4, esperamos que 'Spam' * 3 seja o mesmo que Spam+Spam+Spam,
e assim . Por outro lado, h uma diferena significativa entre a concatenao de strings e a repetio
em relao adio e multiplicao de nmeros inteiros. Voc consegue pensar em uma propriedade
que a adio tem, mas a concatenao de strings no tem?

2.7 - Comentrios
Conforme os programas ficam maiores e mais complicados, eles so mais difceis de ler. As linguagens
formais so densas e muitas vezes difcil ver um pedao de cdigo e compreender o que ele faz ou
por que faz isso.
Por essa razo, uma boa ideia acrescentar notas aos seus programas para explicar em linguagem
natural o que o programa est fazendo. Essas notas so chamadas de comentrios, e comeam com o
smbolo #:
# computa a percentagem da hora que passou
percentage = (minute * 100) / 60
14

Nesse caso, o comentrio aparece sozinho em uma linha. Voc tambm pode pr comentrios no fim
das linhas:
percentage = (minute * 100) / 60 # percentagem de uma hora

Tudo do # ao fim da linha ignorado no tem efeito na execuo do programa.

Os comentrios tornam-se mais teis quando documentam algo no cdigo que no est bvio. Podemos
supor que o leitor compreenda o que o cdigo faz; assim, mais til explicar porque faz o que faz.
Este comentrio redundante em relao ao cdigo, alm de intil:
v = 5 # atribui 5 a v

Este comentrio contm informaes teis que no esto no cdigo:


v = 5 # velocidade em metros/segundo.

Bons nomes de variveis podem reduzir a necessidade de comentrios, mas nomes longos podem
tornar expresses complexas difceis de ler, ento preciso analisar o que vale mais a pena.

2.8 - Depurao
H trs tipos de erros que podem ocorrer em um programa: erros de sintaxe, erros de tempo de
execuo e erros semnticos. til distinguir entre eles para rastre-los mais rapidamente.
Erro de sintaxe
A sintaxe refere-se estrutura de um programa e suas respectivas regras. Por exemplo, os
parnteses devem vir em pares correspondentes, ento (1 + 2) legal, mas 8) um erro de
sintaxe.
Se houver um erro de sintaxe em algum lugar no seu programa, o Python exibe uma mensagem
de erro e para, e no ser possvel executar o programa. Nas primeiras poucas semanas da sua
carreira em programao, voc pode passar muito tempo rastreando erros de sintaxe. Ao adquirir
experincia, voc far menos erros e os encontrar mais rpido.
Erro de tempo de execuo
O segundo tipo de erro o erro de tempo de execuo, assim chamado porque o erro no aparece
at que o programa seja executado. Esses erros tambm se chamam de excees porque
normalmente indicam que algo excepcional (e ruim) aconteceu.
Os erros de tempo de execuo so raros nos programas simples que veremos nos primeiros
captulos, ento pode demorar um pouco at voc encontrar algum.
Erro semntico
O terceiro tipo do erro semntico, ou seja, relacionado ao significado. Se houver um erro
semntico no seu programa, ele ser executado sem gerar mensagens de erro, mas no vai fazer a
coisa certa. Vai fazer algo diferente. Especificamente, vai fazer o que voc disser para fazer.
Identificar erros semnticos pode ser complicado, porque preciso trabalhar de trs para a frente,
vendo a sada do programa e tentando compreender o que ele est fazendo.
15

2.9 - Glossrio
varivel
Um nome que se refere a um valor.
atribuio
Uma instruo que atribui um valor a uma varivel.
diagrama de estado
Uma representao grfica de um grupo de variveis e os valores a que se referem.
palavra-chave
Uma palavra reservada, usada para analisar um programa; no possvel usar palavras-chave
como if, def e while como nomes de variveis.
operando
Um dos valores que um operador produz.
expresso
Uma combinao de variveis, operadores e valores que representa um resultado nico.
avaliar
Simplificar uma expresso executando as operaes para produzir um valor nico.
instruo
Uma seo do cdigo que representa um comando ou ao. Por enquanto, as instrues que
vimos so instrues de atribuies e de exibio.
executar
Executar uma instruo para fazer o que ela diz.
modo interativo
Um modo de usar o interpretador do Python, digitando o cdigo no prompt.
modo script
Um modo de usar o interpretador do Python para ler cdigo em um script e execut-lo.
script
Um programa armazenado em um arquivo.
ordem das operaes
As regras que governam a ordem na qual as expresses que envolvem vrios operadores e
operandos so avaliadas.
concatenar
Juntar dois operandos pelas extremidades.
comentrios
Informaes em um programa destinadas a outros programadores (ou qualquer pessoa que leia o
texto fonte) que no tm efeito sobre a execuo do programa.
erro de sintaxe
Um erro em um programa que torna sua anlise impossvel (e por isso impossvel de interpretar).
exceo
Um erro que se descobre quando o programa executado.
semntica
O significado de um programa.
erro semntico
Um erro que faz com que um programa faa algo diferente do que o programador pretendia.
16

2.10 - Exerccios
Exerccio 2.1
Repetindo o meu conselho do captulo anterior, sempre que voc aprender um recurso novo, voc deve
test-lo no modo interativo e fazer erros de propsito para ver o que acontece.
Vimos que n = 42 legal. E 42 = n?

Ou x = y = 1?

Em algumas linguagens, cada instruo termina em um ponto e vrgula ;. O que acontece se


voc puser um ponto e vrgula no fim de uma instruo no Python?
E se puser um ponto no fim de uma instruo?

Em notao matemtica possvel multiplicar x e y desta forma: xy. O que acontece se voc
tentar fazer o mesmo no Python?

Exerccio 2.2
Pratique o uso do interpretador do Python como uma calculadora:

1. O volume de uma esfera com raio r . Qual o volume de uma esfera com raio 5?
2. Suponha que o preo de capa de um livro seja R$ 24,95, mas as livrarias recebem um desconto
de 40%. O transporte custa R$ 3,00 para o primeiro exemplar e 75 centavos para cada exemplar
adicional. Qual o custo total de atacado para 60 cpias?
3. Se eu sair da minha casa s 6:52 e correr 1 quilmetro a um certo passo (8min15s por
quilmetro), ento 3 quilmetros a um passo mais rpido (7min12s por quilmetro) e 1
quilmetro no mesmo passo usado em primeiro lugar, que horas chego em casa para o caf da
manh?

Captulo 3: Funes
No contexto da programao, uma funo uma sequncia nomeada de instrues que executa uma
operao de computao. Ao definir uma funo, voc especifica o nome e a sequncia de instrues.
Depois, pode chamar a funo pelo nome.

3.1 - Chamada de funo


J vimos um exemplo de chamada de funo:
>>> type(42)
17

<class 'int'>

O nome da funo type. A expresso entre parnteses chamada de argumento da funo. Para esta
funo, o resultado o tipo do argumento.
comum dizer que uma funo recebe um argumento e retorna um resultado. O resultado tambm
chamado de valor de retorno.
O Python oferece funes que convertem valores de um tipo em outro. A funo int recebe qualquer
valor e o converte em um nmero inteiro, se for possvel, ou declara que h um erro:
>>> int('32')
32
>>> int('Hello')
ValueError: invalid literal for int(): Hello

int pode converter valores de ponto flutuante em nmeros inteiros, mas no faz arredondamentos; ela
apenas corta a parte da frao:
>>> int(3.99999)
3
>>> int(-2.3)
-2

float converte nmeros inteiros e strings em nmeros de ponto flutuante:


>>> float(32)
32.0
>>> float('3.14159')
3.14159

Finalmente, str converte o argumento em uma string:


>>> str(32)
'32'
>>> str(3.14159)
'3.14159'

3.2 - Funes matemticas


O Python tem um mdulo matemtico que oferece a maioria das funes matemticas comuns. Um
mdulo um arquivo que contm uma coleo de funes relacionadas.
Antes que possamos usar as funes em um mdulo, precisamos import-lo com uma instruo de
importao:
>>> import math

Esta instruo cria um objeto de mdulo chamado math (matemtica). Ao se exibir o objeto de mdulo,
so apresentadas informaes sobre ele:
18

>>> math
<module 'math' (built-in)>

O objeto de mdulo contm as funes e variveis definidas no mdulo. Para acessar uma das funes,
preciso especificar o nome do mdulo e o nome da funo, separados por um ponto. Este formato
chamado de notao de ponto.
>>> ratio = signal_power / noise_power
>>> decibels = 10 * math.log10(ratio)
>>> radians = 0.7
>>> height = math.sin(radians)

O primeiro exemplo usa math.log10 para calcular a proporo de sinal e de rudo em decibis
(assumindo que signal_power e noise_power tenham sido definidos). O mdulo matemtico tambm
oferece a funo log, que calcula logaritmos de base e.
O segundo exemplo encontra o seno de radians. O nome da varivel indica que sin e outras funes
trigonomtricas (cos, tan etc.) recebem argumentos em radianos. Para converter graus em radianos,
divida por 180 e multiplique por :
>>> degrees = 45
>>> radians = degrees / 180.0 * math.pi
>>> math.sin(radians)
0.707106781187

A expresso math.pi recebe a varivel pi do mdulo matemtico. Seu valor uma aproximao de
ponto flutuante de , com preciso aproximada de 15 dgitos.
Se souber trigonometria, voc pode verificar o resultado anterior comparando-o com a raiz quadrada de
2 dividida por 2:
>>> math.sqrt(2) / 2.0
0.707106781187

3.3 - Composio
Por enquanto, falamos sobre os elementos de um programa variveis, expresses e instrues de
forma isolada, mas no sobre como combin-los.
Uma das caractersticas mais teis das linguagens de programao a sua capacidade de usar pequenos
blocos de montar para compor programas. Por exemplo, o argumento de uma funo pode ser qualquer
tipo de expresso, inclusive operadores aritmticos:
x = math.sin(degrees / 360.0 * 2 * math.pi)
E at chamadas de funo:
x = math.exp(math.log(x+1))
19

possvel colocar um valor, uma expresso arbitrria, em quase qualquer lugar. Com uma exceo: o
lado esquerdo de uma instruo de atribuio tem que ser um nome de varivel. Qualquer outra
expresso no lado esquerdo um erro de sintaxe (veremos excees a esta regra depois).
>>> minutes = hours * 60 # correto
>>> hours * 60 = minutes # errado!
SyntaxError: can't assign to operator

3.4 - Como acrescentar novas funes


Por enquanto, s usamos funes que vm com o Python, mas tambm possvel acrescentar novas
funes. Uma definio de funo especifica o nome de uma nova funo e a sequncia de instrues
que so executadas quando a funo chamada.
Aqui est um exemplo:
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print("I sleep all night and I work all day.")

def uma palavra-chave que indica uma definio de funo. O nome da funo print_lyrics.
As regras para nomes de funo so as mesmas que as das variveis: letras, nmeros e sublinhado so
legais, mas o primeiro caractere no pode ser um nmero. No podemos usar uma palavra-chave como
nome de uma funo e devemos evitar ter uma varivel e uma funo com o mesmo nome.
Os parnteses vazios depois do nome indicam que esta funo no usa argumentos.
A primeira linha da definio de funo chama-se cabealho; o resto chamado de corpo. O cabealho
precisa terminar em dois pontos e o corpo precisa ser endentado. Por conveno, a endentao sempre
de quatro espaos. O corpo pode conter qualquer nmero de instrues.
As strings nas instrues de exibio so limitadas por aspas duplas. As aspas simples e as aspas duplas
fazem a mesma coisa; a maior parte das pessoas usa aspas simples apenas nos casos em que aspas
simples (que tambm so apstrofes) aparecem na string.
Todas as aspas (simples e duplas) devem ser aspas retas, normalmente encontradas ao lado do Enter
no teclado. Aspas curvas, como as desta orao, no so legais no Python.
Se digitar uma definio de funo no modo interativo, o interpretador exibe pontos () para mostrar
que a definio no est completa:
>>> def print_lyrics():
... print("I'm a lumberjack, and I'm okay.")

print(I sleep all night and I work all day.)


...
20

Para terminar a funo, preciso inserir uma linha vazia.


A definio de uma funo cria um objeto de funo, que tem o tipo function:
>>> print(print_lyrics)
<function print_lyrics at 0xb7e99e9c>
>>> type(print_lyrics)
<class 'function'>

A sintaxe para chamar a nova funo a mesma que a das funes integradas:
>>> print_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.

Uma vez que a funo tenha sido definida, possvel us-la dentro de outra funo. Por exemplo, para
repetir o refro anterior, podemos escrever uma funo chamada repeat_lyrics:
def repeat_lyrics():
print_lyrics()
print_lyrics()

E da chamar repeat_lyrics:
>>> repeat_lyrics()
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
I'm a lumberjack, and I'm okay.
I sleep all night and I work all day.
Mas a cano no bem assim.

3.5 - Uso e definies


Juntando fragmentos de cdigo da seo anterior, o programa inteiro fica assim:
def print_lyrics():
print("I'm a lumberjack, and I'm okay.")
print("I sleep all night and I work all day.")

def repeat_lyrics():
print_lyrics()
print_lyrics()

repeat_lyrics()

Este programa contm duas definies de funo: print_lyrics e repeat_lyrics. As


definies de funo so executadas como outras instrues, mas o efeito criar objetos de funo. As
instrues dentro da funo no so executadas at que a funo seja chamada, e a definio de funo
no gera nenhuma sada.
21

Como poderamos esperar, preciso criar uma funo antes de execut-la. Em outras palavras, a
definio de funo tem que ser executada antes que a funo seja chamada.
Como exerccio, mova a ltima linha deste programa para o topo, para que a chamada de funo
aparea antes das definies. Execute o programa e veja qual a mensagem de erro que aparece.
Agora mova a chamada de funo de volta para baixo e mova a definio de print_lyrics para
depois da definio de repeat_lyrics. O que acontece quando este programa executado?

3.6 - Fluxo de execuo


Para garantir que uma funo seja definida antes do seu primeiro uso, preciso saber a ordem na qual
as instrues sero executadas. Isso chamado de fluxo de execuo.
A execuo sempre comea na primeira instruo do programa. As instrues so executadas uma aps
a outra, de cima para baixo.
As definies de funo no alteram o fluxo da execuo do programa, mas lembre-se de que as
instrues dentro da funo no so executadas at a funo ser chamada.
Uma chamada de funo como um desvio no fluxo de execuo. Em vez de ir prxima instruo, o
fluxo salta para o corpo da funo, executa as instrues l, e ento volta para continuar de onde parou.
Parece bastante simples, at voc lembrar que uma funo pode chamar outra. Enquanto estiver no
meio de uma funo, o programa pode ter que executar as instrues em outra funo. Ento, enquanto
estiver executando a nova funo, o programa pode ter que executar mais uma funo!
Felizmente, o Python bom em no perder o fio da meada, ento cada vez que uma funo concluda,
o programa continua de onde parou na funo que o chamou. Quando chega no fim do programa, ele
encerrado.
Resumindo, nem sempre se deve ler um programa de cima para baixo. s vezes faz mais sentido seguir
o fluxo de execuo.

3.7 - Parmetros e argumentos


Algumas funes que vimos exigem argumentos. Por exemplo, ao chamar math.sin, voc usa um
nmero como argumento. Algumas funes exigem mais de um argumento: o math.pow exige dois, a
base e o expoente.
Dentro da funo, os argumentos so atribudos a variveis chamadas parmetros. Aqui est a definio
de uma funo que precisa de um argumento:
def print_twice(bruce):
print(bruce)
print(bruce)
22

Esta funo atribui o argumento a um parmetro chamado bruce. Quando a funo chamada, ela
exibe o valor do parmetro (seja qual for) duas vezes.
Esta funo funciona com qualquer valor que possa ser exibido:
>>> print_twice('Spam')
Spam
Spam
>>> print_twice(42)
42
42
>>> print_twice(math.pi)
3.14159265359
3.14159265359

As mesmas regras de composio usadas para funes integradas tambm so aplicadas a funes
definidas pelos programadores, ento podemos usar qualquer tipo de expresso como argumento para
print_twice:
>>> print_twice('Spam '*4)
Spam Spam Spam Spam
Spam Spam Spam Spam
>>> print_twice(math.cos(math.pi))
-1.0
-1.0

O argumento avaliado antes de a funo ser chamada. Ento, nos exemplos, as expresses 'Spam *
4 e math.cos(math.pi) s so avaliadas uma vez.
Voc tambm pode usar uma varivel como argumento:
>>> michael = 'Eric, the half a bee.'
>>> print_twice(michael)
Eric, the half a bee.
Eric, the half a bee.

O nome da varivel que passamos como argumento (michael) no tem nada a ver com o nome do
parmetro (bruce). No importa que o valor tenha sido chamado de volta (em quem chama); aqui em
print_twice, chamamos todo mundo de bruce.

3.8 - As variveis e os parmetros so locais


Quando voc cria uma varivel dentro de uma funo, ela local, ou seja, ela s existe dentro da
funo. Por exemplo:
def cat_twice(part1, part2):
cat = part1 + part2
print_twice(cat)

Esta funo recebe dois argumentos, concatena-os e exibe o resultado duas vezes. Aqui est um
exemplo que a usa:
23

>>> line1 = 'Bing tiddle '


>>> line2 = 'tiddle bang.'
>>> cat_twice(line1, line2)
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.

Quando cat_twice encerrada, a varivel cat destruda. Se tentarmos exibi-la, recebemos uma
exceo:
>>> print(cat)
NameError: name 'cat' is not defined

Os parmetros tambm so locais. Por exemplo, alm de print_twice, no existe o bruce.

3.9 - Diagrama da pilha


Para monitorar quais variveis podem ser usadas e onde, uma boa ideia desenhar um diagrama da
pilha. Assim como diagramas de estado, os diagramas da pilha mostram o valor de cada varivel, mas
tambm mostram a funo qual cada varivel pertence.
Cada funo representada por um frame (quadro). Um frame uma caixa com o nome de uma funo
junto a ele e os parmetros e as variveis da funo dentro dele. O diagrama da pilha para o exemplo
anterior exibido na Figura 3.1.

Figura 3.1 Diagrama da pilha.


Os frames so organizados em uma pilha que indica qual funo que foi chamada por outra, e assim por
diante. Neste exemplo, print_twice foi chamada por cat_twice e cat_twice foi chamada por
__main__, que um nome especial para o frame na posio mais proeminente. Quando voc cria
uma varivel fora de qualquer funo, ela pertence a __main__.

Cada parmetro refere-se ao mesmo valor como seu argumento correspondente. Desta forma, part1 tem
o mesmo valor que line1, part2 tem o mesmo valor que line2 e bruce tem o mesmo valor que cat.
24

Se ocorrer um erro durante uma chamada de funo, o Python exibe o nome da funo, o nome da
funo que a chamou e o nome da funo que chamou esta funo por sua vez, voltando at
__main__.

Por exemplo, se voc tentar acessar cat de dentro de print_twice, receber uma mensagem de
NameError:
Traceback (innermost last):
File "test.py", line 13, in __main__
cat_twice(line1, line2)
File "test.py", line 5, in cat_twice
print_twice(cat)
File "test.py", line 9, in print_twice
print(cat)
NameError: name 'cat' is not defined

Esta lista de funes chamada de traceback. Ela mostra o arquivo do programa em que o erro ocorreu
e em que linha, e quais funes estavam sendo executadas no momento. Ele tambm mostra a linha de
cdigo que causou o erro.
A ordem das funes no traceback a mesma que a ordem dos frames no diagrama da pilha. A funo
que est sendo executada no momento est no final.

3.10 - Funes com resultado e funes nulas


Algumas funes que usamos, como as funes matemticas, devolvem resultados; por falta de um
nome melhor, vou cham-las de funes com resultados. Outras funes, como print_twice, executam
uma ao, mas no devolvem um valor. Elas so chamadas de funes nulas.
Quando voc chama uma funo com resultado, quase sempre quer fazer algo com o resultado; por
exemplo, voc pode atribui-lo a uma varivel ou us-la como parte de uma expresso:
x = math.cos(radians)
golden = (math.sqrt(5) + 1) / 2

Quando voc chama uma funo no modo interativo, o Python exibe o resultado:
>>> math.sqrt(5)
2.2360679774997898

Mas em um script, se voc chamar uma funo com resultado e mais nada, o valor de retorno perdido
para sempre!
math.sqrt(5)

Este script calcula a raiz quadrada de 5, mas como no armazena ou exibe o resultado, no muito til.
25

As funes nulas podem exibir algo na tela ou ter algum outro efeito, mas no tm um valor de retorno.
Se voc atribuir o resultado a uma varivel, recebe um valor especial chamado None:
>>> result = print_twice('Bing')
Bing
Bing
>>> print(result)
None

O valor None no o mesmo que a string 'None'. um valor especial que tem seu prprio tipo:
>>> print(type(None))
<class 'NoneType'>

As funes que apresentamos por enquanto so todas nulas. Vamos apresentar funes com resultado
mais adiante.

3.11 - Por que funes?


Caso o objetivo de dividir um programa em funes no esteja claro, saiba que na verdade h vrias
razes:
Criar uma nova funo d a oportunidade de nomear um grupo de instrues, o que deixa o seu
programa mais fcil de ler e de depurar.
As funes podem tornar um programa menor, eliminando o cdigo repetitivo. Depois, se fizer
alguma alterao, basta faz-la em um lugar s.
Dividir um programa longo em funes permite depurar as partes uma de cada vez e ento
reuni-las em um conjunto funcional.
As funes bem projetadas muitas vezes so teis para muitos programas. Uma vez que escreva
e depure uma, voc pode reutiliz-la.

3.12 - Depurao
Uma das habilidades mais importantes que voc vai aprender a depurao. Embora possa ser
frustrante, a depurao uma das partes mais intelectualmente ricas, desafiadoras e interessantes da
programao.
De certa forma, depurar similar ao trabalho de um detetive. Voc tem pistas e precisa inferir os
processos e eventos que levaram aos resultados exibidos.
A depurao tambm como cincia experimental. Uma vez que voc tenha uma ideia sobre o que est
errado, basta alterar o programa e tentar novamente. Se a sua hiptese estava correta, voc pode prever
o resultado da alterao e chegar um passo mais perto de um programa funcional. Se a sua hiptese
26

estava errada, preciso criar outra. Como dizia Sherlock Holmes, Quando se elimina o impossvel, o
que sobra, por mais incrvel que parea, s pode ser a verdade. (A. Conan Doyle, O signo dos quatro).
Para algumas pessoas, programar e depurar so a mesma coisa. Isto , a programao o processo de
depurar gradualmente um programa at que ele faa o que o programador quer. A ideia que voc
comece com um programa funcional e faa pequenas alteraes, depurando-as no decorrer do trabalho.
Por exemplo, o Linux um sistema operacional que contm milhes de linhas de cdigo, mas comeou
como um programa simples que Linus Torvalds usava para explorar o chip Intel 80386. Segundo Larry
Greenfield, Um dos primeiros projetos de Linus foi um programa que alternaria entre a exibio de
AAAA e BBBB. Mais tarde isso se desenvolveu at virar o Linux. (Guia do usurio de Linux verso
beta 1).

3.13 - Glossrio
funo
Uma sequncia nomeada de declaraes que executa alguma operao til. As funes podem
receber argumentos ou no e podem ou no produzir algum resultado.
definio de funo
Uma instruo que cria uma funo nova, especificando seu nome, parmetros e as instrues
que contm.
objeto da funo
Um valor criado por uma definio de funo. O nome da funo uma varivel que se refere a
um objeto de funo.
cabealho
A primeira linha de uma definio de funo.
corpo
A sequncia de instrues dentro de uma definio de funo.
parmetro
Um nome usado dentro de uma funo para se referir ao valor passado como argumento.
chamada de funo
Uma instruo que executa uma funo. composta pelo nome da funo seguido de uma lista
de argumentos entre parnteses.
argumento
Um valor apresentado a uma funo quando a funo chamada. Este valor atribudo ao
parmetro correspondente na funo.
varivel local
Uma varivel definida dentro de uma funo. Uma varivel local s pode ser usada dentro da sua
funo.
valor de retorno
O resultado de uma funo. Se uma chamada de funo for usada como uma expresso, o valor
de retorno o valor da expresso.
funo com resultado
Uma funo que devolve um valor.
funo nula
27

Uma funo que sempre devolve None.


None
Um valor especial apresentado por funes nulas.
mdulo
Um arquivo que contm uma coleo de funes relacionadas e outras definies.
instruo de importao
Uma instruo que l um arquivo de mdulo e cria um objeto de mdulo.
objeto de mdulo
Um valor criado por uma instruo import que oferece acesso aos valores definidos em um
mdulo.
notao de ponto
A sintaxe para chamar uma funo em outro mdulo especificando o nome do mdulo seguido de
um ponto e o nome da funo.
composio
O uso de uma expresso como parte de uma expresso maior ou de uma instruo como parte de
uma instruo maior.
fluxo de execuo
A ordem na qual as instrues so executadas.
diagrama da pilha
Representao grfica de uma pilha de funes, suas variveis e os valores a que se referem.
frame
Uma caixa em um diagrama da pilha que representa uma chamada de funo. Contm as
variveis locais e os parmetros da funo.
traceback
Lista das funes que esto sendo executadas, exibidas quando ocorre uma exceo.

3.14 - Exerccios
Exerccio 3.1
Escreva uma funo chamada right_justify, que receba uma string chamada s como parmetro e exiba a
string com espaos suficientes frente para que a ltima letra da string esteja na coluna 70 da tela:
>>> right_justify('monty')
monty

Dica: Use concatenao de strings e repetio. Alm disso, o Python oferece uma funo integrada
chamada len, que apresenta o comprimento de uma string, ento o valor de len('monty') 5.

Exerccio 3.2
Um objeto de funo um valor que pode ser atribudo a uma varivel ou passado como argumento.
Por exemplo, do_twice uma funo que toma um objeto de funo como argumento e o chama duas
vezes:
def do_twice(f):
28

f()
f()

Aqui est um exemplo que usa do_twice para chamar uma funo chamada print_spam duas vezes:
def print_spam():
print('spam')
do_twice(print_spam)

1. Digite este exemplo em um script e teste-o.


2. Altere do_twice para que receba dois argumentos, um objeto de funo e um valor, e chame a
funo duas vezes, passando o valor como um argumento.
3. Copie a definio de print_twice que aparece anteriormente neste captulo no seu script.

4. Use a verso alterada de do_twice para chamar print_twice duas vezes, passando
'spam' como um argumento.

5. Defina uma funo nova chamada do_four que receba um objeto de funo e um valor e
chame a funo quatro vezes, passando o valor como um parmetro. Deve haver s duas
afirmaes no corpo desta funo, no quatro.
Soluo: http://thinkpython2.com/code/do_four.py.

Exerccio 3.3
Nota: Este exerccio deve ser feito usando-se apenas as instrues e os outros recursos que
aprendemos at agora.

1. Escreva uma funo que desenhe uma grade como a seguinte:


+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +
| | |
| | |
| | |
| | |
+ - - - - + - - - - +

Dica: para exibir mais de um valor em uma linha, podemos usar uma sequncia de valores
separados por vrgula:

print('+', '-')

Por padro, print avana para a linha seguinte, mas podemos ignorar esse comportamento e
inserir um espao no fim, desta forma:
29

print('+', end=' ')


print('-')

A sada dessas instrues + -. Uma instruo print sem argumento termina a linha
atual e vai para a prxima linha.

1. Escreva uma funo que desenhe uma grade semelhante com quatro linhas e quatro colunas.
Soluo: http://thinkpython2.com/code/grid.py. Crdito: Este exerccio baseado em outro apresentado
por Oualline, em Practical C Programming, Third Edition, OReilly Media, 1997.

Captulo 4: Estudo de caso: projeto de interface


Este captulo apresenta um estudo de caso que demonstra o processo de criao de funes que operam
simultaneamente.
Ele apresenta o mdulo turtle, que permite criar imagens usando [turtle graphics][1]. O mdulo turtle
includo na maior parte das instalaes do Python, mas se estiver executando a linguagem com o
PythonAnywhere voc no poder executar os exemplos do turtle (pelo menos no era possvel quando
escrevi este livro).
Se j tiver instalado o Python no seu computador, voc poder executar os exemplos. Caso no, agora
uma boa hora para instalar. Publiquei instrues no site http://tinyurl.com/thinkpython2e.
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/polygon.py.

4.1 - Mdulo turtle


Para conferir se voc tem o mdulo turtle, abra o interpretador do Python e digite:
>>> import turtle
>>> bob = turtle.Turtle()

Ao executar este cdigo o programa deve abrir uma nova janela com uma pequena flecha que
representa o turtle. Feche a janela.
Crie um arquivo chamado mypolygon.py e digite o seguinte cdigo:
import turtle
bob = turtle.Turtle()
print(bob)
turtle.mainloop()

O mdulo turtle (com t minsculo) apresenta uma funo chamada Turtle (com T maisculo), que cria
um objeto Turtle, ao qual atribumos uma varivel chamada bob. Exibir bob faz algo assim:
<turtle.Turtle object at 0xb7bfbf4c>
30

Isto significa que bob se refere a um objeto com o tipo Turtle definido no mdulo turtle.
mainloop diz que a janela deve esperar que o usurio faa algo, embora neste caso no haja muito a
fazer, exceto fechar a janela.
Uma vez que tenha criado o Turtle, voc pode chamar um mtodo para mov-lo pela janela. Mtodo
semelhante a uma funo, mas usa uma sintaxe ligeiramente diferente. Por exemplo, para mover o
turtle para a frente:
bob.fd(100)

O mtodo fd associado com o objeto turtle, que denominamos bob. Chamar de um mtodo como
fazer um pedido: voc est pedindo que bob avance.
O argumento de fd uma distncia em pxeis, ento o tamanho real depende da sua tela.
Outros mtodos que voc pode chamar em um Turtle so bk para mover-se para trs, lt para virar
esquerda e rt para virar direita. O argumento para lt e rt um ngulo em graus.
Alm disso, cada Turtle segura uma caneta, que est abaixada ou levantada; se a caneta estiver
abaixada, o Turtle deixa um rastro quando se move. Os mtodos pu e pd representam caneta para
cima e caneta para baixo.
Para desenhar um ngulo reto, acrescente estas linhas ao programa (depois de criar bob e antes de
chamar o mainloop):
bob.fd(100)
bob.lt(90)
bob.fd(100)

Ao executar este programa, voc deveria ver bob mover-se para o leste e depois para o norte, deixando
dois segmentos de reta para trs.
Agora altere o programa para desenhar um quadrado. S siga adiante neste captulo se ele funcionar
adequadamente!

4.2 - Repetio simples


Provavelmente voc escreveu algo assim:
bob.fd(100)
bob.lt(90)
bob.fd(100)
bob.lt(90)
bob.fd(100)
bob.lt(90)
bob.fd(100)
31

Podemos fazer a mesma coisa de forma mais concisa com uma instruo for. Acrescente este exemplo a
mypolygon.py e execute-o novamente:
for i in range(4):
print('Hello!')

Voc deve ver algo assim:


Hello!
Hello!
Hello!
Hello!

Este o uso mais simples da instruo for; depois veremos mais sobre isso. Mas isso deve ser o
suficiente para que voc possa reescrever o seu programa de desenhar quadrados. No continue a
leitura at que d certo.
Aqui est uma instruo for que desenha um quadrado:
for i in range(4):
bob.fd(100)
bob.lt(90)

A sintaxe de uma instruo for semelhante definio de uma funo. Tem um cabealho que termina
em dois pontos e um corpo endentado. O corpo pode conter qualquer nmero de instrues.
Uma instruo for tambm chamada de loop porque o fluxo da execuo passa pelo corpo e depois
volta ao topo. Neste caso, ele passa pelo corpo quatro vezes.
Esta verso, na verdade, um pouco diferente do cdigo anterior que desenha quadrados porque faz
outra volta depois de desenhar o ltimo lado do quadrado. A volta extra leva mais tempo, mas
simplifica o cdigo se fizermos a mesma coisa a cada vez pelo loop. Esta verso tambm tem o efeito
de trazer o turtle de volta posio inicial, de frente para a mesma direo em que estava.

4.3 - Exerccios
A seguir, uma srie de exerccios usando TurtleWorld. Eles servem para divertir, mas tambm tm outro
objetivo. Enquanto trabalha neles, pense que objetivo pode ser.
As sees seguintes tm as solues para os exerccios, mas no olhe at que tenha terminado (ou, pelo
menos, tentado).
1. Escreva uma funo chamada square que receba um parmetro chamado t, que um turtle. Ela
deve usar o turtle para desenhar um quadrado.
Escreva uma chamada de funo que passe bob como um argumento para o square e ento execute
o programa novamente.
32

1. Acrescente outro parmetro, chamado length, ao square. Altere o corpo para que o comprimento
dos lados seja length e ento altere a chamada da funo para fornecer um segundo argumento.
Execute o programa novamente. Teste o seu programa com uma variedade de valores para
length.
2. Faa uma cpia do square e mude o nome para polygon. Acrescente outro parmetro chamado n
e altere o corpo para que desenhe um polgono regular de n lados.
Dica: os ngulos exteriores de um polgono regular de n lados so 360/n graus.
1. Escreva uma funo chamada circle que use o turtle, t e um raio r como parmetros e desenhe
um crculo aproximado ao chamar polygon com um comprimento e nmero de lados adequados.
Teste a sua funo com uma srie de valores de r.
Dica: descubra a circunferncia do crculo e certifique-se de que length * n = circumference.
1. Faa uma verso mais geral do circle chamada arc, que receba um parmetro adicional de angle,
para determinar qual frao do crculo deve ser desenhada. angle est em unidades de graus,
ento quando angle=360, o arc deve desenhar um crculo completo.

4.4 - Encapsulamento
O primeiro exerccio pede que voc ponha seu cdigo para desenhar quadrados em uma definio de
funo e ento chame a funo, passando o turtle como parmetro. Aqui est uma soluo:
def square(t):
for i in range(4):
t.fd(100)
t.lt(90)

square(bob)

As instrues mais internas, fd e lt, so endentadas duas vezes para mostrar que esto dentro do loop
for, que est dentro da definio da funo. A linha seguinte, square(bob), est alinhada margem
esquerda, o que indica tanto o fim do loop for como da definio de funo.
Dentro da funo, o t indica o mesmo turtle bob, ento t.lt (90) tem o mesmo efeito que bob.lt (90).
Neste caso, por que no chamar o parmetro bob? A ideia que t pode ser qualquer turtle, no apenas
bob, ento voc pode criar um segundo turtle e pass-lo como argumento ao square:
alice = turtle.Turtle()
square(alice)

Incluir uma parte do cdigo em uma funo chama-se encapsulamento. Um dos benefcios do
encapsulamento que ele atribui um nome ao cdigo, o que serve como uma espcie de documentao.
Outra vantagem que se voc reutilizar o cdigo, mais conciso chamar uma funo duas vezes que
copiar e colar o corpo!
33

4.5 - Generalizao
O prximo passo acrescentar um parmetro length ao square. Aqui est uma soluo:
def square(t, length):
for i in range(4):
t.fd(length)
t.lt(90)

square(bob, 100)

Acrescentar um parmetro a uma funo chama-se generalizao porque ele torna a funo mais geral:
na verso anterior, o quadrado sempre do mesmo tamanho; nesta verso, pode ser de qualquer
tamanho.
O prximo passo tambm uma generalizao. Em vez de desenhar quadrados, polygon desenha
polgonos regulares com qualquer nmero de lados. Aqui est uma soluo:
def polygon(t, n, length):
angle = 360 / n
for i in range(n):
t.fd(length)
t.lt(angle)

polygon(bob, 7, 70)

Este exemplo desenha um polgono de 7 lados, cada um de comprimento 70.


Se estiver usando Python 2, o valor do angle poderia estar errado por causa da diviso de nmero
inteiro. Uma soluo simples calcular angle = 360.0 / n. Como o numerador um nmero de ponto
flutuante, o resultado em ponto flutuante.
Quando uma funo tem vrios argumentos numricos, fcil esquecer o que eles so ou a ordem na
qual eles devem estar. Neste caso, muitas vezes uma boa ideia incluir os nomes dos parmetros na
lista de argumentos:
polygon (bob, n=7, length=70)

Esses so os argumentos de palavra-chave porque incluem os nomes dos parmetros como palavras-
chave (para no confundir com palavras-chave do Python, tais como while e def).
Esta sintaxe torna o programa mais legvel. Tambm uma lembrana sobre como os argumentos e os
parmetros funcionam: quando voc chama uma funo, os argumentos so atribudos aos parmetros.

4.6 - Projeto da interface


O prximo passo escrever circle, que recebe um raio r, como parmetro. Aqui est uma soluo
simples que usa o polygon para desenhar um polgono de 50 lados:
34

import math
def circle(t, r):
circumference = 2 * math.pi * r
n = 50
length = circumference / n
polygon(t, n, length)

A primeira linha calcula a circunferncia de um crculo com o raio r usando a frmula 2r. J que
usamos math.pi, temos que importar math. Por conveno, instrues import normalmente ficam no
incio do script.
n o nmero de segmentos de reta na nossa aproximao de um crculo, ento length o comprimento
de cada segmento. Assim, polygon desenha um polgono 50 lados que se aproxima de um crculo com
o raio r.
Uma limitao desta soluo que n uma constante. Para crculos muito grandes, os segmentos de
reta so longos demais, e para crculos pequenos, perdemos tempo desenhando segmentos muito
pequenos. Uma soluo seria generalizar a funo tomando n como parmetro. Isso daria ao usurio
(seja quem for que chame circle) mais controle, mas a interface seria menos limpa.
A interface de uma funo um resumo de como ela usada: Quais so os parmetros? O que a funo
faz? E qual o valor de retorno? Uma interface limpa se permitir pessoa que a chama fazer o que
quiser sem ter que lidar com detalhes desnecessrios.
Neste exemplo, r pertence interface porque especifica o crculo a ser desenhado. n menos adequado
porque pertence aos detalhes de como o crculo deve ser apresentado.
Em vez de poluir a interface, melhor escolher um valor adequado para n, dependendo da
circumference:
def circle(t, r):
circumference = 2 * math.pi * r
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, n, length)

Neste ponto, o nmero de segmentos um nmero inteiro prximo a circumference/3, ento o


comprimento de cada segmento aproximadamente 3, pequeno o suficiente para que os crculos
fiquem bons, mas grandes o suficiente para serem eficientes e aceitveis para crculos de qualquer
tamanho.

4.7 - Refatorao
Quando escrevi circle, pude reutilizar polygon porque um polgono de muitos lados uma boa
aproximao de um crculo. Mas o arc no to cooperativo; no podemos usar polygon ou circle para
desenhar um arco.
35

Uma alternativa comear com uma cpia de polygon e transform-la em arc. O resultado poderia ser
algo assim:
def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = angle / n
for i in range(n):
t.fd(step_length)
t.lt(step_angle)

A segunda metade desta funo parece com a do polygon, mas no possvel reutilizar o polygon sem
mudar a interface. Poderamos generalizar polygon para receber um ngulo como um terceiro
argumento, mas ento polygon no seria mais um nome adequado! Em vez disso, vamos chamar a
funo mais geral de polyline:
def polyline(t, n, length, angle):
for i in range(n):
t.fd(length)
t.lt(angle)

Agora podemos reescrever polygon e arc para usar polyline:


def polygon(t, n, length):
angle = 360.0 / n
polyline(t, n, length, angle)
def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = float(angle) / n
polyline(t, n, step_length, step_angle)

Finalmente, podemos reescrever circle para usar arc:


def circle(t, r):
arc(t, r, 360)

Este processo recompor um programa para melhorar interfaces e facilitar a reutilizao do cdigo
chamado de refatorao. Neste caso, notamos que houve cdigo semelhante em arc e polygon, ento
ns o fatoramos no polyline.
Se tivssemos planejado, poderamos ter escrito polyline primeiro e evitado a refatorao, mas muitas
vezes no sabemos o suficiente j no incio de um projeto para projetar todas as interfaces. Quando
comearmos a escrever cdigo, entenderemos melhor o problema. s vezes, a refatorao um sinal de
que aprendemos algo.
36

4.8 - Um plano de desenvolvimento


Um plano de desenvolvimento um processo para escrever programas. O processo que usamos neste
estudo de caso encapsulamento e generalizao. Os passos deste processo so:
1. Comece escrevendo um pequeno programa sem definies de funo.
2. Uma vez que o programa esteja funcionando, identifique uma parte coerente dele, encapsule
essa parte em uma funo e d um nome a ela.
3. Generalize a funo acrescentando os parmetros adequados.
4. Repita os passos 1-3 at que tenha um conjunto de funes operantes. Copie e cole o cdigo
operante para evitar a redigitao (e redepurao).
5. Procure oportunidades de melhorar o programa pela refatorao. Por exemplo, se voc tem um
cdigo semelhante em vrios lugares, pode ser uma boa ideia fator-lo em uma funo geral
adequada.
Este processo tem algumas desvantagens veremos alternativas mais tarde mas pode ser til se voc
no souber de antemo como dividir o programa em funes. Esta abordagem permite criar o projeto
no decorrer do trabalho.

4.9 - docstring
Uma docstring uma string no incio de uma funo que explica a interface (doc uma abreviao
para documentao). Aqui est um exemplo:
def polyline(t, n, length, angle):
"""Desenha n segmentos de reta com o comprimento dado e
ngulo (em graus) entre eles. t um turtle.
"""
for i in range(n):
t.fd(length)
t.lt(angle)

Por conveno, todas as docstrings tm aspas triplas, tambm conhecidas como strings multilinha
porque as aspas triplas permitem que a string se estenda por mais de uma linha.
conciso, mas contm a informao essencial que algum precisaria para usar esta funo. Explica
sucintamente o que a funo faz (sem entrar nos detalhes de como o faz). Explica que efeito cada
parmetro tem sobre o comportamento da funo e o tipo que cada parmetro deve ser (se no for
bvio).
Escrever este tipo de documentao uma parte importante do projeto da interface. Uma interface bem
projetada deve ser simples de explicar; se no for assim, talvez a interface possa ser melhorada.
37

4.10 - Depurao
Uma interface como um contrato entre uma funo e quem a chama. Quem chama concorda em
fornecer certos parmetros e a funo concorda em fazer certa ao.
Por exemplo, polyline precisa de quatro argumentos: t tem que ser um Turtle; n tem que ser um nmero
inteiro; length deve ser um nmero positivo; e o angle tem que ser um nmero, que se espera estar em
graus.
Essas exigncias so chamadas de precondies porque se supe que sejam verdade antes que a funo
seja executada. De forma inversa, as condies no fim da funo so ps-condies. As ps-condies
incluem o efeito desejado da funo (como o desenho de segmentos de reta) e qualquer efeito colateral
(como mover o Turtle ou fazer outras mudanas).
Precondies so responsabilidade de quem chama. Se quem chama violar uma precondio
(adequadamente documentada!) e a funo no funcionar corretamente, o problema est nesta pessoa,
no na funo.
Se as precondies forem satisfeitas e as ps-condies no forem, o problema est na funo. Se as
suas precondies e ps-condies forem claras, elas podem ajudar na depurao.

4.11 - Glossrio
mtodo
Uma funo associada a um objeto e chamada usando a notao de ponto.
loop
Parte de um programa que pode ser executada repetidamente.
encapsulamento
O processo de transformar uma sequncia de instrues em uma definio de funo.
generalizao
O processo de substituir algo desnecessariamente especfico (como um nmero) por algo
adequadamente geral (como uma varivel ou parmetro).
argumento de palavra-chave
Um argumento que inclui o nome do parmetro como uma palavra-chave.
interface
Uma descrio de como usar uma funo, incluindo o nome e as descries dos argumentos e do
valor de retorno.
refatorao
O processo de alterar um programa funcional para melhorar a interface de funes e outras
qualidades do cdigo.
plano de desenvolvimento
Um processo de escrever programas.
docstring
Uma string que aparece no incio de uma definio de funo para documentar a interface da
funo.
precondio
38

Uma exigncia que deve ser satisfeita por quem chama a funo, antes de execut-la.
ps-condio
Uma exigncia que deve ser satisfeita pela funo antes que ela seja encerrada.

4.12 - Exerccios
Exerccio 4.1
Baixe o cdigo deste captulo no site http://thinkpython2.com/code/polygon.py.
1. Desenhe um diagrama da pilha que mostre o estado do programa enquanto executa circle (bob,
radius). Voc pode fazer a aritmtica mo ou acrescentar instrues print ao cdigo.
2. A verso de arc na seo 4.7 - Refatorao no muito precisa porque a aproximao linear do
crculo est sempre do lado de fora do crculo verdadeiro. Consequentemente, o Turtle acaba
ficando alguns pxeis de distncia do destino correto. Minha soluo mostra um modo de
reduzir o efeito deste erro. Leia o cdigo e veja se faz sentido para voc. Se desenhar um
diagrama, poder ver como funciona.

Exerccio 4.2
Escreva um conjunto de funes adequadamente geral que possa desenhar flores como as da Figura
4.1.

Figura 4.1 Flores de tartaruga.


Soluo: http://thinkpython2.com/code/flower.py, tambm exige
http://thinkpython2.com/code/polygon.py.

Exerccio 4.3
Escreva um conjunto de funes adequadamente geral que possa desenhar formas como as da Figura
4.2.
39

Figura 4.2 Tortas de tartaruga.


Soluo: http://thinkpython2.com/code/pie.py.

Exerccio 4.4
As letras do alfabeto podem ser construdas a partir de um nmero moderado de elementos bsicos,
como linhas verticais e horizontais e algumas curvas. Crie um alfabeto que possa ser desenhado com
um nmero mnimo de elementos bsicos e ento escreva funes que desenhem as letras.
Voc deve escrever uma funo para cada letra, com os nomes draw_a, draw_b etc., e colocar suas
funes em um arquivo chamado letters.py. Voc pode baixar uma mquina de escrever de turtle no
site http://thinkpython2.com/code/typewriter.py para ajudar a testar o seu cdigo.
Voc pode ver uma soluo no site http://thinkpython2.com/code/letters.py; ela tambm exige
http://thinkpython2.com/code/polygon.py.

Exerccio 4.5
Leia sobre espirais em https://pt.wikipedia.org/wiki/Espiral; ento escreva um programa que desenhe
uma espiral de Arquimedes (ou um dos outros tipos).
[1] turtle graphics ou grficos de tartaruga o sistema de desenho popularizado pela linguagem Logo,
onde os comandos movimentam um cursor triangular pela tela, conhecido como turtle ou tartaruga. A
tartaruga deixa um rastro medida que movimentada, e com esses rastros que se forma um desenho.
Diferente dos sistemas usuais de desenho em computao grfica, o sistema turtle graphics no exige o
uso de coordenadas cartesianas.

Captulo 5: Condicionais e recursividade


O tpico principal deste captulo a instruo if, que executa cdigos diferentes dependendo do estado
do programa. Mas primeiro quero apresentar dois novos operadores: diviso pelo piso e mdulo.
40

5.1 - Diviso pelo piso e mdulo


O operador de diviso pelo piso, //, divide dois nmeros e arredonda o resultado para um nmero
inteiro para baixo. Por exemplo, suponha que o tempo de execuo de um filme seja de 105 minutos.
Voc pode querer saber a quanto isso corresponde em horas. A diviso convencional devolve um
nmero de ponto flutuante:
>>> minutes = 105
>>> minutes / 60
1.75

Mas no comum escrever horas com pontos decimais. A diviso pelo piso devolve o nmero inteiro
de horas, ignorando a parte fracionria:
>>> minutes = 105
>>> hours = minutes // 60
>>> hours
1

Para obter o resto, voc pode subtrair uma hora em minutos:


>>> remainder = minutes - hours * 60
>>> remainder
45

Uma alternativa usar o operador mdulo, %, que divide dois nmeros e devolve o resto:
>>> remainder = minutes % 60
>>> remainder
45

O operador mdulo mais til do que parece. Por exemplo, possvel verificar se um nmero
divisvel por outro se x % y for zero, ento x divisvel por y.
Alm disso, voc pode extrair o dgito ou dgitos mais direita de um nmero. Por exemplo, x % 10
produz o dgito mais direita de x (na base 10). Da mesma forma x % 100 produz os dois ltimos
dgitos.
Se estiver usando o Python 2, a diviso funciona de forma diferente. O operador de diviso, /, executa a
diviso pelo piso se ambos os operandos forem nmeros inteiros e faz a diviso de ponto flutuante se
pelo menos um dos operandos for do tipo float.

5.2 - Expresses booleanas


Uma expresso booleana uma expresso que pode ser verdadeira ou falsa. Os exemplos seguintes
usam o operador ==, que compara dois operandos e produz True se forem iguais e False se no forem:
>>> 5 == 5
True
41

>>> 5 == 6
False

True e False so valores especiais que pertencem ao tipo bool; no so strings:


>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>

O operador == um dos operadores relacionais; os outros so:


x != y # x no igual a y
x > y # x maior que y
x < y # x menor que y
x >= y # x maior ou igual a y
x <= y # x menor ou igual a y

Embora essas operaes provavelmente sejam familiares para voc, os smbolos do Python so
diferentes dos smbolos matemticos. Um erro comum usar apenas um sinal de igual (=) em vez de
um sinal duplo (==). Lembre-se de que = um operador de atribuio e == um operador relacional.
No existe =< ou =>.

5.3 - Operadores lgicos


H trs operadores lgicos: and, or e not. A semntica (significado) destes operadores semelhante ao
seu significado em ingls. Por exemplo, x> 0 and x <10 s verdade se x for maior que 0 e menor que
10.
n%2 == 0 or n%3 == 0 verdadeiro se uma ou as duas condio(es) for(em) verdadeira(s), isto , se o
nmero for divisvel por 2 ou 3.
Finalmente, o operador not nega uma expresso booleana, ento not (x > y) verdade se x > y for falso,
isto , se x for menor que ou igual a y.
Falando estritamente, os operandos dos operadores lgicos devem ser expresses booleanas, mas o
Python no muito estrito. Qualquer nmero que no seja zero interpretado como True:
>>> 42 and True
True

Esta flexibilidade tem sua utilidade, mas h algumas sutilezas relativas a ela que podem ser confusas.
Assim, pode ser uma boa ideia evit-la (a menos que saiba o que est fazendo).
42

5.4 - Execuo condicional


Para escrever programas teis, quase sempre precisamos da capacidade de verificar condies e mudar
o comportamento do programa de acordo com elas. Instrues condicionais nos do esta capacidade. A
forma mais simples a instruo if:
if x > 0:
print('x is positive')

A expresso booleana depois do if chamada de condio. Se for verdadeira, a instruo endentada


executada. Se no, nada acontece.
Instrues if tm a mesma estrutura que definies de funo: um cabealho seguido de um corpo
endentado. Instrues como essa so chamadas de instrues compostas.
No h limite para o nmero de instrues que podem aparecer no corpo, mas deve haver pelo menos
uma. Ocasionalmente, til ter um corpo sem instrues (normalmente como um espao reservado
para cdigo que ainda no foi escrito). Neste caso, voc pode usar a instruo pass, que no faz nada.
if x < 0:
pass # A FAZER: lidar com valores negativos!

5.5 - Execuo alternativa


Uma segunda forma da instruo if a execuo alternativa, na qual h duas possibilidades e a
condio determina qual ser executada. A sintaxe pode ser algo assim:
if x % 2 == 0:
print('x is even')
else:
print('x is odd')

Se o resto quando x for dividido por 2 for 0, ento sabemos que x par e o programa exibe uma
mensagem adequada. Se a condio for falsa, o segundo conjunto de instrues executado. Como a
condio deve ser verdadeira ou falsa, exatamente uma das alternativas ser executada. As alternativas
so chamadas de ramos (branches), porque so ramos no fluxo da execuo.

5.6 - Condicionais encadeadas


s vezes, h mais de duas possibilidades e precisamos de mais que dois ramos. Esta forma de expressar
uma operao de computao uma condicional encadeada:
if x < y:
print('x is less than y')
elif x > y:
print('x is greater than y')
else:
print('x and y are equal')
43

elif uma abreviatura de else if. Novamente, exatamente um ramo ser executado. No h nenhum
limite para o nmero de instrues elif. Se houver uma clusula else, ela deve estar no fim, mas no
preciso haver uma.
if choice == 'a':
draw_a()
elif choice == 'b':
draw_b()
elif choice == 'c':
draw_c()

Cada condio verificada em ordem. Se a primeira for falsa, a prxima verificada, e assim por
diante. Se uma delas for verdadeira, o ramo correspondente executado e a instruo encerrada.
Mesmo se mais de uma condio for verdade, s o primeiro ramo verdadeiro executado.

5.7 - Condicionais aninhadas


Uma condicional tambm pode ser aninhada dentro de outra. Poderamos ter escrito o exemplo na
seo anterior desta forma:
if x == y:
print('x and y are equal')
else:
if x < y:
print('x is less than y')
else:
print('x is greater than y')

A condicional exterior contm dois ramos. O primeiro ramo contm uma instruo simples. O segundo
ramo contm outra instruo if, que tem outros dois ramos prprios. Esses dois ramos so instrues
simples, embora pudessem ser instrues condicionais tambm.
Embora a endentao das instrues evidencie a estrutura das condicionais, condicionais aninhadas so
difceis de ler rapidamente. uma boa ideia evit-las quando for possvel.
Operadores lgicos muitas vezes oferecem uma forma de simplificar instrues condicionais
aninhadas. Por exemplo, podemos reescrever o seguinte cdigo usando uma nica condicional:
if 0 < x:
if x < 10:
print('x is a positive single-digit number.')

A instruo print s executada se a colocarmos depois de ambas as condicionais, ento podemos obter
o mesmo efeito com o operador and:
if 0 < x and x < 10:
print('x is a positive single-digit number.')

Para este tipo de condio, o Python oferece uma opo mais concisa:
44

if 0 < x < 10:


print('x is a positive single-digit number.')

5.8 - Recursividade
legal para uma funo chamar outra; tambm legal para uma funo chamar a si prpria. Pode no
ser bvio porque isso uma coisa boa, mas na verdade uma das coisas mais mgicas que um
programa pode fazer. Por exemplo, veja a seguinte funo:
def countdown(n):
if n <= 0:
print('Blastoff!')
else:
print(n)
countdown(n-1)

Se n for 0 ou negativo, a palavra Blastoff! exibida, seno a sada n e ento a funo countdown
chamada por si mesma passando n-1 como argumento.
O que acontece se chamarmos esta funo assim?
>>> countdown(3)

A execuo de countdown inicia com n=3 e como n maior que 0, ela produz o valor 3 e ento chama
a si mesma
A execuo de countdown inicia com n=2 e como n maior que 0, ela produz o valor 2 e ento
chama a si mesma
A execuo de countdown inicia com n=1 e como n maior que 0, ela produz o valor 1 e ento
chama a si mesma
A execuo de countdown inicia com n=0 e como n no maior que 0, ela produz a palavra
Blastoff! e ento retorna.
O countdown que recebeu n=1 retorna.
O countdown que recebeu n=2 retorna.
O countdown que recebeu n=3 retorna.
E ento voc est de volta ao __main__. Ento a sada completa ser assim:
3
2
1
Blastoff!

Uma funo que chama a si mesma dita recursiva; o processo para execut-la a recursividade.
45

Como em outro exemplo, podemos escrever uma funo que exiba uma string n vezes:
def print_n(s, n):
if n <= 0:
return
print(s)
print_n(s, n-1)

Se n <= 0 a instruo return causa a sada da funo. O fluxo de execuo volta imediatamente a
quem fez a chamada, e as linhas restantes da funo no so executadas.
O resto da funo similar countdown: ela mostra s e ento chama a si mesma para mostrar s mais n-
1 vezes. Ento o nmero de linhas da sada 1 + (n - 1), at chegar a n.
Para exemplos simples como esse, provavelmente mais fcil usar um loop for. Mais adiante veremos
exemplos que so difceis de escrever com um loop for e fceis de escrever com recursividade, ento
bom comear cedo.

5.9 - Diagramas da pilha para funes recursivas


Em Diagrama da pilha, na pgina 55, usamos um diagrama da pilha para representar o estado de um
programa durante uma chamada de funo. O mesmo tipo de diagrama pode ajudar a interpretar uma
funo recursiva.
Cada vez que uma funo chamada, o Python cria um frame para conter as variveis locais e
parmetros da funo. Para uma funo recursiva, pode haver mais de um frame na pilha ao mesmo
tempo.
A Figura 5.1 mostra um diagrama da pilha para countdown chamado com n = 3.

Figura 5.1 Diagrama da pilha.


46

Como de hbito, o topo da pilha o frame de __main__. Est vazio porque no criamos nenhuma
varivel em __main__ nem passamos argumentos a ela.

Os quatro frames do countdown tm valores diferentes para o parmetro n. O fundo da pilha, onde n =
0, chamado caso-base. Ele no faz uma chamada recursiva, ento no h mais frames.

Como exerccio, desenhe um diagrama da pilha para print_n chamado com s = 'Hello' e n =
2. Ento escreva uma funo chamada do_n que tome um objeto de funo e um nmero n como
argumentos e que chame a respectiva funo n vezes.

5.10 - Recursividade infinita


Se a recursividade nunca atingir um caso-base, continua fazendo chamadas recursivas para sempre, e o
programa nunca termina. Isso conhecido como recursividade infinita e geralmente no uma boa
ideia. Aqui est um programa mnimo com recursividade infinita:
def recurse():
recurse()

Na maior parte dos ambientes de programao, um programa com recursividade infinita no


realmente executado para sempre. O Python exibe uma mensagem de erro quando a profundidade
mxima de recursividade atingida:
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
File "<stdin>", line 2, in recurse
.
.
.
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded

Este traceback um pouco maior que o que vimos no captulo anterior. Quando o erro ocorre, h mil
frames de recurse na pilha!
Se voc escrever em recursividade infinita por engano, confira se a sua funo tem um caso-base que
no faz uma chamada recursiva. E se houver um caso-base, verifique se voc vai mesmo atingi-lo.

5.11 - Entrada de teclado


Os programas que escrevemos at agora no aceitam entradas do usurio. Eles sempre fazem a mesma
coisa cada vez.
O Python fornece uma funo integrada chamada input que interrompe o programa e espera que o
usurio digite algo. Quando o usurio pressionar Return ou Enter, o programa volta a ser executado e
47

input retorna o que o usurio digitou como uma string. No Python 2, a mesma funo chamada
raw_input.
>>> text = input()
What are you waiting for?
>>> text
What are you waiting for?

Antes de receber entradas do usurio, uma boa ideia exibir um prompt dizendo ao usurio o que ele
deve digitar. input pode ter um prompt como argumento:
>>> name = input('What...is your name?\\n')
What...is your name?
Arthur, King of the Britons!
>>> name
Arthur, King of the Britons!

A sequncia \n no final do prompt representa um newline, que um caractere especial de quebra de


linha. por isso que a entrada do usurio aparece abaixo do prompt.
Se esperar que o usurio digite um nmero inteiro, voc pode tentar converter o valor de retorno para
int:
>>> prompt = 'What...is the airspeed velocity of an unladen swallow?\\n'
>>> speed = input(prompt)
What...is the airspeed velocity of an unladen swallow?
42
>>> int(speed)
42

Mas se o usurio digitar algo alm de uma srie de dgitos, voc recebe um erro:
>>> speed = input(prompt)
What...is the airspeed velocity of an unladen swallow?
What do you mean, an African or a European swallow?
>>> int(speed)
ValueError: invalid literal for int() with base 10

Veremos como tratar este tipo de erro mais adiante.

5.12 - Depurao
Quando um erro de sintaxe ou de tempo de execuo ocorre, a mensagem de erro contm muita
informao, s vezes, at demais. As partes mais teis so normalmente:
que tipo de erro foi;

onde ocorreu.
48

Erros de sintaxe so normalmente fceis de encontrar, mas h algumas pegadinhas. Erros de whitespace
podem ser complicados porque os espaos e tabulaes so invisveis e estamos acostumados a ignor-
los.
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent

Neste exemplo, o problema que a segunda linha est endentada por um espao. Mas a mensagem de
erro aponta para y, o que pode ser capcioso. Em geral, mensagens de erro indicam onde o problema foi
descoberto, mas o erro real pode estar em outra parte do cdigo, s vezes, em uma linha anterior.
O mesmo acontece com erros em tempo de execuo. Suponha que voc esteja tentando calcular a
proporo de sinal a rudo em decibis. A frmula SNRdb = 10 log10 (Psignal/Pnoise). No Python,
voc poderia escrever algo assim:
import math
signal_power = 9
noise_power = 10
ratio = signal_power // noise_power
decibels = 10 * math.log10(ratio)
print(decibels)

Ao executar este programa, voc recebe uma exceo:


Traceback (most recent call last):
File "snr.py", line 5, in ?
decibels = 10 * math.log10(ratio)
ValueError: math domain error

A mensagem de erro indica a linha 5, mas no h nada de errado com esta linha. Uma opo para
encontrar o verdadeiro erro exibir o valor de ratio, que acaba sendo 0. O problema est na linha 4, que
usa a diviso pelo piso em vez da diviso de ponto flutuante.
preciso ler as mensagens de erro com ateno, mas no assumir que tudo que dizem esteja correto.

5.13 - Glossrio
diviso pelo piso
Um operador, denotado por //, que divide dois nmeros e arredonda o resultado para baixo (em
direo ao zero), a um nmero inteiro.
operador mdulo
Um operador, denotado com um sinal de percentagem (%), que funciona com nmeros inteiros e
devolve o resto quando um nmero dividido por outro.
expresso booleana
Uma expresso cujo valor True (verdadeiro) ou False (falso).
49

operador relacional
Um destes operadores, que compara seus operandos: `==`, `!=`, `>`, `<`, `>=` e `<=`.
operador lgico
Um destes operadores, que combina expresses booleanas: and (e), or (ou) e not (no).
instruo condicional
Uma instruo que controla o fluxo de execuo, dependendo de alguma condio.
condio
A expresso booleana em uma instruo condicional que determina qual ramo deve ser
executado.
instruo composta
Uma instruo composta de um cabealho e um corpo. O cabealho termina em dois pontos (:). O
corpo endentado em relao ao cabealho.
ramo
Uma das sequncias alternativas de instrues em uma instruo condicional.
condicional encadeada
Uma instruo condicional com uma srie de ramos alternativos.
condicional aninhada
Uma instruo condicional que aparece em um dos ramos de outra instruo condicional.
instruo de retorno
Uma instruo que faz uma funo terminar imediatamente e voltar a quem a chamou.
recursividade
O processo de chamar a funo que est sendo executada no momento.
caso-base
Um ramo condicional em uma funo recursiva que no faz uma chamada recursiva.
recursividade infinita
Recursividade que no tem um caso-base, ou nunca o atinge. A recursividade infinita
eventualmente causa um erro em tempo de execuo.

5.14 - Exerccios
Exerccio 5.1
O mdulo time fornece uma funo, tambm chamada time, que devolve a Hora Mdia de Greenwich
na poca, que um momento arbitrrio usado como ponto de referncia. Em sistemas UNIX, a poca
primeiro de janeiro de 1970.
>>> import time
>>> time.time()
1437746094.5735958

Escreva um script que leia a hora atual e a converta em um tempo em horas, minutos e segundos, mais
o nmero de dias desde a poca.
50

Exerccio 5.2
O ltimo teorema de Fermat diz que no existem nmeros inteiros a, b e c tais que a**n + b**n
== c**n para quaisquer valores de n maiores que 2.

1. Escreva uma funo chamada check_fermat que receba quatro parmetros a, b, c e n e


verifique se o teorema de Fermat se mantm. Se n for maior que 2 e a**n + b**n ==
c**n o programa deve imprimir, Holy smokes, Fermat was wrong! Seno o programa deve
exibir No, that doesnt work.
2. Escreva uma funo que pea ao usurio para digitar valores para a, b, c e n, os converta em
nmeros inteiros e use check_fermat para verificar se violam o teorema de Fermat.

Exerccio 5.3
Se voc tiver trs gravetos, pode ser que consiga arranj-los em um tringulo ou no. Por exemplo, se
um dos gravetos tiver 12 polegadas de comprimento e outros dois tiverem uma polegada de
comprimento, no ser possvel fazer com que os gravetos curtos se encontrem no meio. H um teste
simples para ver se possvel formar um tringulo para quaisquer trs comprimentos:
Se algum dos trs comprimentos for maior que a soma dos outros dois, ento voc no pode formar um
tringulo. Seno, voc pode. (Se a soma de dois comprimentos igualar o terceiro, eles formam um
tringulo chamado degenerado.)
1. Escreva uma funo chamada is_triangle que receba trs nmeros inteiros como
argumentos, e que imprima Yes ou No, dependendo da possibilidade de formar ou no um
tringulo de gravetos com os comprimentos dados.
2. Escreva uma funo que pea ao usurio para digitar trs comprimentos de gravetos, os
converta em nmeros inteiros e use is_triangle para verificar se os gravetos com os
comprimentos dados podem formar um tringulo.

Exerccio 5.4
Qual a sada do seguinte programa? Desenhe um diagrama da pilha que mostre o estado do programa
quando exibir o resultado.
def recurse(n, s):
if n == 0:
print(s)
else:
recurse(n-1, n+s)

recurse(3, 0)

1. O que aconteceria se voc chamasse esta funo desta forma: recurse(-1, 0)?
51

2. Escreva uma docstring que explique tudo o que algum precisaria saber para usar esta funo (e
mais nada).
Os seguintes exerccios usam o mdulo turtle, descrito no Captulo 4:

Exerccio 5.5
Leia a prxima funo e veja se consegue compreender o que ela faz (veja os exemplos no Captulo 4).
Ento execute-a e veja se acertou.
def draw(t, length, n):
if n == 0:
return
angle = 50
t.fd(length * n)
t.lt(angle)
draw(t, length, n-1)
t.rt(2 * angle)
draw(t, length, n-1)
t.lt(angle)
t.bk(length * n)

Exerccio 5.6

Figura 5.2 Uma curva de Koch.


A curva de Koch um fractal que parece com o da Figura 5.2. Para desenhar uma curva de Koch com o
comprimento x, tudo o que voc tem que fazer :
1. Desenhe uma curva de Koch com o comprimento x/3.
2. Vire 60 graus esquerda.
3. Desenhe uma curva de Koch com o comprimento x/3.
4. Vire 120 graus direita.
52

5. Desenhe uma curva de Koch com o comprimento x/3.


6. Vire 60 graus esquerda.
7. Desenhe uma curva de Koch com o comprimento x/3.
A exceo se x for menor que 3: neste caso, voc pode desenhar apenas uma linha reta com o
comprimento x.
1. Escreva uma funo chamada koch que receba um turtle e um comprimento como parmetros, e
use o turtle para desenhar uma curva de Koch com o comprimento dado.
2. Escreva uma funo chamada snowflake que desenhe trs curvas de Koch para fazer o traado
de um floco de neve.
Soluo: http://thinkpython2.com/code/koch.py.
1. A curva de Koch pode ser generalizada de vrios modos. Veja exemplos em
http://en.wikipedia.org/wiki/Koch_snowflake e implemente o seu favorito.

Captulo 6: Funes com resultado


Muitas das funes do Python que usamos, como as matemticas, produzem valores de retorno. Mas as
funes que escrevemos at agora so todas nulas: tm um efeito, como exibir um valor ou mover uma
tartaruga, mas no tm um valor de retorno. Neste captulo voc aprender a escrever funes com
resultados.

6.1 - Valores de retorno


A chamada de funo gera um valor de retorno, que normalmente atribumos a uma varivel ou usamos
como parte de uma expresso.
e = math.exp(1.0)
height = radius * math.sin(radians)

As funes que descrevemos, por enquanto, so todas nulas. Resumindo, elas no tm valores de
retorno; mais precisamente, o seu valor de retorno None.
Neste captulo veremos (finalmente) como escrever funes com resultados. O primeiro exemplo
area, que devolve a rea de um crculo com o raio dado:
def area(radius):
a = math.pi * radius**2
return a

J vimos a instruo return, mas em uma funo com resultado ela inclui uma expresso. Esta instruo
significa: Volte imediatamente desta funo e use a seguinte expresso como valor de retorno. A
53

expresso pode ser arbitrariamente complicada, ento poderamos ter escrito esta funo de forma mais
concisa:

def area(radius):
return math.pi * radius**2

Por outro lado, variveis temporrias como a, tornam a depurao mais fcil.
s vezes, til ter vrias instrues de retorno, uma em cada ramo de uma condicional:
def absolute_value(x):
if x < 0:
return -x
else:
return x

Como essas instrues return esto em uma condicional alternativa, apenas uma executada.
Logo que uma instruo de retorno seja executada, a funo termina sem executar nenhuma instruo
subsequente. Qualquer cdigo que aparea depois de uma instruo return, ou em qualquer outro lugar
que o fluxo da execuo no atinja, chamado de cdigo morto.
Em uma funo com resultado, uma boa ideia garantir que cada caminho possvel pelo programa
atinja uma instruo return. Por exemplo:
def absolute_value(x):
if x < 0:
return -x
if x > 0:
return x

Essa funo incorreta porque se x for 0, nenhuma condio verdade, e a funo termina sem chegar
a uma instruo return. Se o fluxo de execuo chegar ao fim de uma funo, o valor de retorno
None, que no o valor absoluto de 0:
>>> absolute_value(0)
None

A propsito, o Python oferece uma funo integrada chamada abs, que calcula valores absolutos.
Como exerccio, escreva uma funo compare que receba dois valores, x e y, e retorne 1 se x > y, 0 se x
== y e -1 se x < y.

6.2 - Desenvolvimento incremental


Conforme voc escrever funes maiores, pode ser que passe mais tempo as depurando.
54

Para lidar com programas cada vez mais complexos, voc pode querer tentar usar um processo
chamado de desenvolvimento incremental. A meta do desenvolvimento incremental evitar longas
sesses de depurao, acrescentando e testando pequenas partes do cdigo de cada vez.
Como um exemplo, vamos supor que voc queira encontrar a distncia entre dois pontos dados pelas
coordenadas (x1, y1) e(x2, y2). Pelo teorema de Pitgoras, a distncia :

O primeiro passo pensar como uma funo distance deveria ser no Python. Em outras palavras, quais
so as entradas (parmetros) e qual a sada (valor de retorno)?
Nesse caso, as entradas so dois pontos que voc pode representar usando quatro nmeros. O valor de
retorno a distncia representada por um valor de ponto flutuante.
Imediatamente, possvel escrever um rascunho da funo:
def distance(x1, y1, x2, y2):
return 0.0

Claro que esta verso no calcula distncias; sempre retorna zero. Mas est sintaticamente correta, e
pode ser executada, o que significa que voc pode test-la antes de torn-la mais complicada.
Para testar a nova funo, chame-a com argumentos de amostra:
>>> distance(1, 2, 4, 6)
0.0

Escolhi esses valores para que a distncia horizontal seja 3 e a distncia vertical, 4; assim, o resultado
final 5, a hipotenusa de um tringulo 3-4-5. Ao testar uma funo, til saber a resposta certa.
Neste ponto confirmamos que a funo est sintaticamente correta, e podemos comear a acrescentar
cdigo ao corpo. Um prximo passo razovel encontrar as diferenas x2 x1 e y2 y1. A prxima
verso guarda esses valores em variveis temporrias e os exibe:
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print('dx is', dx)
print('dy is', dy)
return 0.0

Se a funo estiver funcionando, deve exibir dx is 3 e dy is 4. Nesse caso sabemos que a funo est
recebendo os argumentos corretos e executando o primeiro clculo acertadamente. Se no, h poucas
linhas para verificar.
Depois calculamos a soma dos quadrados de dx e dy:
def distance(x1, y1, x2, y2):
dx = x2 - x1
55

dy = y2 - y1
dsquared = dx**2 + dy**2
print('dsquared is: ', dsquared)
return 0.0

Nesta etapa voc executaria o programa mais uma vez e verificaria a sada (que deve ser 25).
Finalmente, pode usar math.sqrt para calcular e devolver o resultado:
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dsquared = dx**2 + dy**2
result = math.sqrt(dsquared)
return result

Se funcionar corretamente, pronto. Seno, uma ideia exibir o valor result antes da instruo de
retorno.
A verso final da funo no exibe nada ao ser executada; apenas retorna um valor. As instrues print
que escrevemos so teis para depurao, mas assim que conferir se a funo est funcionando voc
deve retir-las. Cdigos desse tipo so chamados de scaffolding (andaime) porque so teis para
construir o programa, mas no so parte do produto final.
Ao comear, voc deveria acrescentar apenas uma linha ou duas de cdigo de cada vez. Conforme
adquira mais experincia, poder escrever e depurar parcelas maiores. De qualquer forma, o
desenvolvimento incremental pode economizar muito tempo de depurao.
Os principais aspectos do processo so:
1. Comece com um programa que funcione e faa pequenas alteraes incrementais. Se houver um
erro em qualquer ponto, ser bem mais fcil encontr-lo.
2. Use variveis para guardar valores intermedirios, assim poder exibi-los e verific-los.
3. Uma vez que o programa esteja funcionando, voc pode querer remover uma parte do
scaffolding ou consolidar vrias instrues em expresses compostas, mas apenas se isso no
tornar o programa difcil de ler.
Como exerccio, use o desenvolvimento incremental para escrever uma funo chamada hypotenuse,
que devolva o comprimento da hipotenusa de um tringulo retngulo dados os comprimentos dos
outros dois lados como argumentos. Registre cada etapa do processo de desenvolvimento no decorrer
do processo.
56

6.3 - Composio
Como voc j deveria esperar a essa altura, possvel chamar uma funo de dentro de outra. Como
exemplo, escreveremos uma funo que recebe dois pontos, o centro do crculo e um ponto no
permetro, para calcular a rea do crculo.
Suponha que o ponto do centro seja guardado nas variveis xc e yc e o ponto de permetro est em xp e
yp. O primeiro passo deve ser encontrar o raio do crculo, que a distncia entre os dois pontos.
Acabamos de escrever uma funo, distance, que faz isto:
radius = distance(xc, yc, xp, yp)

O prximo passo deve ser encontrar a rea de um crculo com aquele raio; acabamos de escrever isso
tambm:
result = area(radius)

Encapsulando esses passos em uma funo, temos:


def circle_area(xc, yc, xp, yp):
radius = distance(xc, yc, xp, yp)
result = area(radius)
return result

As variveis temporrias radius e result so teis para desenvolvimento e depurao, e uma vez que o
programa esteja funcionando podemos torn-lo mais conciso compondo chamadas de funo:
def circle_area(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))

6.4 - Funes booleanas


As funes podem retornar booleans, o que pode ser conveniente para esconder testes complicados
dentro de funes. Por exemplo:
def is_divisible(x, y):
if x % y == 0:
return True
else:
return False

comum dar nomes de funes booleanas que paream perguntas de sim ou no; is_divisible retorna
True ou False para indicar se x divisvel por y.
Aqui est um exemplo:
>>> is_divisible(6, 4)
False
>>> is_divisible(6, 3)
True
57

O resultado do operador == um booleano, ento podemos escrever a funo de forma mais concisa,
retornando-o diretamente:
def is_divisible(x, y):
return x % y == 0

As funes booleanas muitas vezes so usadas em instrues condicionais:


if is_divisible(x, y):
print('x is divisible by y')

Pode ser tentador escrever algo assim:


if is_divisible(x, y) == True:
print('x is divisible by y')

Mas a comparao extra desnecessria.


Como um exerccio, escreva uma funo is_between(x, y, z) que retorne True, se x y z, ou
False, se no for o caso.

6.5 - Mais recursividade


Cobrimos apenas um pequeno subconjunto do Python, mas talvez seja bom voc saber que este
subconjunto uma linguagem de programao completa, ou seja, qualquer coisa que possa ser
calculada pode ser expressa nesta linguagem. Qualquer programa que j foi escrito pode ser reescrito
apenas com os recursos da linguagem que voc aprendeu at agora (na verdade, seria preciso alguns
comandos para dispositivos de controle como mouse, discos etc., mas isso tudo).
Comprovar esta declarao um exerccio nada trivial realizado pela primeira vez por Alan Turing, um
dos primeiros cientistas da computao (alguns diriam que ele foi matemtico, mas muitos dos
primeiros cientistas da computao comearam como matemticos). Assim, conhecida como a Tese
de Turing. Para uma exposio mais completa (e exata) da Tese de Turing, recomendo o livro de
Michael Sipser, Introduction to the Theory of Computation (Introduo teoria da computao, Course
Technology, 2012).
Para dar uma ideia do que podemos fazer com as ferramentas que aprendeu at agora, avaliaremos
algumas funes matemticas definidas recursivamente. Uma definio recursiva semelhante a uma
definio circular, no sentido de que a definio contm uma referncia coisa que definida. Uma
definio realmente circular no muito til:
vorpal
Adjetivo usado para descrever algo que vorpal.

Ver uma definio assim no dicionrio pode ser irritante. Por outro lado, se procurar a definio da
funo de fatorial, denotada pelo smbolo !, voc pode encontrar algo assim:
58

0! = 1
n! = n(n 1)!

Esta definio diz que o fatorial de 0 1, e o fatorial de qualquer outro valor, n, n multiplicado pelo
fatorial de n-1.
Ento 3! 3 vezes 2!, que 2 vezes 1!, que 1 vez 0!. Juntando tudo, 3! igual a 3 vezes 2 vezes 1
vezes 1, que 6.
Se puder escrever uma definio recursiva de algo, voc poder escrever um programa em Python que
a avalie. O primeiro passo deve ser decidir quais parmetros ela deve ter. Neste caso, deve estar claro
que factorial recebe um nmero inteiro:
def factorial(n):

Se o argumento for 0, tudo que temos de fazer retornar 1:


def factorial(n):
if n == 0:
return 1

Seno, e a que fica interessante, temos que fazer uma chamada recursiva para encontrar o fatorial de
n-1 e ento multiplic-lo por n:
def factorial(n):
if n == 0:
return 1
else:
recurse = factorial(n-1)
result = n * recurse
return result

O fluxo de execuo deste programa semelhante ao fluxo de countdown em Recursividade, na


pgina 81. Se chamarmos factorial com o valor 3:
Como 3 no 0, tomamos o segundo ramo e calculamos o fatorial de n-1
Como 2 no 0, tomamos o segundo ramo e calculamos o fatorial de n-1
Como 1 no 0, tomamos o segundo ramo e calculamos o fatorial de n-1
Como 0 igual a 0, tomamos o primeiro ramo e devolvemos 1 sem fazer mais chamadas
recursivas.
O valor de retorno, 1, multiplicado por n, que 1, e o resultado devolvido.
O valor de retorno, 1, multiplicado por n, que 2, e o resultado devolvido.
O valor devolvido (2) multiplicado por n, que 3, e o resultado, 6, torna-se o valor devolvido pela
chamada de funo que comeou o processo inteiro.
59

A Figura 6.1 mostra como o diagrama da pilha para esta sequncia de chamadas de funo.

Figura 6.1 Diagrama da pilha para factorial.


Os valores devolvidos so mostrados ao serem passados de volta at o alto da pilha. Em cada frame, o
valor devolvido o valor de result, que o produto de n e recurse.

No ltimo frame, as variveis locais recurse e result no existem, porque o ramo que os cria no
executado.

6.6 - Salto de f
Seguir o fluxo da execuo uma forma de ler programas, mas poder ser trabalhoso demais. Uma
alternativa o que chamo de salto de f (leap of faith). Ao chegar a uma chamada de funo, em vez
de seguir o fluxo de execuo suponha que a funo esteja funcionando corretamente e que est
retornando o resultado certo.
Na verdade, voc j est praticando este salto de f quando usa funes integradas. Quando chama
math.cos ou math.exp, voc no examina o corpo dessas funes. Apenas supe que funcionem porque
as pessoas que as escreveram eram bons programadores.
O mesmo acontece ao chamar uma das suas prprias funes. Por exemplo, em Funes booleanas,
na pgina 97, escrevemos uma funo chamada is_divisible que determina se um nmero divisvel
por outro. Uma vez que estejamos convencidos de que esta funo est correta examinando o cdigo
e testando podemos usar a funo sem ver o corpo novamente.
O mesmo verdade para programas recursivos. Quando chega chamada recursiva, em vez de seguir o
fluxo de execuo, voc deveria supor que a chamada recursiva funcione (devolva o resultado correto)
e ento perguntar-se: Supondo que eu possa encontrar o fatorial de n-1, posso calcular o fatorial de
n?. claro que pode, multiplicando por n.
60

Naturalmente, um pouco estranho supor que a funo funcione corretamente quando ainda no
terminou de escrev-la, mas por isso que se chama um salto de f!

6.7 - Mais um exemplo


Depois do factorial, o exemplo mais comum de uma funo matemtica definida recursivamente
fibonacci, que tem a seguinte definio (ver http://en.wikipedia.org/wiki/Fibonacci_number):
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n 1) + fibonacci(n 2)

Traduzida para Python, ela fica assim:


def fibonacci (n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)

Se tentar seguir o fluxo de execuo aqui, at para valores razoavelmente pequenos de n, sua cabea
explode. Porm, seguindo o salto de f, supondo que as duas chamadas recursivas funcionem
corretamente, ento claro que vai receber o resultado correto adicionando-as juntas.

6.8 - Verificao de tipos


O que acontece se chamarmos factorial e usarmos 1.5 como argumento?
>>> factorial(1.5)
RuntimeError: Maximum recursion depth exceeded

Parece uma recursividade infinita. No entanto, por que isso acontece? A funo tem um caso-base
quando n == 0. Mas se n no um nmero inteiro, podemos perder o caso-base e recorrer para sempre.
Na primeira chamada recursiva, o valor de n 0.5. No seguinte, -0.5. Da, torna-se menor (mais
negativo), mas nunca ser 0.
Temos duas escolhas. Podemos tentar generalizar a funo factorial para trabalhar com nmeros de
ponto flutuante, ou podemos fazer factorial controlar o tipo de argumento que recebe. A primeira opo
chama-se funo gamma e est um pouco alm do alcance deste livro. Ento usaremos a segunda
opo.
Podemos usar a funo integrada isinstance para verificar o tipo de argumento. E vamos aproveitar para
verificar tambm se o argumento positivo:
def factorial (n):
61

if not isinstance(n, int):


print('Factorial is only defined for integers.')
return None
elif n < 0:
print('Factorial is not defined for negative integers.')
return None
elif n == 0:
return 1
else:
return n * factorial(n-1)

O primeiro caso-base lida com nmeros no inteiros; o segundo, com nmeros inteiros negativos. Em
ambos os casos o programa exibe uma mensagem de erro e retorna None para indicar que algo deu
errado:
>>> factorial('fred')
Factorial is only defined for integers.
None
>>> factorial(-2)
Factorial is not defined for negative integers.
None

Se passarmos por ambas as verificaes, sabemos que n positivo ou zero, ento podemos comprovar
que a recursividade termina.
Esse programa demonstra um padro s vezes chamado de guardio. As duas primeiras condicionais
atuam como guardis, protegendo o cdigo que segue de valores que poderiam causar um erro. As
guardis permitem comprovar a correo do cdigo.
Na Busca reversa, na pgina 165, veremos uma alternativa mais flexvel para a exibio de uma
mensagem de erro: o levantamento de excees.

6.9 - Depurao
Quebrar um grande programa em funes menores cria controles naturais da depurao. Se uma funo
no estiver funcionando, h trs possibilidades a considerar:
H algo errado com os argumentos que a funo est recebendo; uma precondio est sendo
violada.
H algo errado com a funo; uma ps-condio foi violada.

H algo errado com o valor de retorno ou a forma na qual est sendo usado.

Para excluir a primeira possibilidade, voc pode acrescentar uma instruo print no incio da funo e
exibir os valores dos parmetros (e talvez os seus tipos). Ou escrever cdigo que verifique as
precondies explicitamente.
62

Se os parmetros parecerem bons, acrescente uma instruo print antes de cada instruo return e exiba
o valor de retorno. Se possvel, verifique o resultado mo. Uma possibilidade chamar a funo com
valores facilitem a verificao do resultado (como no Desenvolvimento incremental, da pgina 94).
Se a funo parecer funcionar, veja a chamada da funo para ter certeza de que o valor de retorno est
sendo usado corretamente (ou se est sendo usado mesmo!).
Acrescentar instrues de exibio no comeo e no fim de uma funo pode ajudar a tornar o fluxo de
execuo mais visvel. Por exemplo, aqui est uma verso de factorial com instrues de exibio:
def factorial(n):
space = ' ' * (4 * n)
print(space, 'factorial', n)
if n == 0:
print(space, 'returning 1')
return 1
else:
recurse = factorial(n-1)
result = n * recurse
print(space, 'returning', result)
return result

space uma string de caracteres especiais que controla a endentao da sada. Aqui est o resultado de
factorial(4):
factorial 4
factorial 3
factorial 2
factorial 1
factorial 0
returning 1
returning 1
returning 2
returning 6
returning 24

Se o fluxo de execuo parecer confuso a voc, este tipo de sada pode ser til. Leva um tempo para
desenvolver um scaffolding eficaz, mas um pouco dele pode economizar muita depurao.

6.10 - Glossrio
varivel temporria
Uma varivel usada para guardar um valor intermedirio em um clculo complexo.
cdigo morto
A parte de um programa que nunca pode ser executada, muitas vezes porque aparece depois de
uma instruo return.
desenvolvimento incremental
Um plano de desenvolvimento de programa para evitar a depurao, que acrescenta e testa
poucas linhas de cdigo de cada vez.
scaffolding (andaime)
63

O cdigo que se usa durante o desenvolvimento de programa, mas que no faz parte da verso
final.
guardio
Um padro de programao que usa uma instruo condicional para verificar e lidar com
circunstncias que possam causar erros.

6.11 - Exerccios
Exerccio 6.1
Desenhe um diagrama da pilha do seguinte programa. O que o programa exibe?
def b(z):
prod = a(z, z)
print(z, prod)
return prod

def a(x, y):


x = x + 1
return x * y

def c(x, y, z):


total = x + y + z
square = b(total)**2
return square

x = 1
y = x + 1
print(c(x, y+3, x+y))

Exerccio 6.2
A funo de Ackermann, A(m, n), definida assim:

Veja http://en.wikipedia.org/wiki/Ackermann_function. Escreva uma funo denominada ack que


avalie a funo de Ackermann. Use a sua funo para avaliar ack(3, 4), cujo resultado deve ser
125. O que acontece para valores maiores de m e n?
Soluo: http://thinkpython2.com/code/ackermann.py.

Exerccio 6.3
Um palndromo uma palavra que se soletra da mesma forma nos dois sentidos, como osso e
reviver. Recursivamente, uma palavra um palndromo se a primeira e ltima letras forem iguais e o
meio for um palndromo.
64

As funes seguintes recebem uma string como argumento e retornam as letras iniciais, finais e do
meio das palavras:
def first(word):
return word[0]
def last(word):
return word[-1]
def middle(word):
return word[1:-1]

Veremos como funcionam no Captulo 8.


1. Digite essas funes em um arquivo chamado palindrome.py e teste-as. O que acontece se
chamar middle com uma string de duas letras? Uma letra? E se a string estiver vazia, escrita
com '' e no contiver nenhuma letra?

2. Escreva uma funo chamada is_palindrome que receba uma string como argumento e
retorne True se for um palndromo e False se no for. Lembre-se de que voc pode usar a funo
integrada len para verificar o comprimento de uma string.
Soluo: http://thinkpython2.com/code/palindrome_soln.py.

Exerccio 6.4
Um nmero a uma potncia de b se for divisvel por b e a/b for uma potncia de b. Escreva uma
funo chamada is_power que receba os parmetros a e b e retorne True se a for uma potncia de b.
Dica: pense no caso-base.

Exerccio 6.5
O maior divisor comum (MDC, ou GCD em ingls) de a e b o maior nmero que divide ambos sem
sobrar resto.
Um modo de encontrar o MDC de dois nmeros observar qual o resto r quando a dividido por b,
verificando que gcd(a, b) = gcd(b, r). Como caso-base, podemos usar gcd(a, 0) = a.
Escreva uma funo chamada gcd que receba os parmetros a e b e devolva o maior divisor comum.
Crdito: Este exerccio baseado em um exemplo do livro de Abelson e Sussman, Structure and
Interpretation of Computer Programs (Estrutura e interpretao de programas de computador, MIT
Press, 1996).

Captulo 7: Iterao
Este captulo sobre a iterao, a capacidade de executar um bloco de instrues repetidamente. Vimos
um tipo de iterao, usando a recursividade, em Recursividade, na pgina 81. Vimos outro tipo,
65

usando um loop for, em Repetio simples, na pgina 65. Neste captulo veremos ainda outro tipo,
usando a instruo while. Porm, primeiro quero falar um pouco mais sobre a atribuio de variveis.

7.1 - Reatribuio
Pode ser que voc j tenha descoberto que permitido fazer mais de uma atribuio para a mesma
varivel. Uma nova atribuio faz uma varivel existente referir-se a um novo valor (e deixar de referir-
se ao valor anterior).
>>> x = 5
>>> x
5
>>> x = 7
>>> x
7

A primeira vez que exibimos x, seu valor 5; na segunda vez, seu valor 7.
A Figura 7.1 mostra que a reatribuio parece um diagrama de estado.
Neste ponto quero tratar de uma fonte comum de confuso. Como o Python usa o sinal de igual (=)
para atribuio, tentador interpretar uma afirmao como a = b como uma proposio matemtica de
igualdade; isto , a declarao de que a e b so iguais. Mas esta uma interpretao equivocada.
Em primeiro lugar, a igualdade uma relao simtrica e a atribuio no . Por exemplo, na
matemtica, se a=7 ento 7=a. Mas no Python, a instruo a = 7 legal e 7 = a no .
Alm disso, na matemtica, uma proposio de igualdade verdadeira ou falsa para sempre. Se a=b
agora, ento a sempre ser igual a b. No Python, uma instruo de atribuio pode tornar duas variveis
iguais, mas elas no precisam se manter assim:
>>> a = 5
>>> b = a # a e b agora so iguais
>>> a = 3 # a e b no so mais iguais
>>> b
5

A terceira linha modifica o valor de a, mas no muda o valor de b, ento elas j no so iguais.
A reatribuio de variveis muitas vezes til, mas voc deve us-la com prudncia. Se os valores das
variveis mudarem frequentemente, isso pode dificultar a leitura e depurao do cdigo.
Figura 7.1 Diagrama de estado.

Figura 7.1 Diagrama de estado da varivel x.


66

7.2 - Atualizao de variveis


Um tipo comum de reatribuio uma atualizao, onde o novo valor da varivel depende do velho.
>>> x = x + 1

Isso significa pegue o valor atual de x, acrescente um, e ento atualize x para o novo valor.
Se voc tentar atualizar uma varivel que no existe, recebe um erro porque o Python avalia o lado
direito antes de atribuir um valor a x:
>>> x = x + 1
NameError: name 'x' is not defined

Antes de poder atualizar uma varivel preciso inicializ-la, normalmente com uma atribuio simples:
>>> x = 0
>>> x = x + 1

Atualizar uma varivel acrescentando 1 chama-se incremento; subtrair 1 chama-se decremento.

7.3 - Instruo while


Os computadores muitas vezes so usados para automatizar tarefas repetitivas. A repetio de tarefas
idnticas ou semelhantes sem fazer erros algo que os computadores fazem bem e as pessoas no. Em
um programa de computador, a repetio tambm chamada de iterao.
J vimos duas funes, countdown e print_n, que se repetem usando recursividade. Como a
iterao bem comum, o Python fornece recursos de linguagem para facilit-la. Um deles a instruo
for que vimos em Repetio simples, na pgina 65. Voltaremos a isso mais adiante.

Outra a instruo while. Aqui est uma verso de countdown que usa a instruo while:
def countdown(n):
while n > 0:
print(n)
n = n - 1
print('Blastoff!')

Voc at pode ler a instruo while como se fosse uma traduo do ingls. Significa Enquanto n for
maior que 0, mostre o valor de n e ento decremente n. Quando chegar a 0, mostre a palavra Blastoff!

Mais formalmente, aqui est o fluxo de execuo para uma instruo while:
1. Determine se a condio verdadeira ou falsa.
2. Se for falsa, saia da instruo while e continue a execuo da prxima instruo.
3. Se a condio for verdadeira, execute o corpo e ento volte ao passo 1.
67

Este tipo de fluxo chama-se loop (lao), porque o terceiro passo faz um loop de volta ao topo.
O corpo do loop deve mudar o valor de uma ou mais variveis para que, a certa altura, a condio fique
falsa e o loop termine. Seno o loop vai se repetir para sempre, o que chamado de loop infinito. Uma
fonte infindvel de divertimento para cientistas da computao a observao das instrues no
xampu, Faa espuma, enxgue, repita, que so parte de um loop infinito.
No caso de countdown, podemos provar que o loop termina: se n for zero ou negativo, o loop nunca
executado. Seno, n fica cada vez menor ao passar pelo loop, at eventualmente chegar a 0.

Para alguns outros loops, no to fcil perceber isso. Por exemplo:


def sequence(n):
while n != 1:
print(n)
if n % 2 == 0: # n par
n = n / 2
else: # n mpar
n = n * 3 + 1

A condio deste loop n != 1, ento o loop continuar at que n seja 1, o que torna a condio
falsa.
Cada vez que passa pelo loop, o programa produz o valor de n e ento verifica se par ou mpar. Se for
par, n dividido por 2. Se for mpar, o valor de n substitudo por n * 3 + 1. Por exemplo, se o
argumento passado a sequence for 3, os valores resultantes de n so 3, 10, 5, 16, 8, 4, 2, 1.

Como n s vezes aumenta e s vezes diminui, no h nenhuma prova bvia de que n chegar
eventualmente a 1, ou que o programa terminar. Para alguns valores de n, podemos provar o trmino.
Por exemplo, se o valor inicial for uma potncia de dois, n ser par cada vez que passar pelo loop at
que chegue a 1. O exemplo anterior termina com uma sequncia assim, que inicia com 16.
A questo difcil se podemos provar que este programa termina para todos os valores positivos de n.
Por enquanto, ningum foi capaz de comprovar ou refutar isso! (Veja
http://en.wikipedia.org/wiki/Collatz_conjecture.)
Como um exerccio, reescreva a funo print_n de Recursividade, na pgina 81, usando a iterao em
vez da recursividade.

7.4 - break
s vezes voc no sabe que est na hora de terminar um loop at que j esteja na metade do corpo.
Neste caso pode usar a instruo break para sair do loop.
Por exemplo, suponha que voc quer receber uma entrada do usurio at que este digite done. Voc
pode escrever:
68

while True:
line = input('> ')
if line == 'done':
break
print(line)
print('Done!')

A condio do loop True, que sempre verdade, ento o loop roda at que chegue instruo de
interrupo.
Cada vez que passa pelo loop, o programa apresenta ao usurio um colchete angular. Se o usurio
digitar done, a instruo break sai do loop. Seno, o programa ecoa o que quer que o usurio digite e
volta ao topo do loop. Aqui est uma amostra de execuo:
> not done
not done
> done
Done!

Esta forma de escrever loops while comum porque podemos verificar a condio em qualquer lugar
do loop (no somente no topo) e podemos exprimir a condio de parada afirmativamente (pare
quando isto acontecer) em vez de negativamente (continue a seguir at que isto acontea).

7.5 - Razes quadradas


Loops muitas vezes so usados em programas que calculam resultados numricos, comeando com
uma resposta aproximada e melhorando-a iterativamente.
Por exemplo, uma forma de calcular razes quadradas o mtodo de Newton. Suponha que voc queira
saber a raiz quadrada de a. Se comear com quase qualquer estimativa, x, possvel calcular uma
estimativa melhor com a seguinte frmula:

Por exemplo, se a for 4 e x for 3:


>>> a = 4
>>> x = 3
>>> y = (x + a/x) / 2
>>> y
2.16666666667

O resultado mais prximo resposta correta ( = 2). Se repetirmos o processo com a nova
estimativa, chegamos ainda mais perto:
>>> x = y
>>> y = (x + a/x) / 2
>>> y
2.00641025641
69

Depois de algumas atualizaes, a estimativa quase exata:


>>> x = y
>>> y = (x + a/x) / 2
>>> y
2.00001024003
>>> x = y
>>> y = (x + a/x) / 2
>>> y
2.00000000003

Em geral, no sabemos com antecedncia quantos passos so necessrios para chegar resposta
correta, mas sabemos quando chegamos l porque a estimativa para de mudar:
>>> x = y
>>> y = (x + a/x) / 2
>>> y
2.0
>>> x = y
>>> y = (x + a/x) / 2
>>> y
2.0

Quando y == x, podemos parar. Aqui est um loop que comea com uma estimativa inicial, x, e a
melhora at que deixe de mudar:
while True:
print(x)
y = (x + a/x) / 2
if y == x:
break
x = y

Para a maior parte de valores de a funciona bem, mas pode ser perigoso testar a igualdade de um float.
Os valores de ponto flutuante so aproximadamente corretos: a maioria dos nmeros racionais, como
1/3, e nmeros irracionais, como , no podem ser representados exatamente com um float.
Em vez de verificar se x e y so exatamente iguais, mais seguro usar a funo integrada abs para
calcular o valor absoluto ou magnitude da diferena entre eles:
if abs(y-x) < epsilon:
break

Onde epsilon tem um valor como 0.0000001, que determina a proximidade desejada entre x e y.

7.6 - Algoritmos
O mtodo de Newton um exemplo de um algoritmo: um processo mecnico para resolver uma
categoria de problemas (neste caso, calcular razes quadradas).
70

Para entender o que um algoritmo, pode ser til comear com algo que no um algoritmo. Quando
aprendeu a multiplicar nmeros de um dgito, voc provavelmente memorizou a tabuada. Ou seja, voc
memorizou 100 solues especficas. Este tipo de conhecimento no algortmico.
No entanto, se voc foi preguioso, poderia ter aprendido alguns truques. Por exemplo, para
encontrar o produto de n e 9, pode escrever n-1 como o primeiro dgito e 10-n como o segundo
dgito. Este truque uma soluo geral para multiplicar qualquer nmero de dgito nico por 9. Isto
um algoritmo!
De forma semelhante, as tcnicas que aprendeu, como o transporte na adio, o emprstimo na
subtrao e a diviso longa so todos algoritmos. Uma das caractersticas de algoritmos que eles no
exigem inteligncia para serem executados. So processos mecnicos, nos quais cada passo segue a
partir do ltimo, de acordo com um conjunto de regras simples.
A execuo de algoritmos maante, mas projet-los interessante, intelectualmente desafiador e uma
parte central da Cincia da Computao.
Algumas coisas que as pessoas fazem naturalmente, sem dificuldade ou pensamento consciente, so as
mais difceis para exprimir algoritmicamente. A compreenso de linguagem natural um bom exemplo.
Todos ns o fazemos, mas por enquanto ningum foi capaz de explicar como o fazemos, pelo menos
no na forma de um algoritmo.

7.7 - Depurao
Ao comear a escrever programas maiores, pode ser que voc passe mais tempo depurando. Mais
cdigo significa mais possibilidades fazer erros e mais lugares para esconder defeitos.
Uma forma de cortar o tempo de depurao depurar por bisseo. Por exemplo, se h 100 linhas no
seu programa e voc as verifica uma a uma, seriam 100 passos a tomar.
Em vez disso, tente quebrar o problema pela metade. Olhe para o meio do programa, ou perto disso,
para um valor intermedirio que possa verificar. Acrescente uma instruo print (ou outra coisa que
tenha um efeito verificvel) e execute o programa.
Se a verificao do ponto central for incorreta, deve haver um problema na primeira metade do
programa. Se for correta, o problema est na segunda metade.
Cada vez que executar uma verificao assim, divida ao meio o nmero de linhas a serem verificadas.
Depois de seis passos (que menos de 100), voc teria menos de uma ou duas linhas do cdigo para
verificar, pelo menos em teoria.
Na prtica, nem sempre claro o que representa o meio do programa e nem sempre possvel
verific-lo. No faz sentido contar linhas e encontrar o ponto central exato. Em vez disso, pense em
lugares no programa onde poderia haver erros e lugares onde fcil inserir um ponto de verificao.
71

Ento escolha um lugar onde as possibilidades so basicamente as mesmas de que o defeito esteja antes
ou depois da verificao.

7.8 - Glossrio
reatribuio
Atribuir um novo valor a uma varivel que j existe.
atualizao
Uma atribuio onde o novo valor da varivel dependa do velho.
inicializao
Uma atribuio que d um valor inicial a uma varivel que ser atualizada.
incremento
Uma atualizao que aumenta o valor de uma varivel (normalmente por uma unidade).
decremento
Uma atualizao que reduz o valor de uma varivel.
iterao
Execuo repetida de um grupo de instrues, usando uma chamada da funo recursiva ou um
loop.
loop infinito
Um loop no qual a condio de trmino nunca satisfeita.
algoritmo
Um processo geral para resolver uma categoria de problemas.

7.9 - Exerccios
Exerccio 7.1
Copie o loop de Razes quadradas, na pgina 111, e encapsule-o em uma funo chamada mysqrt
que receba a como parmetro, escolha um valor razovel de x e devolva uma estimativa da raiz
quadrada de a.
Para testar, escreva uma funo denominada test_square_root, que exibe uma tabela como esta:
a mysqrt(a) math.sqrt(a) diff
- --------- ------------ ----
1.0 1.0 1.0 0.0
2.0 1.41421356237 1.41421356237 2.22044604925e-16
3.0 1.73205080757 1.73205080757 0.0
4.0 2.0 2.0 0.0
5.0 2.2360679775 2.2360679775 0.0
6.0 2.44948974278 2.44948974278 0.0
7.0 2.64575131106 2.64575131106 0.0
8.0 2.82842712475 2.82842712475 4.4408920985e-16
9.0 3.0 3.0 0.0
72

A primeira coluna um nmero, a; a segunda coluna a raiz quadrada de a calculada com mysqrt; a
terceira coluna a raiz quadrada calculada por math.sqrt; a quarta coluna o valor absoluto da
diferena entre as duas estimativas.

Exerccio 7.2
A funo integrada eval toma uma string e a avalia, usando o interpretador do Python. Por exemplo:
>>> eval('1 + 2 * 3')
7
>>> import math
>>> eval('math.sqrt(5)')
2.2360679774997898
>>> eval('type(math.pi)')
<class 'float'>

Escreva uma funo chamada eval_loop que iterativamente pea uma entrada ao usurio, a avalie
usando eval e exiba o resultado.

Ela deve continuar at que o usurio digite done; ento dever exibir o valor da ltima expresso
avaliada.

Exerccio 7.3
O matemtico Srinivasa Ramanujan encontrou uma srie infinita que pode ser usada para gerar uma
aproximao numrica de 1/:

Escreva uma funo chamada estimate_pi que use esta frmula para computar e devolver uma
estimativa de . Voc deve usar o loop while para calcular os termos da adio at que o ltimo termo
seja menor que 1e-15 (que a notao do Python para 10 ** 15). Voc pode verificar o resultado
comparando-o com math.pi.

Captulo 8: Strings
Strings no so como nmeros inteiros, de ponto flutuante ou booleanos. Uma string uma sequncia,
ou seja, uma coleo ordenada de outros valores. Neste captulo voc ver como acessar os caracteres
que compem uma string e aprender alguns mtodos que as strings oferecem.
73

8.1 - Uma string uma sequncia


Uma string uma sequncia de caracteres. Voc pode acessar um caractere de cada vez com o operador
de colchete:
>>> fruit = 'banana'
>>> letter = fruit[1]

A segunda instruo seleciona o caractere nmero 1 de fruit e o atribui a letter.


A expresso entre colchetes chama-se ndice. O ndice aponta qual caractere da sequncia voc quer
(da o nome).
Mas pode ser que voc no obtenha o que espera:
>>> letter
'a'

Para a maior parte das pessoas, a primeira letra de banana b, no a. Mas para os cientistas da
computao, o ndice uma referncia do comeo da string, e a referncia da primeira letra zero.
>>> letter = fruit[0]
>>> letter
'b'

Ento b a 0 (zersima) letra de banana, a a 1 (primeira) letra e n a 2 (segunda) letra.


Voc pode usar uma expresso que contenha variveis e operadores como ndice:
>>> i = 1
>>> fruit[i]
'a'
>>> fruit[i+1]
'n'

Porm, o valor do ndice tem que ser um nmero inteiro. Se no for, isso que aparece:
>>> letter = fruit[1.5]
TypeError: string indices must be integers

8.2 - len
len uma funo integrada que devolve o nmero de caracteres em uma string:
>>> fruit = 'banana'
>>> len(fruit)
6

Para obter a ltima letra de uma string, pode parecer uma boa ideia tentar algo assim:
>>> length = len(fruit)
>>> last = fruit[length]
IndexError: string index out of range
74

A razo de haver um IndexError aqui que no h nenhuma letra em banana com o ndice 6. Como a
contagem inicia no zero, as seis letras so numeradas de 0 a 5. Para obter o ltimo caractere, voc deve
subtrair 1 de length:
>>> last = fruit[length-1]
>>> last
'a'

Ou voc pode usar ndices negativos, que contam de trs para a frente a partir do fim da string. A
expresso fruit[-1] apresenta a ltima letra, fruit[-2] apresenta a segunda letra de trs para a frente, e
assim por diante.

8.3 - Travessia com loop for


Muitos clculos implicam o processamento de um caractere por vez em uma string. Muitas vezes
comeam no incio, selecionam um caractere por vez, fazem algo e continuam at o fim. Este modelo
do processamento chama-se travessia. Um modo de escrever uma travessia com o loop while:
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1

Este loop atravessa a string e exibe cada letra sozinha em uma linha. A condio do loop index <len
(fruit), ento quando index igual ao comprimento da string, a condio falsa e o corpo do loop no
mais executado. O ltimo caractere acessado aquele com o ndice len (fruit)-1, que o ltimo
caractere na string.
Como exerccio, escreva uma funo que receba uma string como argumento e exiba as letras de trs
para a frente, uma por linha.
Outra forma de escrever uma travessia com um loop for:
for letter in fruit:
print(letter)

Cada vez que o programa passar pelo loop, o caractere seguinte na string atribudo varivel letter. O
loop continua at que no sobre nenhum caractere.
O prximo exemplo mostra como usar a concatenao (adio de strings) e um loop for para gerar uma
srie abecedria (isto , em ordem alfabtica). No livro de Robert McCloskey, Make Way for Ducklings
(Abram caminho para os patinhos), os nomes dos patinhos so Jack, Kack, Lack, Mack, Nack, Ouack,
Pack e Quack. Este loop produz estes nomes em ordem:
prefixes = 'JKLMNOPQ'
suffix = 'ack'
for letter in prefixes:
75

print(letter + suffix)
A sada :
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack

Claro que no est exatamente certo porque Ouack e Quack foram mal soletrados. Como exerccio,
altere o programa para corrigir este erro.

8.4 - Fatiamento de strings


Um segmento de uma string chamado de fatia. Selecionar uma fatia como selecionar um caractere:
>>> s = 'Monty Python'
>>> s[0:5]
'Monty'
>>> s[6:12]
'Python'

O operador [n:m] retorna a parte da string do ensimo caractere ao emsimo caractere, incluindo
o primeiro, mas excluindo o ltimo. Este comportamento contraintuitivo, porm pode ajudar a
imaginar os ndices que indicam a parte entre os caracteres, como na Figura 8.1.

Figura 8.1 ndices de fatias.


Se voc omitir o primeiro ndice (antes dos dois pontos), a fatia comea no incio da string. Se omitir o
segundo ndice, a fatia vai ao fim da string:
>>> fruit = 'banana'
>>> fruit[:3]
'ban'
>>> fruit[3:]
'ana'

Se o primeiro ndice for maior ou igual ao segundo, o resultado uma string vazia, representada por
duas aspas:
>>> fruit = 'banana'
>>> fruit[3:3]
''
76

Uma string vazia no contm nenhum caractere e tem o comprimento 0, fora isso, igual a qualquer
outra string.
Continuando este exemplo, o que voc acha que fruit[:] significa? Teste e veja.

8.5 - Strings so imutveis


tentador usar o operador [] no lado esquerdo de uma atribuio, com a inteno de alterar um
caractere em uma string. Por exemplo:
>>> greeting = 'Hello, world!'
>>> greeting[0] = 'J'
TypeError: 'str' object does not support item assignment

O objeto neste caso a string e o item o caractere que voc tentou atribuir. Por enquanto, um
objeto a mesma coisa que um valor, mas refinaremos esta definio mais adiante (Objetos e valores,
no captulo 10).
A razo do erro que as strings so imutveis, o que significa que voc no pode alterar uma string
existente. O melhor que voc pode fazer criar uma string que seja uma variao da original:
>>> greeting = 'Hello, world!'
>>> new_greeting = 'J' + greeting[1:]
>>> new_greeting
'Jello, world!'

Esse exemplo concatena uma nova primeira letra a uma fatia de greeting. No tem efeito sobre a
string original.

8.6 - Buscando
O que faz a seguinte funo?
def find(word, letter):
index = 0
while index < len(word):
if word[index] == letter:
return index
index = index + 1
return-1

De certo modo, find o inverso do operador []. Em vez de tomar um ndice e extrair o caractere
correspondente, ele toma um caractere e encontra o ndice onde aquele caractere aparece. Se o
caractere no for encontrado, a funo retorna -1.
Esse o primeiro exemplo que vimos de uma instruo return dentro de um loop. Se
word[index] == letter, a funo sai do loop e retorna imediatamente.

Se o caractere no aparecer na string, o programa sai do loop normalmente e devolve -1.


77

Este modelo de clculo atravessar uma sequncia e retornar quando encontramos o que estamos
procurando chama-se busca.
Como exerccio, altere find para que tenha um terceiro parmetro: o ndice em word onde deve
comear a busca.

8.7 - Loop e contagem


O seguinte programa conta o nmero de vezes que a letra a aparece em uma string:
word = 'banana'
count = 0
for letter in word:
if letter == 'a':
count = count + 1
print(count)

Este programa demonstra outro padro de computao chamado contador. A varivel count
inicializada com 0 e ento incrementada cada vez que um a encontrado. Ao sair do loop, count
contm o resultado o nmero total de letras 'a'.

Como exerccio, encapsule este cdigo em uma funo denominada count e generalize-o para que
aceite a string e a letra como argumentos.
Ento reescreva a funo para que, em vez de atravessar a string, ela use a verso de trs parmetros do
find da seo anterior.

8.8 - Mtodos de strings


As strings oferecem mtodos que executam vrias operaes teis. Um mtodo semelhante a uma
funo toma argumentos e devolve um valor , mas a sintaxe diferente. Por exemplo, o mtodo
upper recebe uma string e devolve uma nova string com todas as letras maisculas.
Em vez da sintaxe de funo upper(word), ela usa a sintaxe de mtodo word.upper():
>>> word = 'banana'
>>> new_word = word.upper()
>>> new_word
'BANANA'

Esta forma de notao de ponto especifica o nome do mtodo, upper e o nome da string, word,
qual o mtodo ser aplicado. Os parnteses vazios indicam que este mtodo no toma nenhum
argumento.
Uma chamada de mtodo denomina-se invocao; neste caso, diramos que estamos invocando upper
em word.
78

E, na verdade, h um mtodo de string denominado find, que notavelmente semelhante funo


que escrevemos:
>>> word = 'banana'
>>> index = word.find('a')
>>> index
1

Neste exemplo, invocamos find em word e passamos a letra que estamos procurando como um
parmetro.
Na verdade, o mtodo find mais geral que a nossa funo; ele pode encontrar substrings, no apenas
caracteres:
>>> word.find('na')
2

Por padro, find inicia no comeo da string, mas pode receber um segundo argumento, o ndice onde
deve comear:
>>> word.find('na', 3)
4

Este um exemplo de um argumento opcional. find tambm pode receber um terceiro argumento, o
ndice para onde deve parar:
>>> name = 'bob'
>>> name.find('b', 1, 2)
-1

Esta busca falha porque 'b' no aparece no intervalo do ndice de 1 a 2, no incluindo 2. Fazer buscas
at (mas no incluindo) o segundo ndice torna find similar ao operador de fatiamento.

8.9 - Operador in
A palavra in um operador booleano que recebe duas strings e retorna True se a primeira aparecer
como uma substring da segunda:
>>> 'a' in 'banana'
True
>>> 'seed' in 'banana'
False

Por exemplo, a seguinte funo imprime todas as letras de word1 que tambm aparecem em word2:
def in_both(word1, word2):
for letter in word1:
if letter in word2:
print(letter)
79

Com nomes de variveis bem escolhidos, o Python s vezes pode ser lido como um texto em ingls.
Voc pode ler este loop, para (cada) letra em (a primeira) palavra, se (a) letra (aparecer) em (a
segunda) palavra, exiba (a) letra.

Veja o que apresentado ao se comparar mas e laranjas:


>>> in_both('apples', 'oranges')
a
e
s

8.10 - Comparao de strings


Os operadores relacionais funcionam em strings. Para ver se duas strings so iguais:
if word == 'banana':
print('All right, bananas.')

Outras operaes relacionais so teis para colocar palavras em ordem alfabtica:


if word < 'banana':
print('Your word, ' + word + ', comes before banana.')
elif word > 'banana':
print('Your word, ' + word + ', comes after banana.')
else:
print('All right, bananas.')

O Python no lida com letras maisculas e minsculas do mesmo jeito que as pessoas. Todas as letras
maisculas vm antes de todas as letras minsculas, portanto:
Your word, Pineapple, comes before banana.

Uma forma comum de lidar com este problema converter strings em um formato padro, como letras
minsculas, antes de executar a comparao. Lembre-se disso caso tenha que se defender de um
homem armado com um abacaxi.

8.11 - Depurao
Ao usar ndices para atravessar os valores em uma sequncia, complicado acertar o comeo e o fim
da travessia. Aqui est uma funo que supostamente compara duas palavras e retorna True se uma das
palavras for o reverso da outra, mas contm dois erros:
def is_reverse(word1, word2):
if len(word1) != len(word2):
return False
80

i = 0
j = len(word2)
while j > 0:
if word1[i] != word2[j]:
return False
i = i+1
j = j-1
return True

A primeira instruo if verifica se as palavras tm o mesmo comprimento. Se no for o caso, podemos


retornar False imediatamente. Do contrrio, para o resto da funo, podemos supor que as palavras
tenham o mesmo comprimento. Este um exemplo do modelo de guardio em Verificao de tipos,
na pgina 101.
i e j so ndices: i atravessa word1 para a frente, enquanto j atravessa word2 para trs. Se encontrarmos
duas letras que no combinam, podemos retornar False imediatamente. Se terminarmos o loop inteiro e
todas as letras corresponderem, retornamos True.
Se testarmos esta funo com as palavras pots e stop, esperamos o valor de retorno True, mas
recebemos um IndexError:
>>> is_reverse('pots', 'stop')
...
File "reverse.py", line 15, in is_reverse
if word1[i] != word2[j]:
IndexError: string index out of range

Para depurar este tipo de erro, minha primeira ao exibir os valores dos ndices imediatamente antes
da linha onde o erro aparece.
while j > 0:
print(i, j) # exibir aqui
if word1[i] != word2[j]:
return False
i = i+1
j = j-1

Agora quando executo o programa novamente, recebo mais informao:


>>> is_reverse('pots', 'stop')
0 4
...
IndexError: string index out of range

Na primeira vez que o programa passar pelo loop, o valor de j 4, que est fora do intervalo da string
pots. O ndice do ltimo caractere 3, ento o valor inicial de j deve ser len(word2)-1.
Se corrigir esse erro e executar o programa novamente, recebo:
>>> is_reverse('pots', 'stop')
0 3
1 2
81

2 1
True

Desta vez, recebemos a resposta certa, mas parece que o loop s foi executado trs vezes, o que
suspeito. Para ter uma ideia melhor do que est acontecendo, til desenhar um diagrama de estado.
Durante a primeira iterao, o frame de is_reverse mostrado na Figura 8.2.

8.2 Diagrama de estado de is_reverse.


Tomei a liberdade de arrumar as variveis no frame e acrescentei linhas pontilhadas para mostrar que
os valores de i e j indicam caracteres em word1 e word2.
Comeando com este diagrama, execute o programa em papel, alterando os valores de i e j durante
cada iterao. Encontre e corrija o segundo erro desta funo.

8.12 - Glossrio
objeto
Algo a que uma varivel pode se referir. Por enquanto, voc pode usar objeto e valor de
forma intercambivel.
sequncia
Uma coleo ordenada de valores onde cada valor identificado por um ndice de nmero
inteiro.
item
Um dos valores em uma sequncia.
ndice
Um valor inteiro usado para selecionar um item em uma sequncia, como um caractere em uma
string. No Python, os ndices comeam em 0.
fatia
Parte de uma string especificada por um intervalo de ndices.
string vazia
Uma string sem caracteres e de comprimento 0, representada por duas aspas.
imutvel
A propriedade de uma sequncia cujos itens no podem ser alterados.
atravessar
Repetir os itens em uma sequncia, executando uma operao semelhante em cada um.
busca
Um modelo de travessia que interrompido quando encontra o que est procurando.
contador
Uma varivel usada para contar algo, normalmente inicializada com zero e ento incrementada.
invocao
82

Uma instruo que chama um mtodo.


argumento opcional
Um argumento de funo ou mtodo que no necessrio.

8.13 - Exerccios
Exerccio 8.1
Leia a documentao dos mtodos de strings em http://docs.python.org/3/library/stdtypes.html#string-
methods. Pode ser uma boa ideia experimentar alguns deles para entender como funcionam. strip e
replace so especialmente teis.

A documentao usa uma sintaxe que pode ser confusa. Por exemplo, em find(sub[, start[,
end]]), os colchetes indicam argumentos opcionais. Ento sub exigido, mas start opcional, e
se voc incluir start, ento end opcional.

Exerccio 8.2
H um mtodo de string chamado count, que semelhante funo em Loop e contagem, na
pgina 123. Leia a documentao deste mtodo e escreva uma invocao que conte o nmero de letras
'a' em banana.

Exerccio 8.3
Uma fatia de string pode receber um terceiro ndice que especifique o tamanho do passo; isto , o
nmero de espaos entre caracteres sucessivos. Um tamanho de passo 2 significa tomar um caractere e
outro no; 3 significa tomar um e dois no etc.
>>> fruit = 'banana'
>>> fruit[0:5:2]
'bnn'

Um tamanho de passo -1 atravessa a palavra de trs para a frente, ento a fatia [::-1] gera uma
string invertida.
Use isso para escrever uma verso de uma linha de is_palindrome do Exerccio 6.3.

Exerccio 8.4
As seguintes funes pretendem verificar se uma string contm alguma letra minscula, mas algumas
delas esto erradas. Para cada funo, descreva o que ela faz (assumindo que o parmetro seja uma
string).
def any_lowercase1(s):
for c in s:
if c.islower():
83

return True
else:
return False

def any_lowercase2(s):
for c in s:
if 'c'.islower():
return 'True'
else:
return 'False'

def any_lowercase3(s):
for c in s:
flag = c.islower()
return flag

def any_lowercase4(s):
flag = False
for c in s:
flag = flag or c.islower()
return flag

def any_lowercase5(s):
for c in s:
if not c.islower():
return False
return True

Exerccio 8.5
Uma cifra de Csar uma forma fraca de criptografia que implica rotacionar cada letra por um
nmero fixo de lugares. Rotacionar uma letra significa desloc-lo pelo alfabeto, voltando ao incio se
for necessrio, portanto A rotacionado por 3 D e Z rotacionado por 1 A.
Para rotacionar uma palavra, faa cada letra se mover pela mesma quantidade de posies. Por
exemplo, cheer rotacionado por 7 jolly e melon rotacionado por -10 cubed. No filme 2001:
Uma odisseia no espao, o computador da nave chama-se HAL, que IBM rotacionado por -1.
Escreva uma funo chamada rotate_word que receba uma string e um nmero inteiro como
parmetros, e retorne uma nova string que contm as letras da string original rotacionadas pelo nmero
dado.
Voc pode usar a funo integrada ord, que converte um caractere em um cdigo numrico e chr, que
converte cdigos numricos em caracteres. As letras do alfabeto so codificadas em ordem alfabtica,
ento, por exemplo:
>>> ord('c') - ord('a')
2

Porque 'c' a segunda letra do alfabeto. Mas tenha cuidado: os cdigos numricos de letras
maisculas so diferentes.
84

Piadas potencialmente ofensivas na internet s vezes so codificadas em ROT13, que uma cifra de
Csar com rotao 13. Se no se ofender facilmente, encontre e decifre algumas delas.

Captulo 9: Estudo de caso: jogos de palavras


Este captulo apresenta o segundo estudo de caso que envolve solucionar quebra-cabeas usando
palavras com certas propriedades. Por exemplo, encontraremos os palndromos mais longos em ingls e
procuraremos palavras cujas letras apaream em ordem alfabtica. E apresentarei outro plano de
desenvolvimento de programa: a reduo a um problema resolvido anteriormente.

9.1 - Leitura de listas de palavras


Para os exerccios deste captulo vamos usar uma lista de palavras em ingls. H muitas listas de
palavras disponveis na internet, mas a mais conveniente ao nosso propsito uma das listas de
palavras disponibilizadas em domnio pblico por Grady Ward como parte do projeto lexical Moby
(ver http://wikipedia.org/wiki/Moby_Project). uma lista de 113.809 palavras cruzadas oficiais; isto ,
as palavras que se consideram vlidas em quebra-cabeas de palavras cruzadas e outros jogos de
palavras. Na coleo Moby, o nome do arquivo 113809of.fic; voc pode baixar uma cpia, com um
nome mais simples como words.txt, de http://thinkpython2.com/code/words.txt.
Este arquivo est em texto simples, ento voc pode abri-lo com um editor de texto, mas tambm pode
l-lo no Python. A funo integrada open recebe o nome do arquivo como um parmetro e retorna um
objeto de arquivo que voc pode usar para ler o arquivo.
>>> fin = open('words.txt')

fin um nome comum de objeto de arquivo usado para entrada de dados. O objeto de arquivo oferece
vrios mtodos de leitura, inclusive readline, que l caracteres no arquivo at chegar a um comando de
nova linha, devolvendo o resultado como uma string:
>>> fin.readline()
'aa\r\n'

A primeira palavra nesta lista especfica aa, uma espcie de lava. A sequncia '\r\n' representa
dois caracteres que representam espaos em branco (whitespace), um retorno de carro e uma nova
linha, que separa esta palavra da seguinte.
O objeto de arquivo grava a posio em que est no arquivo, ento se voc chamar readline mais uma
vez, receber a seguinte palavra:
>>> fin.readline()
'aah\r\n'
85

A palavra seguinte aah, uma palavra perfeitamente legtima, ento pare de olhar para mim desse
jeito. Ou, se o whitespace que est incomodando voc, podemos nos livrar dele com o mtodo de
string strip:
>>> line = fin.readline()
>>> word = line.strip()
>>> word
'aahed'

Voc tambm pode usar um objeto de arquivo como parte de um loop for. Este programa l words.txt
e imprime cada palavra, uma por linha:
fin = open('words.txt')
for line in fin:
word = line.strip()
print(word)

9.2 - Exerccios
H solues para estes exerccios na prxima seo. Mas bom voc tentar fazer cada um antes de ver
as solues.

Exerccio 9.1
Escreva um programa que leia words.txt e imprima apenas as palavras com mais de 20 caracteres (sem
contar whitespace).

Exerccio 9.2
Em 1939, Ernest Vincent Wright publicou uma novela de 50.000 palavras, chamada Gadsby, que no
contm a letra e. Como o e a letra mais comum em ingls, isso no algo fcil de fazer.
Na verdade, difcil at construir um nico pensamento sem usar o smbolo mais comum do idioma.
No incio lento, mas com prudncia e horas de treino, vai ficando cada vez mais fcil.
Muito bem, agora eu vou parar.

Escreva uma funo chamada has_no_e que retorne True se a palavra dada no tiver a letra e
nela.
Altere seu programa na seo anterior para imprimir apenas as palavras que no tm e e calcule a
porcentagem de palavras na lista que no tm e.
86

Exerccio 9.3
Escreva uma funo chamada avoids que receba uma palavra e uma srie de letras proibidas, e retorne
True se a palavra no usar nenhuma das letras proibidas.
Altere o cdigo para que o usurio digite uma srie de letras proibidas e o programa imprima o nmero
de palavras que no contm nenhuma delas. Voc pode encontrar uma combinao de cinco letras
proibidas que exclua o menor nmero possvel de palavras?

Exerccio 9.4
Escreva uma funo chamada uses_only que receba uma palavra e uma srie de letras e retorne
True, se a palavra s contiver letras da lista. Voc pode fazer uma frase usando s as letras acefhlo?
Que no seja Hoe alfalfa?

Exerccio 9.5
Escreva uma funo chamada uses_all que receba uma palavra e uma srie de letras obrigatrias e
retorne True se a palavra usar todas as letras obrigatrias pelo menos uma vez. Quantas palavras usam
todas as vogais (aeiou)? E que tal aeiouy?

Exerccio 9.6
Escreva uma funo chamada is_abecedarian que retorne True se as letras numa palavra
aparecerem em ordem alfabtica (tudo bem se houver letras duplas). Quantas palavras em ordem
alfabtica existem?

9.3 - Busca
Todos os exerccios na seo anterior tm algo em comum; eles podem ser resolvidos com o modelo de
busca que vimos em Buscando, na pgina 123. O exemplo mais simples :
def has_no_e(word):
for letter in word:
if letter == 'e':
return False
return True

O loop for atravessa os caracteres em word. Se encontrarmos a letra e, podemos retornar False
imediatamente; se no for o caso, temos que ir letra seguinte. Se sairmos do loop normalmente, isso
quer dizer que no encontramos um e, ento retornamos True.
Voc pode escrever esta funo de forma mais concisa usando o operador in, mas comecei com esta
verso porque ela demonstra a lgica do modelo de busca.
avoids uma verso mais geral de has_no_e, mas tem a mesma estrutura:
87

def avoids(word, forbidden):


for letter in word:
if letter in forbidden:
return False
return True

Podemos retornar False logo que encontrarmos uma letra proibida; se chegarmos ao fim do loop,
retornamos True.

uses_only semelhante, exceto pelo sentido da condio, que se inverte:


def uses_only(word, available):
for letter in word:
if letter not in available:
return False
return True

Em vez de uma lista de letras proibidas, temos uma lista de letras disponveis. Se encontrarmos uma
letra em word que no est em available, podemos retornar False.

uses_all semelhante, mas invertemos a funo da palavra e a string de letras:


def uses_all(word, required):
for letter in required:
if letter not in word:
return False
return True

Em vez de atravessar as letras em word, o loop atravessa as letras obrigatrias. Se alguma das letras
obrigatrias no aparecer na palavra, podemos retornar False.

Se voc realmente estivesse pensando como um cientista da computao, teria reconhecido que
uses_all foi um exemplo de um problema resolvido anteriormente e escreveria:
def uses_all(word, required):
return uses_only(required, word)

Esse um exemplo de um plano de desenvolvimento de programa chamado reduo a um problema


resolvido anteriormente, ou seja, voc reconhece o problema no qual est trabalhando como um
exemplo de um problema j resolvido e aplica uma soluo existente.

9.4 - Loop com ndices


Escrevi as funes na seo anterior com loops for porque eu s precisava dos caracteres nas strings;
no precisava fazer nada com os ndices.
Para is_abecedarian temos que comparar letras adjacentes, o que um pouco complicado para o
loop for:
88

def is_abecedarian(word):
previous = word[0]
for c in word:
if c < previous:
return False
previous = c
return True

Uma alternativa usar a recursividade:


def is_abecedarian(word):
if len(word) <= 1:
return True
if word[0] > word[1]:
return False
return is_abecedarian(word[1:])

Outra opo usar um loop while:


def is_abecedarian(word):
i = 0
while i < len(word)-1:
if word[i+1] < word[i]:
return False
i = i+1
return True

O loop comea com i == 0 e termina quando i == len(word)-1. Cada vez que passa pelo loop,
o programa compara o i-simo caractere (que voc pode considerar o caractere atual) com o caractere
de posio i+1 (que pode ser considerado o caractere seguinte).

Se o prximo caractere for de uma posio anterior (alfabeticamente anterior) atual, ento
descobrimos uma quebra na tendncia alfabtica, e retornamos False.

Se chegarmos ao fim do loop sem encontrar uma quebra, ento a palavra passa no teste. Para
convencer-se de que o loop termina corretamente, considere um exemplo como 'flossy'. O
comprimento da palavra 6, ento o loop executado pela ltima vez quando i for igual a 4, que o
ndice do segundo caractere de trs para frente. Na ltima iterao, o programa compara o penltimo
caractere com o ltimo, que o que queremos.
Aqui est uma verso de is_palindrome (veja o Exerccio 6.3) que usa dois ndices: um comea no
incio e aumenta; o outro comea no final e diminui.
def is_palindrome(word):
i = 0
j = len(word)-1
while i<j:
if word[i] != word[j]:
return False
i = i+1
j = j-1
89

return True

Ou podemos reduzir a um problema resolvido anteriormente e escrever:


def is_palindrome(word):
return is_reverse(word, word)

Usando is_reverse da seo 8.11.

9.5 - Depurao
Testar programas difcil. As funes neste captulo so relativamente fceis para testar porque
possvel verificar os resultados mo. Ainda assim, pode ser difcil ou at impossvel escolher um
grupo de palavras que teste todos os erros possveis.
Tomando has_no_e como exemplo, h dois casos bvios para verificar: as palavras que tm um e
devem retornar False, e as palavras que no tm devem retornar True. No dever ser um problema
pensar em um exemplo de cada uma.
Dentro de cada caso, h alguns subcasos menos bvios. Entre as palavras que tm um e, voc deve
testar palavras com um e no comeo, no fim e em algum lugar no meio. Voc deve testar palavras
longas, palavras curtas e palavras muito curtas, como a string vazia. A string vazia um exemplo de um
caso especial, no bvio, onde erros muitas vezes espreitam.
Alm dos casos de teste que voc gerar, tambm pode ser uma boa ideia testar seu programa com uma
lista de palavras como words.txt. Ao analisar a sada, pode ser que os erros apaream, mas tenha
cuidado: voc pode pegar um tipo de erro (palavras que no deveriam ser includas, mas foram) e no
outro (palavras que deveriam ser includas, mas no foram).
Em geral, o teste pode ajudar a encontrar bugs, mas no fcil gerar um bom conjunto de casos de
teste, e, mesmo se conseguir, no h como ter certeza de que o programa est correto. Segundo um
lendrio cientista da computao:
Testar programas pode ser usado para mostrar a presena de bugs, mas nunca para mostrar a ausncia
deles! Edsger W. Dijkstra

9.6 - Glossrio
objeto de arquivo
Um valor que representa um arquivo aberto.
reduo a um problema resolvido anteriormente
Um modo de resolver um problema expressando-o como uma instncia de um problema
resolvido anteriormente.
caso especial
90

Um caso de teste que atpico ou no bvio (e com probabilidade menor de ser tratado
corretamente).

9.7 - Exerccios
Exerccio 9.7
Esta pergunta baseada em um quebra-cabea veiculado em um programa de rdio chamado Car Talk
(http://www.cartalk.com/content/puzzlers):
D uma palavra com trs letras duplas consecutivas. Vou dar exemplos de palavras que quase cumprem
a condio, mas no chegam l. Por exemplo, a palavra committee, c-o-m-m-i-t-t-e-e. Seria perfeita se
no fosse aquele i que se meteu ali no meio. Ou Mississippi: M-i-s-s-i-s-s-i-p-p-i. Se pudesse tirar
aqueles is, daria certo. Mas h uma palavra que tem trs pares consecutivos de letras e, que eu saiba,
pode ser a nica palavra que existe. claro que provavelmente haja mais umas 500, mas s consigo
pensar nessa. Qual a palavra?
Escreva um programa que a encontre.
Soluo: http://thinkpython2.com/code/cartalk1.py.

Exerccio 9.8
Aqui est outro quebra-cabea do programa Car Talk (http://www.cartalk.com/content/puzzlers):
Estava dirigindo outro dia e percebi algo no hodmetro que chamou a minha ateno. Como a maior
parte dos hodmetros, ele mostra seis dgitos, apenas em milhas inteiras. Por exemplo, se o meu carro
tivesse 300.000 milhas, eu veria 3-0-0-0-0-0.
Agora, o que vi naquele dia foi muito interessante. Notei que os ltimos 4 dgitos eram um
palndromo; isto , podiam ser lidos da mesma forma no sentido correto e no sentido inverso. Por
exemplo, 5-4-4-5 um palndromo, ento no meu hodmetro poderia ser 3-1-5-4-4-5.
Uma milha depois, os ltimos 5 nmeros formaram um palndromo. Por exemplo, poderia ser 3-6-5-4-
5-6. Uma milha depois disso, os 4 nmeros do meio, dentro dos 6, formavam um palndromo. E
adivinhe s? Um milha depois, todos os 6 formavam um palndromo!
A pergunta : o que estava no hodmetro quando olhei primeiro?
Escreva um programa Python que teste todos os nmeros de seis dgitos e imprima qualquer nmero
que satisfaa essas condies.
Soluo: http://thinkpython2.com/code/cartalk2.py.
91

Exerccio 9.9
Aqui est outro problema do Car Talk que voc pode resolver com uma busca
(http://www.cartalk.com/content/puzzlers):
H pouco tempo recebi uma visita da minha me e percebemos que os dois dgitos que compem a
minha idade, quando invertidos, representavam a idade dela. Por exemplo, se ela tem 73 anos, eu tenho
37 anos. Ficamos imaginando com que frequncia isto aconteceu nos anos anteriores, mas acabamos
mudando de assunto e no chegamos a uma resposta.
Quando cheguei em casa, cheguei concluso de que os dgitos das nossas idades tinham sido
reversveis seis vezes at ento. Tambm percebi que, se tivssemos sorte, isso aconteceria novamente
dali a alguns anos, e se fssemos muito sortudos, aconteceria mais uma vez depois disso. Em outras
palavras, aconteceria 8 vezes no total. Ento a pergunta : quantos anos tenho agora?
Escreva um programa em Python que busque solues para esse problema. Dica: pode ser uma boa
ideia usar o mtodo de string zfill.

Captulo 10: Listas


Este captulo apresenta um dos tipos integrados mais teis do Python: listas. Voc tambm aprender
mais sobre objetos e o que pode acontecer quando o mesmo objeto tem mais de um nome.

10.1 - Uma lista uma sequncia


Como uma string, uma lista uma sequncia de valores. Em uma string, os valores so caracteres; em
uma lista, eles podem ser de qualquer tipo. Os valores em uma lista so chamados de elementos, ou,
algumas vezes, de itens.
H vrias formas para criar uma lista; a mais simples colocar os elementos entre colchetes ([ e ]):
[10, 20, 30, 40]
['crunchy frog', 'ram bladder', 'lark vomit']

O primeiro exemplo uma lista de quatro nmeros inteiros. O segundo uma lista de trs strings. Os
elementos de uma lista no precisam ser do mesmo tipo. A lista seguinte contm uma string, um
nmero de ponto flutuante, um nmero inteiro e (olhe s!) outra lista:
['spam', 2.0, 5, [10, 20]]

Uma lista dentro de outra lista uma lista aninhada.


Uma lista que no contm elementos chamada de lista vazia; voc pode criar uma com colchetes
vazios [].
Como j se poderia esperar, podemos atribuir uma lista de valores a variveis:
92

>>> cheeses = ['Cheddar', 'Edam', 'Gouda']


>>> numbers = [42, 123]
>>> empty = []
>>> print(cheeses, numbers, empty)
['Cheddar', 'Edam', 'Gouda'] [42, 123] []

10.2 - Listas so mutveis


A sintaxe para acessar os elementos de uma lista a mesma que para acessar os caracteres de uma
string: o operador de colchete. A expresso dentro dos colchetes especifica o ndice. Lembre-se de que
os ndices comeam em 0:
>>> cheeses[0]
'Cheddar'

Diferente das strings, listas so mutveis. Quando o operador de colchete aparece do lado esquerdo de
uma atribuio, ele identifica o elemento da lista que ser atribudo:
>>> numbers = [42, 123]
>>> numbers[1] = 5
>>> numbers
[42, 5]

O primeiro elemento de numbers, que costumava ser 123, agora 5.


A Figura 10.1 mostra o diagrama de estado para cheeses, numbers e empty.

Figura 10.1 Diagrama de estado de trs listas.


As listas so representadas pelas caixas com a palavra lista fora delas e os elementos da lista dentro
delas. cheeses refere-se a uma lista com trs elementos indexados como 0, 1 e 2. numbers contm dois
elementos e o diagrama mostra que o valor do segundo elemento foi reatribudo de 123 para 5. empty
refere-se a uma lista sem elementos.
ndices de listas funcionam da mesma forma que os ndices de strings:
93

Qualquer expresso de nmeros inteiros pode ser usada como ndice.

Se tentar ler ou escrever um elemento que no existe, voc recebe um IndexError.

Se um ndice tiver um valor negativo, ele conta de trs para a frente, a partir do final da lista.

O operador in tambm funciona com listas:


>>> cheeses = ['Cheddar', 'Edam', 'Gouda']
>>> 'Edam' in cheeses
True
>>> 'Brie' in cheeses
False

10.3 - Percorrendo uma lista


A forma mais comum de percorrer os elementos em uma lista com um loop for. A sintaxe a mesma
que a das strings:
for cheese in cheeses:
print(cheese)

Isso funciona bem se voc precisa apenas ler os elementos da lista. Mas se voc quer escrever ou
atualizar os elementos, voc precisa dos ndices. Uma forma comum de fazer isso combinar as
funes integradas range e len:
for i in range(len(numbers)):
numbers[i] = numbers[i] * 2

Este loop percorre a lista e atualiza cada elemento. len retorna o nmero de elementos na lista. range
retorna uma lista de ndices de 0 a n-1, em que n o comprimento da lista. Cada vez que passa pelo
loop, i recebe o ndice do prximo elemento. A instruo de atribuio no corpo usa i para ler o valor
antigo do elemento e atribuir o novo valor.
Um loop for que passe por uma lista vazia nunca executa o corpo:
for x in []:
print('This never happens.')

Apesar de uma lista poder conter outra lista, a lista aninhada ainda conta como um nico elemento. O
comprimento desta lista quatro:
['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]

10.4 - Operaes com listas


O operador + concatena listas:
>>> a = [1, 2, 3]
94

>>> b = [4, 5, 6]
>>> c = a + b
>>> c
[1, 2, 3, 4, 5, 6]

O operador * repete a lista um dado nmero de vezes:


>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

O primeiro exemplo repete [0] quatro vezes. O segundo exemplo repete a lista [1, 2, 3] trs
vezes.

10.5 - Fatias de listas


O operador de fatiamento tambm funciona com listas:
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3]
['b', 'c']
>>> t[:4]
['a', 'b', 'c', 'd']
>>> t[3:]
['d', 'e', 'f']

Se voc omitir o primeiro ndice, a fatia comea no incio. Se voc omitir o segundo, a fatia vai at o
final. Se voc omitir ambos, a fatia uma cpia da lista inteira.
>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']

Como as listas so mutveis, pode ser til fazer uma cpia antes de executar operaes que as alterem.
Um operador de fatia esquerda de uma atribuio pode atualizar vrios elementos:
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3] = ['x', 'y']
>>> t
['a', 'x', 'y', 'd', 'e', 'f']

10.6 - Mtodos de listas


O Python oferece mtodos que operam em listas. Por exemplo, append adiciona um novo elemento
ao fim de uma lista:
>>> t = ['a', 'b', 'c']
>>> t.append('d')
>>> t
['a', 'b', 'c', 'd']
95

extend toma uma lista como argumento e adiciona todos os elementos:


>>> t1 = ['a', 'b', 'c']
>>> t2 = ['d', 'e']
>>> t1.extend(t2)
>>> t1
['a', 'b', 'c', 'd', 'e']

Este exemplo deixa t2 intocado.


sort classifica os elementos da lista em ordem ascendente:
>>> t = ['d', 'c', 'e', 'b', 'a']
>>> t.sort()
>>> t
['a', 'b', 'c', 'd', 'e']

A maior parte dos mtodos de listas so nulos; eles alteram a lista e retornam None. Se voc escrever t
= t.sort() por acidente, ficar desapontado com o resultado.

10.7 - Mapeamento, filtragem e reduo


Para somar o total de todos os nmeros em uma lista, voc pode usar um loop como esse:
def add_all(t):
total = 0
for x in t:
total += x
return total

total inicializado com 0. Cada vez que o programa passa pelo loop, x recebe um elemento da lista.
O operador += oferece uma forma curta de atualizar uma varivel. Esta instruo de atribuio
aumentada,
total += x

equivalente a
total = total + x

No decorrer da execuo do loop, total acumula a soma dos elementos; uma varivel usada desta
forma s vezes chamada de acumuladora.
Somar todos elementos de uma lista uma operao to comum que o Python a oferece como uma
funo integrada, sum:
>>> t = [1, 2, 3]
>>> sum(t)
6
96

Uma operao como essa, que combina uma sequncia de elementos em um nico valor, s vezes
chamada de reduo.
Algumas vezes voc quer percorrer uma lista enquanto cria outra. Por exemplo, a funo seguinte
recebe uma lista de strings e retorna uma nova lista que contm strings com letras maisculas:
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res

res inicializado com uma lista vazia; cada vez que o programa passa pelo loop, acrescentamos o
prximo elemento. Ento res outro tipo de acumulador.

Uma operao como capitalize_all s vezes chamada de mapeamento porque ela mapeia
uma funo (nesse caso o mtodo capitalize) sobre cada um dos elementos em uma sequncia.

Outra operao comum selecionar alguns dos elementos de uma lista e retornar uma sublista. Por
exemplo, a funo seguinte recebe uma lista de strings e retorna uma lista que contm apenas strings
em letra maiscula:
def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res

isupper um mtodo de string que retorna True se a string contiver apenas letras maisculas.

Uma operao como only_upper chamada de filtragem porque filtra alguns dos elementos e
desconsidera outros.
As operaes de lista mais comuns podem ser expressas como uma combinao de mapeamento,
filtragem e reduo.

10.8 - Como excluir elementos


H vrias formas de excluir elementos de uma lista. Se souber o ndice do elemento que procura, voc
pode usar pop:
>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> t
['a', 'c']
>>> x
'b'
97

pop altera a lista e retorna o elemento que foi excludo. Se voc no incluir um ndice, ele exclui e
retorna o ltimo elemento.
Se no precisar do valor removido, voc pode usar a instruo del:
>>> t = ['a', 'b', 'c']
>>> del t[1]
>>> t
['a', 'c']

Se souber o elemento que quer excluir (mas no o ndice), voc pode usar remove:
>>> t = ['a', 'b', 'c']
>>> t.remove('b')
>>> t
['a', 'c']

O valor devolvido por remove None.

Para remover mais de um elemento, voc pode usar del com um ndice de fatia:
>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> t
['a', 'f']

Como sempre, a fatia seleciona todos os elementos at, mas no incluindo, o segundo ndice.

10.9 - Listas e strings


Uma string uma sequncia de caracteres e uma lista uma sequncia de valores, mas uma lista de
caracteres no a mesma coisa que uma string. Para converter uma string em uma lista de caracteres,
voc pode usar list:
>>> s = 'spam'
>>> t = list(s)
>>> t
['s', 'p', 'a', 'm']

Como list o nome de uma funo integrada, voc deve evitar us-lo como nome de varivel.
Tambm evito usar l porque parece demais com 1. por isso que uso t.

A funo list quebra uma string em letras individuais. Se voc quiser quebrar uma string em
palavras, voc pode usar o mtodo split:
>>> s = 'pining for the fjords'
>>> t = s.split()
>>> t
['pining', 'for', 'the', 'fjords']
98

Um argumento opcional chamado delimiter especifica quais caracteres podem ser usados para
demonstrar os limites das palavras. O exemplo seguinte usa um hfen como delimitador:
>>> s = 'spam-spam-spam'
>>> delimiter = '-'
>>> t = s.split(delimiter)
>>> t
['spam', 'spam', 'spam']

join o contrrio de split. Ele toma uma lista de strings e concatena os elementos. join um
mtodo de string, ento preciso invoc-lo no delimitador e passar a lista como parmetro:
>>> t = ['pining', 'for', 'the', 'fjords']
>>> delimiter = ' '
>>> s = delimiter.join(t)
>>> s
'pining for the fjords'

Nesse caso, o delimitador um caractere de espao, ento join coloca um espao entre as palavras.
Para concatenar strings sem espaos, voc pode usar a string vazia '', como delimitador.

10.10 - Objetos e valores


Se executarmos essas instrues de atribuio:
a = 'banana'
b = 'banana'

Sabemos que a e b se referem a uma string, mas no sabemos se elas se referem mesma string. H
dois estados possveis, mostrados na Figura 10.2.

Figura 10.2 Diagramas de estado possveis com duas variveis.


Em um caso, a e b se referem a dois objetos diferentes que tm o mesmo valor. No segundo caso, elas
se referem ao mesmo objeto.
Para verificar se duas variveis se referem ao mesmo objeto, voc pode usar o operador is:
>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True

Nesse exemplo, o Python criou apenas um objeto de string e tanto a quanto b se referem a ele. Mas
quando voc cria duas listas, voc tem dois objetos:
>>> a = [1, 2, 3]
99

>>> b = [1, 2, 3]
>>> a is b
False

Ento o diagrama de estado fica igual ao da Figura 10.3.

Figura 10.3 Diagrama de estado com variveis associadas a listas distintas, de mesmo valor.
Nesse caso, diramos que as duas listas so equivalentes, porque elas tm os mesmos elementos, mas
no idnticas, porque elas no so o mesmo objeto. Se dois objetos forem idnticos, eles tambm so
equivalentes, mas se eles forem equivalentes, no so necessariamente idnticos.
At agora, temos usado objeto e valor de forma intercambivel, mas mais exato dizer que um
objeto tem um valor. Se avaliar [1, 2, 3], voc tem um objeto de lista cujo valor uma sequncia de
nmeros inteiros. Se outra lista tem os mesmos elementos, dizemos que tem o mesmo valor, mas no
o mesmo objeto.

10.11 - Alias
Se a se refere a um objeto e voc atribui b = a, ento ambas as variveis se referem ao mesmo objeto.
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True

O diagrama de estado ficar igual Figura 10.4.

Figura 10.4 Diagrama de estado com duas variveis associadas mesma lista.
A associao de uma varivel com um objeto chamada de referncia. Neste exemplo, h duas
referncias ao mesmo objeto.
Um objeto com mais de uma referncia tem mais de um nome, ento dizemos que o objeto tem um
alias.
Se o objeto com alias mutvel, alteraes feitas em um alias afetam o outro tambm.
>>> b[0] = 42
>>> a
[42, 2, 3]
100

Apesar de esse comportamento poder ser til, ele passvel de erros. Em geral, mais seguro evitar
usar alias ao trabalhar com objetos mutveis.
Para objetos imutveis como strings, usar alias no um problema to grande. Neste exemplo:
a = 'banana'
b = 'banana'

Quase nunca faz diferena se a e b se referem mesma string ou no.

10.12 - Argumentos de listas


Ao passar uma lista a uma funo, a funo recebe uma referncia lista. Se a funo alterar a lista,
quem faz a chamada v a mudana. Por exemplo, delete_head remove o primeiro elemento de uma
lista:
def delete_head(t):
del t[0]
Ela usada assim:
>>> letters = ['a', 'b', 'c']
>>> delete_head(letters)
>>> letters
['b', 'c']

O parmetro t e a varivel letters so alias para o mesmo objeto. O diagrama da pilha fica igual ao da
Figura 10.5.

Figura 10.5 Diagrama da pilha: __main__ e delete_head compartilham referncias mesma


lista.
Como a lista compartilhada por dois frames, desenhei-a entre eles.
importante distinguir entre operaes que alteram listas e operaes que criam novas listas. Por
exemplo, o mtodo append altera a lista, mas o operador + cria uma nova lista:
>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> t1
[1, 2, 3]
>>> t2
None
101

Note que append altera a lista e retorna None (na realidade, o console do Python omite o None da
sada, mas voc pode conferir usando t2 is None).
>>> t3 = t1 + [4]
>>> t1
[1, 2, 3]
>>> t3
[1, 2, 3, 4]
>>> t1

O operador + cria uma nova lista e deixa a lista original inalterada.

Essa diferena importante quando voc escreve funes que devem alterar listas. Por exemplo, esta
funo no remove a cabea de uma lista:
def bad_delete_head(t):
t = t[1:] # ERRADO!

O operador de fatia cria uma nova lista e a atribuio faz t se referir a ela, mas isso no afeta quem faz
chamada.
>>> t4 = [1, 2, 3]
>>> bad_delete_head(t4)
>>> t4
[1, 2, 3]

No incio de bad_delete_head, t e t4 se referem mesma lista. No final, t se refere a uma nova


lista, mas t4 ainda se refere lista original, inalterada.
Uma alternativa escrever uma funo que crie e retorne uma nova lista. Por exemplo, tail retorna
tudo, exceto o primeiro elemento de uma lista:
def tail(t):
return t[1:]

Esta funo deixa a lista original inalterada. Ela usada assim:


>>> letters = ['a', 'b', 'c']
>>> rest = tail(letters)
>>> rest
['b', 'c']

10.13 - Depurao
O uso descuidado de listas (e de outros objetos mutveis) pode levar a longas horas de depurao. Aqui
esto algumas armadilhas comuns e formas de evit-las:
1. A maior parte dos mtodos de listas alteram o argumento e retornam None. Isto o oposto dos
mtodos de strings, que retornam uma nova string e deixam a original intocada.
102

Se voc est acostumado a escrever cdigo de strings desta forma:


word = word.strip()

tentador escrever cdigo de listas como este:


t = t.sort() # ERRADO!

Como sort retorna None, a prxima operao que voc executar com t provavelmente vai falhar.
Antes de usar mtodos e operadores de listas, voc deve ler a documentao com cuidado e test-
los no modo interativo.
1. Escolha o termo e fique com ele.
Parte do problema com listas que h muitas formas de fazer coisas com elas. Por exemplo, para
remover um elemento de uma lista voc pode usar pop, remove, del ou at uma atribuio de fatia.
Para adicionar um elemento voc pode usar o mtodo append ou o operador +. Assumindo que t
uma lista e x um elemento da lista, isto est correto:
t.append(x)
t = t + [x]
t += [x]
E isto est errado:
t.append([x]) # ERRADO!
t = t.append(x) # ERRADO!
t + [x] # ERRADO!
t = t + x # ERRADO!

Experimente cada um desses exemplos no modo interativo para conferir se voc entendeu o que
fazem. Note que apenas o ltimo causa um erro de tempo de execuo; os outros trs so legais, mas
eles fazem a coisa errada.
1. Faa cpias para evitar o uso de alias.
Se quiser usar um mtodo como sort, que altera o argumento, mas precisa manter a lista original,
voc pode fazer uma cpia:
>>> t = [3, 1, 2]
>>> t2 = t[:]
>>> t2.sort()
>>> t
[3, 1, 2]
>>> t2
[1, 2, 3]

Neste exemplo voc poderia usar tambm a funo integrada sorted, que retorna uma nova lista
classificada e deixa a original intocada.
>>> t2 = sorted(t)
>>> t
103

[3, 1, 2]
>>> t2
[1, 2, 3]

10.14 - Glossrio
lista
Uma sequncia de valores.
elemento
Um dos valores em uma lista (ou outra sequncia), tambm chamado de item.
lista aninhada
Uma lista que um elemento de outra lista.
acumuladora
Varivel usada em um loop para adicionar ou acumular um resultado.
atribuio aumentada
Instruo que atualiza o valor de uma varivel usando um operador como +=.
reduo
Padro de processamento que atravessa uma sequncia e acumula os elementos em um nico
resultado.
mapeamento
Padro de processamento que atravessa uma sequncia e executa uma operao em cada
elemento.
filtragem
Padro de processamento que atravessa uma lista e seleciona os elementos que satisfazem algum
critrio.
objeto
Algo a que uma varivel pode se referir. Um objeto tem um tipo e um valor.
equivalente
Ter o mesmo valor.
idntico
Ser o mesmo objeto (o que implica equivalncia).
referncia
Associao entre uma varivel e seu valor.
alias
Uma circunstncia onde duas ou mais variveis se referem ao mesmo objeto.
delimitador
Um caractere ou uma string usada para indicar onde uma string deve ser dividida.

10.15 - Exerccios
Voc pode baixar as solues para estes exerccios em http://thinkpython2.com/code/list_exercises.py.

Exerccio 10.1
Escreva uma funo chamada nested_sum que receba uma lista de listas de nmeros inteiros e
adicione os elementos de todas as listas aninhadas. Por exemplo:
104

>>> t = [[1, 2], [3], [4, 5, 6]]


>>> nested_sum(t)
21

Exerccio 10.2
Escreva uma funo chamada cumsum que receba uma lista de nmeros e retorne a soma cumulativa;
isto , uma nova lista onde o i-simo elemento a soma dos primeiros i+1 elementos da lista original.
Por exemplo:
>>> t = [1, 2, 3]
>>> cumsum(t)
[1, 3, 6]

Exerccio 10.3
Escreva uma funo chamada middle que receba uma lista e retorne uma nova lista com todos os
elementos originais, exceto os primeiros e os ltimos elementos. Por exemplo:
>>> t = [1, 2, 3, 4]
>>> middle(t)
[2, 3]

Exerccio 10.4
Escreva uma funo chamada chop que tome uma lista alterando-a para remover o primeiro e o ltimo
elementos, e retorne None. Por exemplo:
>>> t = [1, 2, 3, 4]
>>> chop(t)
>>> t
[2, 3]

Exerccio 10.5
Escreva uma funo chamada is_sorted que tome uma lista como parmetro e retorne True se a lista
estiver classificada em ordem ascendente, e False se no for o caso. Por exemplo:
>>> is_sorted([1, 2, 2])
True
>>> is_sorted(['b', 'a'])
False

Exerccio 10.6
Duas palavras so anagramas se voc puder soletrar uma rearranjando as letras da outra. Escreva uma
funo chamada is_anagram que tome duas strings e retorne True se forem anagramas.
105

Exerccio 10.7
Escreva uma funo chamada has_duplicates que tome uma lista e retorne True se houver algum
elemento que aparea mais de uma vez. Ela no deve modificar a lista original.

Exerccio 10.8
Este exerccio pertence ao assim chamado Paradoxo de aniversrio, sobre o qual voc pode ler em
http://en.wikipedia.org/wiki/Birthday_paradox.
Se h 23 alunos na sua sala, quais so as chances de dois deles fazerem aniversrio no mesmo dia?
Voc pode estimar esta probabilidade gerando amostras aleatrias de 23 dias de aniversrio e
verificando as correspondncias. Dica: voc pode gerar aniversrios aleatrios com a funo randint no
mdulo random.
Se quiser, voc pode baixar minha soluo em http://thinkpython2.com/code/birthday.py.

Exerccio 10.9
Escreva uma funo que leia o arquivo words.txt e construa uma lista com um elemento por palavra.
Escreva duas verses desta funo, uma usando o mtodo append e outra usando a expresso t = t
+ [x]. Qual leva mais tempo para ser executada? Por qu?

Soluo: http://thinkpython2.com/code/wordlist.py.

Exerccio 10.10
Para verificar se uma palavra est na lista de palavras, voc pode usar o operador in, mas isso seria
lento porque pesquisaria as palavras em ordem.
Como as palavras esto em ordem alfabtica, podemos acelerar as coisas com uma busca por bisseo
(tambm conhecida como pesquisa binria), que semelhante ao que voc faz quando procura uma
palavra no dicionrio. Voc comea no meio e verifica se a palavra que est procurando vem antes da
palavra no meio da lista. Se for o caso, procura na primeira metade da lista. Se no, procura na segunda
metade.
De qualquer forma, voc corta o espao de busca restante pela metade. Se a lista de palavras tiver
113.809 palavras, o programa seguir uns 17 passos para encontrar a palavra ou concluir que no est
l.
Escreva uma funo chamada in_bisect que receba uma lista ordenada, um valor-alvo e devolva o
ndice do valor na lista se ele estiver l, ou None se no estiver.
Ou voc pode ler a documentao do mdulo bisect e us-lo!
Soluo: http://thinkpython2.com/code/inlist.py.
106

Exerccio 10.11
Duas palavras so um par inverso se uma for o contrrio da outra. Escreva um programa que
encontre todos os pares inversos na lista de palavras.
Soluo: http://thinkpython2.com/code/reverse_pair.py.

Exerccio 10.12
Duas palavras interligam-se quando, ao tomarmos letras alternadas de cada uma, formamos uma
palavra nova. Por exemplo, shoe e cold interligam-se para formar schooled.
Soluo: http://thinkpython2.com/code/interlock.py. Crdito: este exerccio foi inspirado por um
exemplo em http://puzzlers.org.
1. Escreva um programa que encontre todos os pares de palavras que se interligam. Dica: no
enumere todos os pares!
2. Voc pode encontrar palavras que sejam interligadas de trs em trs; isto , cada terceira letra
forma uma palavra, comeando da primeira, segunda ou terceira?

Captulo 11: Dicionrios


Este captulo apresenta outro tipo integrado chamado dicionrio. Dicionrios so um dos melhores
recursos do Python; eles so os blocos de montar de muitos algoritmos eficientes e elegantes.

11.1 - Um dicionrio um mapeamento


Um dicionrio se parece com uma lista, mas mais geral. Em uma lista, os ndices tm que ser
nmeros inteiros; em um dicionrio, eles podem ser de (quase) qualquer tipo.
Um dicionrio contm uma coleo de ndices, que se chamam chaves e uma coleo de valores. Cada
chave associada com um nico valor. A associao de uma chave e um valor chama-se par chave-
valor ou item.
Em linguagem matemtica, um dicionrio representa um mapeamento de chaves a valores, para que
voc possa dizer que cada chave mostra o mapa a um valor. Como exemplo, vamos construir um
dicionrio que faz o mapa de palavras do ingls ao espanhol, portanto as chaves e os valores so todos
strings.
A funo dict cria um novo dicionrio sem itens. Como dict o nome de uma funo integrada, voc
deve evitar us-lo como nome de varivel.
>>> eng2sp = dict()
>>> eng2sp
107

{}

As chaves {} representam um dicionrio vazio. Para acrescentar itens ao dicionrio, voc pode usar
colchetes:
>>> eng2sp['one'] = 'uno'

Esta linha cria um item que mapeia da chave one ao valor uno. Se imprimirmos o dicionrio
novamente, vemos um par chave-valor com dois pontos entre a chave e o valor:
>>> eng2sp
{'one': 'uno'}

Este formato de sada tambm um formato de entrada. Por exemplo, voc pode criar um dicionrio
com trs itens:
>>> eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}

Porm, se exibir eng2sp, pode se surpreender:


>>> eng2sp
{'one': 'uno', 'three': 'tres', 'two': 'dos'}

A ordem dos pares chave-valor pode no ser a mesma. Se voc digitar o mesmo exemplo no seu
computador, pode receber um resultado diferente. Em geral, a ordem dos itens em um dicionrio
imprevisvel.
No entanto, isso no um problema porque os elementos de um dicionrio nunca so indexados com
ndices de nmeros inteiros. Em vez disso, voc usa as chaves para procurar os valores
correspondentes:
>>> eng2sp['two']
'dos'

A chave 'two' sempre mapeia ao valor 'dos', assim a ordem dos itens no importa.

Se a chave no estiver no dicionrio, voc recebe uma exceo:


>>> eng2sp['four']
KeyError: 'four'

A funo len compatvel com dicionrios; ela devolve o nmero de pares chave-valor:
>>> len(eng2sp)
3

O operador in funciona em dicionrios tambm; ele acusa se algo aparece como chave no dicionrio
(aparecer como valor no o suficiente).
>>> 'one' in eng2sp
108

True
>>> 'uno' in eng2sp
False

Para ver se algo aparece como um valor em um dicionrio, voc pode usar o mtodo values, que
devolve uma coleo de valores, e ento usar o operador in:
>>> vals = eng2sp.values()
>>> 'uno' in vals
True

O operador in usa algoritmos diferentes para listas e dicionrios. Para listas, ele procura os elementos
da lista em ordem, como descrito em Busca, na pgina 123. Conforme a lista torna-se mais longa, o
tempo de busca tambm fica proporcionalmente mais longo.
Para dicionrios, o Python usa um algoritmo chamado hashtable (tabela de disperso), que tem uma
propriedade notvel: o operador in leva praticamente o mesmo tempo na busca, no importa quantos
itens estejam no dicionrio. Eu explico como isso possvel em Hashtables, na pgina 302, mas a
explicao pode no fazer sentido at que voc tenha lido mais alguns captulos.

11.2 - Um dicionrio como uma coleo de contadores


Suponha que voc receba uma string e queira contar quantas vezes cada letra aparece nela. H vrios
modos de fazer isso:
1. Voc pode criar 26 variveis, uma para cada letra do alfabeto. Ento pode atravessar a string e,
para cada caractere, incrementar o contador correspondente, provavelmente usando uma
condicional encadeada.
2. Voc pode criar uma lista com 26 elementos. Ento pode converter cada caractere em um
nmero (com a funo integrada ord), usar o nmero como ndice na lista e incrementar o
respectivo contador.
3. Voc pode criar um dicionrio com caracteres como chaves e contadores como valores
correspondentes. Na primeira vez que visse um caractere, voc acrescentaria um item ao
dicionrio. Depois disso, incrementaria o valor de um item existente.
Cada uma dessas opes executa o mesmo clculo, mas o implementa de forma diferente.
Uma implementao um modo de executar um clculo; algumas implementaes so melhores que
outras. Por exemplo, uma vantagem da implementao de dicionrios que no precisamos saber de
antemo quais letras aparecem na string e s preciso criar espao para as letras que realmente venham
a aparecer.
O cdigo poderia ser assim:
def histogram(s):
109

d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] += 1
return d

O nome da funo histogram, um termo estatstico para uma coleo de contadores (ou
frequncias).
A primeira linha da funo cria um dicionrio vazio. O loop for atravessa a string. Cada vez que passa
pelo loop, se o caractere c no estiver no dicionrio, criamos um item com a chave c e o valor inicial 1
(pois j vimos esta letra uma vez). Se o c j estiver no dicionrio, incrementamos d [c].
Funciona assim:
>>> h = histogram('brontosaurus')
>>> h
{'a': 1, 'b': 1, 'o': 2, 'n': 1, 's': 2, 'r': 2, 'u': 2, 't': 1}

O histograma indica que as letras a e b aparecem uma vez; o aparece duas vezes, e assim por
diante.
Os dicionrios tm um mtodo chamado get, que toma uma chave e um valor padro. Se a chave
aparecer no dicionrio, get retorna o valor correspondente; se no for o caso, ele retorna o valor
padro. Por exemplo:
>>> h = histogram('a')
>>> h
{'a': 1}
>>> h.get('a', 0)
1
>>> h.get('b', 0)
0

Como exerccio, use o get para escrever a funo histogram de forma mais concisa. Tente eliminar
a instruo if.

11.3 - Loop e dicionrios


Se usar um dicionrio em uma instruo for, ela percorre as chaves do dicionrio. Por exemplo,
print_hist exibe cada chave e o valor correspondente:
def print_hist(h):
for c in h:
print(c, h[c])

Isso o que aparece:


110

>>> h = histogram('parrot')
>>> print_hist(h)
a 1
p 1
r 2
t 1
o 1

Novamente, as chaves no esto em nenhuma ordem determinada. Para atravessar as chaves em ordem
ascendente, voc pode usar a funo integrada sorted:
>>> for key in sorted(h):
... print(key, h[key])
a 1
o 1
p 1
r 2
t 1

11.4 - Busca reversa


Considerando um dicionrio d e uma chave k, fcil encontrar o valor correspondente v = d [k].
Esta operao chama-se busca.
Mas e se voc tiver v e quiser encontrar k? Voc tem dois problemas: em primeiro lugar, pode haver
mais de uma chave que esteja mapeada ao valor v. Dependendo da aplicao, quem sabe voc pode
escolher um, ou talvez tenha de fazer uma lista que contenha todos eles. Em segundo lugar, no h
sintaxe simples para fazer uma busca reversa; preciso procurar.
Aqui est uma funo que recebe um valor e retorna a primeira chave mapeada ao valor dado:
def reverse_lookup(d, v):
for k in d:
if d[k] == v:
return k
raise LookupError()

Essa funo mais um exemplo do padro de busca, mas usa um recurso que ainda no tnhamos visto:
raise. A instruo raise causa uma exceo; neste caso, causa um LookupError, que uma
exceo integrada, usada para indicar que uma operao de busca falhou.
Se chegarmos ao fim do loop significa que v no aparece no dicionrio como um valor, portanto
apresentaremos uma exceo.
Aqui est um exemplo de uma busca reversa bem sucedida:
>>> h = histogram('parrot')
>>> k = reverse_lookup(h, 2)
>>> k
'r'
111

E uma mal sucedida:


>>> k = reverse_lookup(h, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in reverse_lookup
LookupError

O efeito causado por voc ao apresentar uma exceo igual ao causado pelo Python quando faz o
mesmo: ele exibe um traceback e uma mensagem de erro.
A instruo raise pode receber uma mensagem de erro detalhada como argumento opcional. Por
exemplo:
>>> raise LookupError('value does not appear in the dictionary')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
LookupError: value does not appear in the dictionary

Uma busca reversa muito mais lenta que uma busca no sentido normal; se for preciso faz-lo muitas
vezes, ou se o dicionrio ficar muito grande, o desempenho do seu programa ser prejudicado.

11.5 - Dicionrios e listas


As listas podem aparecer como valores em um dicionrio. Por exemplo, se voc receber um dicionrio
que mapeie letras e frequncias, uma boa ideia invert-lo; isto , crie um dicionrio que mapeie de
frequncias a letras. Como pode haver vrias letras com a mesma frequncia, cada valor no dicionrio
invertido deve ser uma lista de letras.
Aqui est uma funo que inverte um dicionrio:
def invert_dict(d):
inverse = dict()
for key in d:
val = d[key]
if val not in inverse:
inverse[val] = [key]
else:
inverse[val].append(key)
return inverse

Cada vez que o programa passar pelo loop, a key recebe uma chave de d e val recebe o valor
correspondente. Se val no estiver em inverse significa que no foi vista antes, ento criamos um item e
o inicializamos com um item avulso (em ingls, singleton, uma lista que contm um nico elemento).
Se no for o caso porque vimos esse valor antes, ento acrescentamos a chave correspondente lista.
Aqui est um exemplo:
>>> hist = histogram('parrot')
>>> hist
112

{'a': 1, 'p': 1, 'r': 2, 't': 1, 'o': 1}


>>> inverse = invert_dict(hist)
>>> inverse
{1: ['a', 'p', 't', 'o'], 2: ['r']}

A Figura 11.1 um diagrama de estado mostrando hist e inverse. Um dicionrio representado como
uma caixa com o tipo dict acima dela e os pares chave-valor no interior. Se os valores forem nmeros
inteiros, de ponto flutuante ou strings, desenho-os dentro da caixa, mas normalmente prefiro desenhar
listas do lado de fora, para manter o diagrama simples.

Figura 11.1 Diagrama de estado de um dicionrio e seu inverso.


As listas podem ser valores em um dicionrio, como mostra este exemplo, mas no podem ser chaves.
Isso o que acontece se voc tentar:
>>> t = [1, 2, 3]
>>> d = dict()
>>> d[t] = 'oops'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: list objects are unhashable

J mencionei que um dicionrio implementado usando uma hashtable e isso significa que preciso
que as chaves possam ser hashable (que seja possvel computar seu hash, e que este valor de hash seja
imutvel).
hash uma funo que recebe um valor (de qualquer tipo) e devolve um nmero inteiro. Dicionrios
usam esses nmeros inteiros, chamados valores hash, para guardar e buscar pares chave-valor.

Este sistema funciona perfeitamente se as chaves forem imutveis. Porm, se as chaves so mutveis,
como listas, coisas ruins acontecem. Por exemplo, quando voc cria um par chave-valor, o Python
guarda a chave na posio correspondente. Se voc modificar a chave e ento guard-la novamente, ela
iria para uma posio diferente. Nesse caso, voc poderia ter duas entradas para a mesma chave, ou
pode no conseguir encontrar uma chave. De qualquer forma, o dicionrio no funcionaria
corretamente.
por isso que as chaves tm de ser hashable, e tipos mutveis como listas, no so. A forma mais
simples de resolver esta limitao usar tuplas, que sero vistas no prximo captulo.
113

Como os dicionrios so mutveis, eles no podem ser usados como chaves, mas podem ser usados
como valores.

11.6 - Memos
Se usou a funo de fibonacci em Mais um exemplo, na pgina 101, pode ter notado que quanto
maior o argumento dado mais tempo a funo leva para ser executada. Alm disso, o tempo de
execuo aumenta rapidamente.
Para entender por que, considere a Figura 11.2, que mostra o grfico de chamada de fibonacci com
n=4.

Figura 11.2 Grfico de chamada para fibonacci.

Um grfico de chamada mostra um conjunto de frames de funo, com linhas que unem cada frame aos
frames das funes que chama. Na parte de cima do grfico, fibonacci com n=4 chama
fibonacci com n=3 e n=2. Por sua vez, fibonacci com n=3 chama fibonacci com n=2 e
n=1. E assim por diante.

Conte quantas vezes fibonacci(0) e fibonacci(1) so chamadas. Essa uma soluo


ineficiente para o problema, e piora conforme o argumento se torna maior.
Uma soluo acompanhar os valores que j foram calculados, guardando-os em um dicionrio. Um
valor calculado anteriormente que guardado para uso posterior chamado de memo. Aqui est uma
verso com memos de fibonacci:
114

known = {0:0, 1:1}


def fibonacci(n):
if n in known:
return known[n]
res = fibonacci(n-1) + fibonacci(n-2)
known[n] = res
return res

known um dicionrio que monitora os nmeros de Fibonacci que j conhecemos. Comea com dois
itens: 0 mapeia a 0 e 1 mapeia a 1.
Sempre que fibonacci chamada, ela verifica known. Se o resultado j estiver l, pode voltar
imediatamente. Se no for o caso, preciso calcular o novo valor, acrescent-lo ao dicionrio e
devolv-lo.
Se voc executar essa verso de fibonacci e a comparar com a original, descobrir que muito
mais rpida.

11.7 - Variveis globais


No exemplo anterior, known criada fora da funo, ento pertence ao frame especial chamado
__main__. As variveis em __main__ s vezes so chamadas de globais, porque podem ser
acessadas de qualquer funo. Em contraste com as variveis locais, que desaparecem quando sua
funo termina, as variveis globais persistem de uma chamada da funo seguinte.
comum usar variveis globais para flags; isto , variveis booleanas que indicam (flag) se uma
condio verdadeira. Por exemplo, alguns programas usam um flag denominado verbose para
controlar o nvel de detalhe da sada:
verbose = True
def example1():
if verbose:
print('Running example1')

Se tentar reatribuir uma varivel global, voc pode se surpreender. O prximo exemplo mostra como
acompanhar se a funo foi chamada:
been_called = False
def example2():
been_called = True # ERRADO

Porm, se execut-la, voc ver que o valor de been_called no se altera. O problema que
example2 cria uma nova varivel local chamada been_called. A varivel local some quando a
funo termina e no tem efeito sobre a varivel global.
Para reatribuir uma varivel global dentro de uma funo preciso declarar a varivel como global
antes de us-la:
115

been_called = False
def example2():
global been_called
been_called = True

A instruo global diz ao interpretador algo como Nesta funo, quando digo been_called,
estou falando da varivel global; no crie uma local.
Aqui est um exemplo que tenta atualizar uma varivel global:
count = 0
def example3():
count = count + 1 # ERRADO

Se execut-la, voc recebe:


UnboundLocalError: local variable 'count' referenced before assignment

O Python supe que count seja local, e dentro desta suposio, a varivel est sendo lida antes de ser
escrita. A soluo, mais uma vez, declarar count como global:
def example3():
global count
count += 1

Se uma varivel global se referir a um valor mutvel, voc pode alterar o valor sem declarar a varivel:
known = {0:0, 1:1}
def example4():
known[2] = 1

Ento voc pode adicionar, retirar e substituir elementos de uma lista global ou dicionrio, mas se
quiser reatribuir a varivel, precisa declar-la:
def example5():
global known
known = dict()

As variveis globais podem ser teis, mas se voc tiver muitas delas e alter-las com frequncia, isso
poder dificultar a depurao do programa.

11.8 - Depurao
Ao trabalhar com conjuntos de dados maiores, depurar exibindo e verificando a sada mo pode ser
trabalhoso. Aqui esto algumas sugestes para depurar grandes conjuntos de dados:
Reduza a entrada
Se for possvel, reduza o tamanho do conjunto de dados. Por exemplo, se o programa l um
arquivo de texto, comece com apenas as 10 primeiras linhas, ou com o menor exemplo que puder
116

encontrar. Voc pode editar os prprios arquivos ou alterar o programa para que leia s as
primeiras n linhas ( melhor).
Se houver um erro, voc pode reduzir n ao menor valor que manifeste o erro, e ento aument-lo
gradativamente at encontrar e corrigir o erro.
Verifique os resumos e tipos
Em vez de imprimir e verificar o conjunto de dados inteiro, pense em exibir resumos dos dados:
por exemplo, o nmero de itens em um dicionrio ou o total de uma lista de nmeros.
Uma causa comum de erros em tempo de execuo so valores de tipo incompatvel. Para
depurar essa espcie de erro, muitas vezes basta exibir o tipo de um valor.
Crie autoverificaes
possvel escrever o cdigo para verificar erros automaticamente. Por exemplo, se estiver
calculando a mdia de uma lista de nmeros, voc pode verificar se o resultado no mais alto
que o maior elemento da lista ou mais baixo que o menor. Isso chamado de verificao de
sanidade porque descobre resultados insanos.
Outro tipo de verificao compara os resultados de dois clculos diferentes para ver se so
consistentes. Isso chamado de verificao de consistncia.
Formate a sada
A formatao da sada para depurao pode facilitar a busca de erros. Vimos um exemplo em
Depurao, na pgina 172. O mdulo `pprint` apresenta uma funo `pprint` que exibe tipos
integrados em um formato mais legvel para humanos (`pprint` a abreviao de pretty print
(bela exibio)).

Reforando, o tempo que voc passar construindo o scaffolding (o andaime) pode reduzir o tempo de
depurao.

11.9 - Glossrio
mapeamento
Relao na qual cada elemento de um conjunto corresponde a um elemento de outro conjunto.
dicionrio
Mapeamento de chaves aos seus valores correspondentes.
par chave-valor
Representao do mapeamento de uma chave a um valor.
item
Em um dicionrio, outro nome para um par chave-valor.
chave
Objeto que aparece em um dicionrio como a primeira parte de um par chave-valor.
valor
Objeto que aparece em um dicionrio como a segunda parte de um par chave-valor. Isso mais
especfico que o nosso uso anterior da palavra valor.
implementao
Uma forma de executar um clculo.
hashtable
Algoritmo usado para implementar dicionrios de Python.
funo hash
Funo usada por uma hashtable para calcular a posio de uma chave.
117

hashable
Um tipo que tem uma funo hash. Tipos imutveis como nmeros inteiros, de ponto flutuante e
strings so hashable; tipos mutveis, como listas e dicionrios, no so.
busca
Operao de dicionrio que recebe uma chave e encontra o valor correspondente.
busca reversa
Operao de dicionrio que recebe um valor e encontra uma ou vrias chaves que o mapeiem.
instruo raise
Instruo que (deliberadamente) causa uma exceo.
item avulso (singleton)
Uma lista (ou outra sequncia) com um nico elemento.
grfico de chamada
Um diagrama que mostra cada frame criado durante a execuo de um programa, com uma flecha
apontando quem chama a quem chamado.
memo
Valor j calculado, guardado para no ter que fazer o mesmo clculo no futuro.
varivel global
Varivel definida fora de uma funo. As variveis globais podem ser acessadas de qualquer
funo.
instruo global
Instruo que declara um nome de varivel global.
flag
Varivel booleana usada para indicar se uma condio verdadeira.
declarao
Instruo tal como global, que diz ao interpretador algo a respeito de uma varivel.

11.10 - Exerccios
Exerccio 11.1
Escreva uma funo que leia as palavras em words.txt e guarde-as como chaves em um dicionrio. No
importa quais so os valores. Ento voc pode usar o operador in como uma forma rpida de verificar
se uma string est no dicionrio.
Se fez o Exerccio 10.10, voc pode comparar a velocidade desta implementao com o operador in de
listas e a busca por bisseo.

Exerccio 11.2
Leia a documentao do mtodo de dicionrio setdefault e use-o para escrever uma verso mais concisa
de invert_dict.
Soluo: http://thinkpython2.com/code/invert_dict.py.
118

Exerccio 11.3
Memorize a funo de Ackermann do Exerccio 6.2 e veja se a memorizao permite avaliar a funo
com argumentos maiores. Dica: no.
Soluo: http://thinkpython2.com/code/ackermann_memo.py.

Exerccio 11.4
Se fez o Exerccio 10.7, voc j tem uma funo chamada has_duplicates, que recebe uma lista como
parmetro e retorna True se houver algum objeto que aparece mais de uma vez na lista.
Use um dicionrio para escrever uma verso mais rpida e simples de has_duplicates.
Soluo: http://thinkpython2.com/code/has_duplicates.py.

Exerccio 11.5
Duas palavras so pares rotacionados se for possvel rotacionar um deles e chegar ao outro (ver
rotate_word no Exerccio 8.5).

Escreva um programa que leia uma lista de palavras e encontre todos os pares rotacionados.
Soluo: http://thinkpython2.com/code/rotate_pairs.py.

Exerccio 11.6
Aqui est outro quebra-cabea do programa Car Talk (http://www.cartalk.com/content/puzzlers):
Ele foi enviado por Dan OLeary. Dan descobriu uma palavra comum, com uma slaba e cinco letras
que tem a seguinte propriedade nica. Ao removermos a primeira letra, as letras restantes formam um
homfono da palavra original, que uma palavra que soa exatamente da mesma forma. Substitua a
primeira letra, isto , coloque-a de volta, retire a segunda letra e o resultado um outro homfono da
palavra original. E a pergunta , qual a palavra?
Agora vou dar um exemplo que no funciona. Vamos usar a palavra de cinco letras, wrack (mover,
eliminar). W-R-A-C-K, como na expresso wrack with pain (se contorcer de dor). Se eu retirar a
primeira letra, sobra uma palavra de quatro letras, R-A-C-K (galhada). Como na frase, Holy cow, did
you see the rack on that buck! It must have been a nine-pointer! (Minha nossa, voc viu a galhada
daquele cervo! Deve ter nove pontas!). um homfono perfeito. Se puser o w de volta e retirar o r
em vez disso, sobra a palavra wack, que uma palavra de verdade, mas no um homfono das
outras duas palavras.
Mas h pelo menos uma palavra que Dan e eu conhecemos, que produz dois homfonos se voc retirar
qualquer uma das duas primeiras letras, e duas novas palavras de quatro letras so formadas. A
pergunta , qual a palavra?
119

Voc pode usar o dicionrio do Exerccio 11.1 para verificar se uma string est na lista de palavras.
Para verificar se duas palavras so homfonas, voc pode usar o Dicionrio de pronncia CMU. Ele
pode ser baixado em http://www.speech.cs.cmu.edu/cgi-bin/cmudict ou em
http://thinkpython2.com/code/c06d. Voc tambm pode baixar http://thinkpy
thon2.com/code/pronounce.py, que tem uma funo chamada read_dictionary, que l o
dicionrio de pronncia e retorna um dicionrio de Python que mapeia cada palavra a uma string que
descreve sua pronncia primria.
Escreva um programa que liste todas as palavras que resolvem o quebra-cabea.
Soluo: http://thinkpython2.com/code/homophone.py.

Captulo 12: Tuplas


Este captulo apresenta mais um tipo integrado, a tupla, e descreve como as listas, os dicionrios e as
tuplas trabalham juntos. Alm disso, apresento um recurso til para listas de argumentos de
comprimento varivel: os operadores gather e scatter.
Uma observao: no h consenso sobre como pronunciar tuple (em ingls). Algumas pessoas dizem
tuhple, que rima com supple. Porm, no contexto da programao, a maioria das pessoas diz too-
ple, que rima com quadruple.

12.1 - Tuplas so imutveis


Uma tupla uma sequncia de valores. Os valores podem ser de qualquer tipo, e podem ser indexados
por nmeros inteiros, portanto, nesse sentido, as tuplas so muito parecidas com as listas. A diferena
importante que as tuplas so imutveis.
Sintaticamente, uma tupla uma lista de valores separados por vrgulas:
>>> t = 'a', 'b', 'c', 'd', 'e'

Embora no seja sempre necessrio, comum colocar tuplas entre parnteses:


>>> t = ('a', 'b', 'c', 'd', 'e')

Para criar uma tupla com um nico elemento, preciso incluir uma vrgula final:
>>> t1 = 'a',
>>> type(t1)
<class 'tuple'>

Um nico valor entre parnteses no uma tupla:


>>> t2 = ('a')
120

>>> type(t2)
<class 'str'>

Outra forma de criar uma tupla com a funo integrada tuple. Sem argumentos, cria uma tupla
vazia:
>>> t = tuple()
>>> t
()

Se os argumentos forem uma sequncia (string, lista ou tupla), o resultado uma tupla com os
elementos da sequncia:
>>> t = tuple('lupins')
>>> t
('l', 'u', 'p', 'i', 'n', 's')

Como tuple o nome de uma funo integrada, voc deve evitar us-lo como nome de varivel.

A maior parte dos operadores de lista tambm funciona em tuplas. O operador de colchetes indexa um
elemento:
>>> t = ('a', 'b', 'c', 'd', 'e')
>>> t[0]
'a'

E o operador de fatia seleciona vrios elementos:


>>> t[1:3]
('b', 'c')

Entretanto, se tentar alterar um dos elementos da tupla, vai receber um erro:


>>> t[0] = 'A'
TypeError: object doesn't support item assignment

Como tuplas so imutveis, voc no pode alterar os elementos, mas pode substituir uma tupla por
outra:
>>> t = ('A',) + t[1:]
>>> t
('A', 'b', 'c', 'd', 'e')

Essa instruo faz uma nova tupla e ento a atribui a t.

Os operadores relacionais funcionam com tuplas e outras sequncias; o Python comea comparando o
primeiro elemento de cada sequncia. Se forem iguais, vai para os prximos elementos, e assim por
diante, at que encontre elementos que sejam diferentes. Os elementos subsequentes no so
considerados (mesmo se forem muito grandes).
>>> (0, 1, 2) < (0, 3, 4)
121

True
>>> (0, 1, 2000000) < (0, 3, 4)
True

12.2 - Atribuio de tuplas


Muitas vezes, til trocar os valores de duas variveis. Com a atribuio convencional, preciso usar
uma varivel temporria. Por exemplo, trocar a e b.
>>> temp = a
>>> a = b
>>> b = temp

Essa soluo trabalhosa; a atribuio de tuplas mais elegante:


>>> a, b = b, a

O lado esquerdo uma tupla de variveis; o lado direito uma tupla de expresses. Cada valor
atribudo sua respectiva varivel. Todas as expresses no lado direito so avaliadas antes de todas as
atribuies.
O nmero de variveis esquerda e o nmero de valores direita precisam ser iguais:
>>> a, b = 1, 2, 3
ValueError: too many values to unpack

De forma geral, o lado direito pode ter qualquer tipo de sequncia (string, lista ou tupla). Por exemplo,
para dividir um endereo de email em um nome de usurio e um domnio, voc poderia escrever:
>>> addr = 'monty@python.org'
>>> uname, domain = addr.split('@')

O valor de retorno do split uma lista com dois elementos; o primeiro elemento atribudo a
uname, o segundo a domain:
>>> uname
'monty'
>>> domain
'python.org'

12.3 - Tuplas como valores de retorno


Falando estritamente, uma funo s pode retornar um valor, mas se o valor for uma tupla, o efeito o
mesmo que retornar valores mltiplos. Por exemplo, se voc quiser dividir dois nmeros inteiros e
122

calcular o quociente e resto, no eficiente calcular x/y e depois x%y. melhor calcular ambos ao
mesmo tempo.
A funo integrada divmod toma dois argumentos e devolve uma tupla de dois valores: o quociente e o
resto. Voc pode guardar o resultado como uma tupla:
>>> t = divmod(7, 3)
>>> t
(2, 1)

Ou usar a atribuio de tuplas para guardar os elementos separadamente:


>>> quot, rem = divmod(7, 3)
>>> quot
2
>>> rem
1

Aqui est um exemplo de funo que retorna uma tupla:


def min_max(t):
return min(t), max(t)

max e min so funes integradas que encontram os maiores e menores elementos de uma sequncia.
min_max calcula ambos e retorna uma tupla de dois valores.

12.4 - Tuplas com argumentos de comprimento varivel


As funes podem receber um nmero varivel de argumentos. Um nome de parmetro que comece
com * rene vrios argumentos em uma tupla. Por exemplo, printall recebe qualquer nmero de
argumentos e os exibe:
def printall(*args):
print(args)

O parmetro com o prefixo * pode ter qualquer nome que voc goste, mas args o convencional.
assim que a funo funciona:
>>> printall(1, 2.0, '3')
(1, 2.0, '3')

O complemento de reunir espalhar. Se voc tiver uma sequncia de valores e quiser pass-la a uma
funo como argumentos mltiplos, pode usar o operador *. Por exemplo, o divmod recebe
exatamente dois argumentos; ele no funciona com uma tupla:
>>> t = (7, 3)
>>> divmod(t)
TypeError: divmod expected 2 arguments, got 1
123

No entanto, se voc espalhar a tupla, a funciona:


>>> divmod(*t)
(2, 1)

Muitas das funes integradas usam tuplas com argumentos de comprimento varivel. Por exemplo,
max e min podem receber qualquer nmero de argumentos:
>>> max(1, 2, 3)
3

Mas sum, no:


>>> sum(1, 2, 3)
TypeError: sum expected at most 2 arguments, got 3

Como exerccio, escreva uma funo chamada sumall que receba qualquer nmero de argumentos e
retorne a soma deles.

12.5 - Listas e tuplas


zip uma funo integrada que recebe duas ou mais sequncias e devolve uma lista de tuplas onde
cada tupla contm um elemento de cada sequncia. O nome da funo tem a ver com o zper, que se
junta e encaixa duas carreiras de dentes.
Este exemplo encaixa uma string e uma lista:
>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
<zip object at 0x7f7d0a9e7c48>

O resultado um objeto zip que sabe como percorrer os pares. O uso mais comum de zip em um
loop for:
>>> for pair in zip(s, t):
... print(pair)
...
('a', 0)
('b', 1)
('c', 2)

Um objeto zip um tipo de iterador, ou seja, qualquer objeto que percorre ou itera sobre uma
sequncia. Iteradores so semelhantes a listas em alguns aspectos, mas, ao contrrio de listas, no
possvel usar um ndice para selecionar um elemento de um iterador.
Se quiser usar operadores e mtodos de lista, voc pode usar um objeto zip para fazer uma lista:
>>> list(zip(s, t))
[('a', 0), ('b', 1), ('c', 2)]
124

O resultado uma lista de tuplas; neste exemplo, cada tupla contm um caractere da string e o
elemento correspondente da lista.
Se as sequncias no forem do mesmo comprimento, o resultado tem o comprimento da mais curta:
>>> list(zip('Anne', 'Elk'))
[('A', 'E'), ('n', 'l'), ('n', 'k')]

Voc pode usar a atribuio de tuplas em um loop for para atravessar uma lista de tuplas:
t = [('a', 0), ('b', 1), ('c', 2)]
for letter, number in t:
print(number, letter)

Cada vez que o programa passa pelo loop, o Python seleciona a prxima tupla na lista e atribui os
elementos letter e number. A sada deste loop :
0 a
1 b
2 c

Se combinar zip, for e atribuio de tuplas, voc pode fazer uma expresso til para percorrer duas
(ou mais) sequncias ao mesmo tempo. Por exemplo, has_match recebe duas sequncias, t1 e t2 e
retorna True se houver um ndice i tal que t1[i] == t2[i]:
def has_match(t1, t2):
for x, y in zip(t1, t2):
if x == y:
return True
return False

Se precisar atravessar os elementos de uma sequncia e seus ndices, voc pode usar a funo integrada
enumerate:
for index, element in enumerate('abc'):
print(index, element)

O resultado de enumerate um objeto enumerate, que itera sobre uma sequncia de pares; cada
par contm um ndice (comeando de 0) e um elemento da sequncia dada. Neste exemplo, a sada
0 a
1 b
2 c

De novo.
125

12.6 - Dicionrios e tuplas


Os dicionrios tm um mtodo chamado items que devolve uma sequncia de tuplas, onde cada tupla
um par chave-valor:
>>> d = {'a':0, 'b':1, 'c':2}
>>> t = d.items()
>>> t
dict_items([('c', 2), ('a', 0), ('b', 1)])

O resultado um objeto dict_items, que um iterador que percorre os pares chave-valor. Voc
pode us-lo em um loop for, desta forma:
>>> for key, value in d.items():
... print(key, value)
...
c 2
a 0
b 1

Como se poderia esperar de um dicionrio, os itens no esto em nenhuma ordem em particular.


Indo em outra direo, voc pode usar uma lista de tuplas para inicializar um novo dicionrio:
>>> t = [('a', 0), ('c', 2), ('b', 1)]
>>> d = dict(t)
>>> d
{'a': 0, 'c': 2, 'b': 1}

Combinar dict com zip produz uma forma concisa de criar um dicionrio:
>>> d = dict(zip('abc', range(3)))
>>> d
{'a': 0, 'c': 2, 'b': 1}

O mtodo de dicionrio update tambm recebe uma lista de tuplas e as adiciona, como pares chave-
valor, a um dicionrio existente.
comum usar tuplas como chaves em dicionrios (principalmente porque voc no pode usar listas).
Por exemplo, uma lista telefnica poderia mapear pares de sobrenome e primeiro nome a nmeros de
telefone. Supondo que tenhamos definido last, first e number, podemos escrever:
directory[last, first] = number

A expresso entre chaves uma tupla. Podemos usar atribuio de tuplas para atravessar este
dicionrio:
for last, first in directory:
print(first, last, directory[last,first])
126

Este loop atravessa as chaves em directory, que so tuplas. Ele atribui os elementos de cada tupla para
last e first, e ento exibe o nome e nmero de telefone correspondente.

H duas formas de representar tuplas em um diagrama de estado. A verso mais detalhada mostra os
ndices e elementos como aparecem em uma lista. Por exemplo, a tupla (Cleese, John) apareceria
como na Figura 12.1.

Figura 12.1 Diagrama de estado de uma tupla.


No entanto, em um diagrama maior, voc pode querer omitir os detalhes. Por exemplo, um diagrama da
lista telefnica poderia ser como o da Figura 12.2.

Figura 12.2 Diagrama de estado de um dicionrio com chaves do tipo tupla.


Aqui as tuplas so mostradas usando a sintaxe do Python para simplificar o grfico. O nmero de
telefone no diagrama a linha de reclamaes da BBC, ento, por favor, no ligue para l.

12.7 - Sequncias de sequncias


Eu me concentrei em listas de tuplas, mas quase todos os exemplos neste captulo tambm funcionam
com listas de listas, tuplas de tuplas e tuplas de listas. Para evitar enumerar as combinaes possveis,
s vezes mais fcil falar sobre sequncias de sequncias.
Em muitos contextos, os tipos diferentes de sequncias (strings, listas e tuplas) podem ser usados de
forma intercambivel. Ento, como escolher uma em vez da outra?
Para comear com o bvio, as strings so mais limitadas que outras sequncias porque os elementos
tm de ser caracteres. Tambm so imutveis. Se precisar da capacidade de alterar caracteres em uma
string (em vez de criar outra string) voc pode querer usar uma lista de caracteres.
127

As listas so mais comuns que as tuplas, principalmente porque so mutveis. Mas h alguns casos em
que voc pode preferir tuplas:
1. Em alguns contextos, como em uma instruo return, sintaticamente mais simples criar
uma tupla que uma lista.
2. Se quiser usar uma sequncia como uma chave de dicionrio, preciso usar um tipo imutvel
como uma tupla ou string.
3. Se estiver passando uma sequncia como um argumento a uma funo, usar tuplas reduz o
potencial de comportamento inesperado devido a alias.
Como tuplas so imutveis, elas no fornecem mtodos como sort e reverse, que alteram listas
existentes. Porm, o Python fornece a funo integrada sorted, que recebe qualquer sequncia e
retorna uma nova lista com os mesmos elementos ordenados, e reversed, que recebe uma sequncia
e retorna um iterador que percorre a lista em ordem reversa.

12.8 - Depurao
As listas, os dicionrios e as tuplas so exemplos de estruturas de dados; neste captulo estamos
comeando a ver estruturas de dados compostas, como as listas de tuplas ou dicionrios que contm
tuplas como chaves e listas como valores. As estruturas de dados compostas so teis, mas so
propensas ao que chamo de erros de forma; isto , erros causados quando uma estrutura de dados tem o
tipo, tamanho ou estrutura incorretos. Por exemplo, se voc estiver esperando uma lista com um
nmero inteiro e eu der apenas o nmero inteiro (no em uma lista), no vai funcionar.
Para ajudar a depurar esses tipos de erro, escrevi um mdulo chamado structshape, que fornece
uma funo, tambm chamada structshape, que recebe qualquer tipo de estrutura de dados como
argumento e retorna uma string, que resume sua forma. Voc pode baix-la em
http://thinkpython2.com/code/structshape.py.
Aqui est o resultado de uma lista simples:
>>> from structshape import structshape
>>> t = [1, 2, 3]
>>> structshape(t)
'list of 3 int'

Um programa mais sofisticado pode escrever list of 3 ints, mas mais fcil no lidar com plurais.
Aqui est uma lista de listas:
>>> t2 = [[1,2], [3,4], [5,6]]
>>> structshape(t2)
'list of 3 list of 2 int'

Se os elementos da lista no forem do mesmo tipo, structshape os agrupa, na ordem, por tipo:
128

>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]


>>> structshape(t3)
'list of (3 int, float, 2 str, 2 list of int, int)'

Aqui est uma lista de tuplas:


>>> s = 'abc'
>>> lt = list(zip(t, s))
>>> structshape(lt)
'list of 3 tuple of (int, str)'

E aqui est um dicionrio com trs itens que mapeia nmeros inteiros a strings:
>>> d = dict(lt)
>>> structshape(d)
'dict of 3 int->str'

Se estiver com problemas para monitorar suas estruturas de dados, o structshape pode ajudar.

12.9 - Glossrio
tupla
Sequncia imutvel de elementos.
atribuio de tupla
Atribuio com uma sequncia no lado direito e uma tupla de variveis esquerda. O lado direito
avaliado e ento seus elementos so atribudos s variveis esquerda.
gather
Operao para montar uma tupla com argumento de comprimento varivel.
scatter
Operao para tratar uma sequncia como uma lista de argumentos.
objeto zip
O resultado de chamar uma funo integrada zip; um objeto que se repete por uma sequncia de
tuplas.
iterador
Objeto que pode se repetir por uma sequncia, mas que no oferece operadores de lista e
mtodos.
estrutura de dados
Coleo de valores relacionados, muitas vezes organizados em listas, dicionrios, tuplas etc.
erro de forma
Erro causado pelo fato de o valor ter a forma incorreta; isto , tipo ou tamanho incorreto.

12.10 - Exerccios
Exerccio 12.1
Escreva uma funo chamada most_frequent que receba uma string e exiba as letras em ordem
decrescente de frequncia. Encontre amostras de texto de vrios idiomas diferentes e veja como a
129

frequncia das letras varia entre os idiomas. Compare seus resultados com as tabelas em
http://en.wikipedia.org/wiki/Letter_frequencies.
Soluo: http://thinkpython2.com/code/most_frequent.py.

Exerccio 12.2
Mais anagramas!
1. Escreva um programa que leia uma lista de palavras de um arquivo (veja Leitura de listas de
palavras, na pgina 133) e imprima todos os conjuntos de palavras que so anagramas.
Aqui est um exemplo de como a sada pode parecer:
['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']
['retainers', 'ternaries']
['generating', 'greatening']
['resmelts', 'smelters', 'termless']

Dica: voc pode querer construir um dicionrio que mapeie uma coleo de letras a uma lista de
palavras que podem ser soletradas com essas letras. A pergunta : como representar a coleo de letras
de forma que possa ser usada como uma chave?
1. Altere o programa anterior para que exiba a lista mais longa de anagramas primeiro, seguido
pela segunda mais longa, e assim por diante.
2. No Scrabble, um bingo quando voc joga todas as sete peas na sua estante, junto com uma
pea no tabuleiro, para formar uma palavra de oito letras. Que coleo de oito letras forma o
maior nmero possvel de bingos? Dica: h sete.
Soluo: http://thinkpython2.com/code/anagram_sets.py.

Exerccio 12.3
Duas palavras formam um par de mettese se voc puder transformar uma na outra trocando duas
letras, por exemplo, converse e conserve. Escreva um programa que descubra todos os pares de
mettese no dicionrio. Dica: no teste todos os pares de palavras e no teste todas as trocas possveis.
Soluo: http://thinkpython2.com/code/metathesis.py. Crdito: este exerccio foi inspirado por um
exemplo em http://puzzlers.org.

Exerccio 12.4
Aqui est outro quebra-cabea do programa Car Talk (http://www.cartalk.com/content/puzzlers):
Qual a palavra inglesa mais longa, que permanece uma palavra inglesa vlida, conforme vai
removendo suas letras, uma aps a outra?
130

Agora, as letras podem ser retiradas do fim ou do meio, mas voc no pode reajustar nenhuma delas.
Cada vez que remove uma letra, voc acaba com outra palavra inglesa. Se fizer isto, eventualmente
voc acabar com uma letra e isso tambm ser uma palavra inglesa; uma encontrada no dicionrio.
Quero saber qual a palavra mais longa e quantas letras tem?
Vou dar um pequeno exemplo modesto: Sprite. Ok? Voc comea com sprite, tira uma letra do interior
da palavra, tira o r, e ficamos com a palavra spite, ento tiramos o e do fim, ficamos com spit, tiramos o
s, ficamos com pit, it e I.
Escreva um programa que encontre todas as palavras que podem ser reduzidas desta forma, e ento
encontre a mais longa.
Este exerccio um pouco mais desafiador que a maioria, ento aqui esto algumas sugestes:
1. Voc pode querer escrever uma funo que receba uma palavra e calcule uma lista de todas as
palavras que podem ser formadas retirando uma letra. Esses so os filhos da palavra.
2. Recursivamente, uma palavra redutvel se algum de seus filhos for redutvel. Como caso base,
voc pode considerar a string vazia redutvel.
3. A lista de palavras que forneci, words.txt, no contm palavras de uma letra s. Portanto, voc
pode querer acrescentar I, a, e a string vazia.
4. Para melhorar o desempenho do seu programa, voc pode querer memorizar as palavras
conhecidas por serem redutveis.
Soluo: http://thinkpython2.com/code/reducible.py.

Captulo 13: Estudo de caso: seleo de


estrutura de dados
Neste ponto voc j aprendeu sobre as principais estruturas de dados do Python, e viu alguns
algoritmos que as usam. Se quiser saber mais sobre algoritmos, pode ler o Captulo 21. Mas isso no
necessrio para continuar, pode l-lo a qualquer momento em que tenha interesse.
Este captulo apresenta um estudo de caso com exerccios que fazem pensar sobre a escolha de
estruturas de dados e prticas de uso delas.

13.1 - Anlise de frequncia de palavras


Como de hbito, voc deve pelo menos tentar fazer os exerccios antes de ler as minhas solues.
131

Exerccio 13.1
Escreva um programa que leia um arquivo, quebre cada linha em palavras, remova os espaos em
branco e a pontuao das palavras, e as converta em letras minsculas.
Dica: O mdulo string oferece uma string chamada whitespace, que contm espao, tab, newline
etc., e punctuation, que contm os caracteres de pontuao. Vamos ver se conseguimos fazer o
Python falar palavres:
>>> import string
>>> string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'

Alm disso, voc pode usar os mtodos de string, strip, replace e translate.

Exerccio 13.2
Acesse o Projeto Gutenberg (http://gutenberg.org) e baixe seu livro favorito em domnio pblico em
formato de texto simples.
Altere seu programa do exerccio anterior para ler o livro que voc baixou, pulando as informaes do
cabealho no incio do arquivo e processando o resto das palavras como antes.
Ento altere o programa para contar o nmero total de palavras no livro e o nmero de vezes que cada
palavra usada.
Exiba o nmero de palavras diferentes usadas no livro. Compare livros diferentes de autores diferentes,
escritos em eras diferentes. Que autor usa o vocabulrio mais extenso?

Exerccio 13.3
Altere o programa do exerccio anterior para exibir as 20 palavras mais frequentes do livro.

Exerccio 13.4
Altere o programa anterior para ler uma lista de palavras (ver Leitura de listas de palavras, na pgina
133) e ento exiba todas as palavras do livro que no esto na lista de palavras. Quantas delas so erros
ortogrficos? Quantas delas so palavras comuns que deveriam estar na lista de palavras, e quantas so
muito obscuras?

13.2 - Nmeros aleatrios


Com as mesmas entradas, a maior parte dos programas gera as mesmas sadas a cada vez, ento eles
so chamados de deterministas. Determinismo normalmente uma coisa boa, j que esperamos que o
mesmo clculo produza o mesmo resultado. Para algumas aplicaes, entretanto, queremos que o
computador seja imprevisvel. Os jogos so um exemplo bvio, mas h outros.
132

Fazer um programa no determinista de verdade difcil; mas h formas de, pelo menos, faz-los
parecer que no so. Uma delas usar algoritmos que geram nmeros pseudoaleatrios. Os nmeros
pseudoaleatrios no so aleatrios mesmo porque so gerados por um clculo determinista, mas
quase impossvel distingui-los dos aleatrios s olhando para os nmeros.
O mdulo random fornece funes que geram nmeros pseudoaleatrios (que chamarei apenas de
aleatrios daqui em diante).
A funo random retorna um nmero de ponto flutuante entre 0,0 e 1,0 (incluindo 0,0, mas no 1,0).
Cada vez que random chamada, voc recebe o prximo nmero em uma longa srie. Para ver uma
amostra, execute este loop:
import random
for i in range(10):
x = random.random()
print(x)

A funo randint recebe os parmetros low e high e retorna um nmero inteiro entre low e high
(inclusive ambos):
>>> random.randint(5, 10)
5
>>> random.randint(5, 10)
9

Para escolher aleatoriamente um elemento de uma sequncia, voc pode usar choice:
>>> t = [1, 2, 3]
>>> random.choice(t)
2
>>> random.choice(t)
3

O mdulo random tambm fornece funes para gerar valores aleatrios de distribuies contnuas,
incluindo gaussianas, exponenciais, gamma e algumas outras.

Exerccio 13.5
Escreva uma funo chamada choose_from_hist que receba um histograma como definido em
Um dicionrio como uma coleo de contadores, na pgina 163, e retorne um valor aleatrio do
histograma, escolhido por probabilidade em proporo frequncia. Por exemplo, para este
histograma:
>>> t = ['a', 'a', 'b']
>>> hist = histogram(t)
>>> hist
{'a': 2, 'b': 1}

sua funo deve retornar a com a probabilidade de 2/3 e b com a probabilidade 1/3.
133

13.3 - Histograma de palavras


uma boa ideia tentar fazer os exerccios anteriores antes de continuar. Voc pode baixar minha
soluo em http://thinkpython2.com/code/analyze_book1.py. Tambm vai precisar de
http://thinkpython2.com/code/emma.txt.
Aqui est um programa que l um arquivo e constri um histograma das palavras no arquivo:
import string

def process_file(filename):
hist = dict()
fp = open(filename)
for line in fp:
process_line(line, hist)
return hist

def process_line(line, hist):


line = line.replace('-', ' ')
for word in line.split():
word = word.strip(string.punctuation + string.whitespace)
word = word.lower()
hist[word] = hist.get(word, 0) + 1

hist = process_file('emma.txt')

Este programa l emma.txt, que contm o texto de Emma, de Jane Austen.

process_file faz o loop pelas linhas do arquivo, passando-as uma a uma para process_line.
O histograma hist est sendo usado como um acumulador.
process_line usa o mtodo de string replace para substituir hifens por espaos antes de usar split
para quebrar a linha em uma lista de strings. Ele atravessa a lista de palavras e usa strip e lower para
retirar a pontuao e converter tudo em letras minsculas. (Dizer que as strings so convertidas
uma forma simples de explicar a coisa; lembre-se de que as strings so imutveis, ento mtodos como
strip e lower retornam novas strings.)
Finalmente, process_line atualiza o histograma, criando um novo item ou incrementando um
existente.
Para contar o nmero total de palavras no arquivo, podemos somar as frequncias no histograma:
def total_words(hist):
return sum(hist.values())

O nmero de palavras diferentes somente o nmero de itens no dicionrio:


def different_words(hist):
return len(hist)

Aqui est o cdigo para exibir os resultados:


134

print('Total number of words:', total_words(hist))


print('Number of different words:', different_words(hist))

E os resultados:
Total number of words: 161080
Number of different words: 7214

13.4 - Palavras mais comuns


Para encontrar as palavras mais comuns, podemos fazer uma lista de tuplas, onde cada tupla contenha
uma palavra e a sua frequncia, e ordenar a lista.
A funo seguinte recebe um histograma e retorna uma lista de tuplas de palavras e frequncias:
def most_common(hist):
t = []
for key, value in hist.items():
t.append((value, key))
t.sort(reverse=True)
return t

Em cada tupla, a frequncia aparece primeiro, ento a lista resultante ordenada por frequncia. Aqui
est um loop que imprime as 10 palavras mais comuns:
t = most_common(hist)
print('The most common words are:')
for freq, word in t[:10]:
print(word, freq, sep='\\t')

Uso o argumento de palavra-chave sep para que print use um caractere tab como separador, em
vez de um espao, assim a segunda coluna fica alinhada verticalmente. Aqui esto os resultados de
Emma:
The most common words are:
to 5242
the 5205
and 4897
of 4295
i 3191
a 3130
it 2529
her 2483
was 2400
she 2364

Este cdigo pode ser simplificado usando o parmetro key da funo sort. Se tiver curiosidade, pode
ler sobre ele em https://wiki.python.org/moin/HowTo/Sorting.
135

13.5 - Parmetros opcionais


Vimos funes integradas e mtodos que recebem argumentos opcionais. possvel escrever funes
definidas pelos programadores com argumentos opcionais, tambm. Por exemplo, aqui est uma funo
que exibe as palavras mais comuns em um histograma:
def print_most_common(hist, num=10):
t = most_common(hist)
print('The most common words are:')
for freq, word in t[:num]:
print(word, freq, sep='\\t')

O primeiro parmetro necessrio; o segundo opcional. O valor padro de num 10.

Se voc s fornecer um argumento:


print_most_common(hist)

num recebe o valor padro. Se fornecer dois argumentos:


print_most_common(hist, 20)

num recebe o valor do argumento em vez disso. Em outras palavras, o argumento opcional ignora o
valor padro.
Se uma funo tem ambos os parmetros obrigatrio e opcional, todos os parmetros necessrios tm
que vir primeiro, seguidos pelos opcionais.

13.6 - Subtrao de dicionrio


Encontrar as palavras do livro que no esto na lista de palavras de words.txt um problema que voc
pode reconhecer como subtrao de conjuntos; isto , queremos encontrar todas as palavras de um
conjunto (as palavras no livro) que no esto no outro (as palavras na lista).
subtract recebe os dicionrios d1 e d2 e devolve um novo dicionrio que contm todas as chaves
de d1 que no esto em d2. Como no nos preocupamos com os valores, estabelecemos todos como
None:
def subtract(d1, d2):
res = dict()
for key in d1:
if key not in d2:
res[key] = None
return res

Para encontrar as palavras no livro que no esto em words.txt, podemos usar process_file para
construir um histograma para words.txt, e ento subtrair:
words = process_file('words.txt')
136

diff = subtract(hist, words)


print("Words in the book that aren't in the word list:")
for word in diff:
print(word, end=' ')

Aqui esto alguns resultados de Emma:


Words in the book that aren't in the word list:
rencontre jane's blanche woodhouses disingenuousness
friend's venice apartment ...

Algumas dessas palavras so nomes e possessivos. Os outros, como rencontre, j no so de uso


comum. Mas algumas so palavras comuns que realmente deveriam estar na lista!

Exerccio 13.6
O Python fornece uma estrutura de dados chamada set, que fornece muitas operaes de conjunto.
Voc pode ler sobre elas em Conjuntos, na pgina 274, ou ler a documentao em
http://docs.python.org/3/library/stdtypes.html#types-set.
Escreva um programa que use a subtrao de conjuntos para encontrar palavras no livro que no esto
na lista de palavras.
Soluo: http://thinkpython2.com/code/analyze_book2.py.

13.7 - Palavras aleatrias


Para escolher uma palavra aleatria do histograma, o algoritmo mais simples construir uma lista com
vrias cpias de cada palavra, segundo a frequncia observada, e ento escolher da lista:
def random_word(h):
t = []
for word, freq in h.items():
t.extend([word] * freq)
return random.choice(t)

A expresso [word] * freq cria uma lista com freq cpias da string word. O mtodo extend
similar a append, exceto pelo argumento, que uma sequncia.

Esse algoritmo funciona, mas no muito eficiente; cada vez que voc escolhe uma palavra aleatria,
ele reconstri a lista, que to grande quanto o livro original. Uma melhoria bvia construir a lista
uma vez e ento fazer selees mltiplas, mas a lista ainda grande.
Uma alternativa :
1. Usar keys para conseguir uma lista das palavras no livro.

2. Construir uma lista que contenha a soma cumulativa das frequncias das palavras (veja o
Exerccio 10.2). O ltimo item desta lista o nmero total de palavras no livro, n.
137

3. Escolher um nmero aleatrio de 1 a n. Use uma pesquisa de bisseo (veja o Exerccio 10.10)
para encontrar o ndice onde o nmero aleatrio seria inserido na soma cumulativa.
4. Usar o ndice para encontrar a palavra correspondente na lista de palavras.

Exerccio 13.7
Escreva um programa que use este algoritmo para escolher uma palavra aleatria do livro.
Soluo: http://thinkpython2.com/code/analyze_book3.py.

13.8 - Anlise de Markov


Se escolher palavras do livro aleatoriamente, voc pode at captar certo sentido a partir do vocabulrio,
mas provavelmente no vai conseguir uma sentena completa:
this the small regard harriet which knightley's it most things

Uma srie de palavras aleatrias raramente faz sentido porque no h nenhuma relao entre palavras
sucessivas. Por exemplo, em uma sentena de verdade voc esperaria que um artigo como o fosse
seguido de um adjetivo ou um substantivo, e provavelmente no um verbo ou advrbio.
Uma forma de medir estes tipos de relaes a anlise de Markov, que caracteriza, para uma dada
sequncia de palavras, o que poderia vir a seguir, segundo a probabilidade. Por exemplo, a cano
Eric, the Half a Bee comea assim:
Half a bee, philosophically,
Must, ipso facto, half not be.
But half the bee has got to be
Vis a vis, its entity. Dyou see?
But can a bee be said to be
Or not to be an entire bee
When half the bee is not a bee
Due to some ancient injury?

Nesse texto, a frase half the sempre seguida pela palavra bee, mas a frase the bee pode ser
seguida por has ou is.
O resultado da anlise de Markov um mapeamento de cada prefixo (como half the e the bee) a
todos os sufixos possveis (como has e is).
Com este mapeamento voc pode gerar um texto aleatrio, comeando com qualquer prefixo e
escolhendo a esmo entre os sufixos possveis. Em seguida, voc pode combinar o fim do prefixo e o
novo sufixo para formar o prximo prefixo e repetir.
Por exemplo, se voc comear com o prefixo Half a, ento a prxima palavra tem que ser bee,
porque o prefixo s aparece uma vez no texto. O prefixo seguinte a bee, ento o prximo sufixo
poderia ser philosophically, be ou due.
138

Neste exemplo, o comprimento do prefixo sempre dois, mas voc pode fazer a anlise de Markov
com qualquer comprimento de prefixo.

Exerccio 13.8
Anlise de Markov:
a) Escreva um programa que leia o texto de um arquivo e execute a anlise de Markov. O resultado
deve ser um dicionrio que mapeie prefixos a uma coleo de possveis sufixos. A coleo pode ser
uma lista, tupla ou dicionrio; voc que dever fazer a escolha adequada. Voc pode testar seu
programa com um comprimento de prefixo 2, mas deve escrever o programa de forma que seja fcil
testar outros comprimentos.
b) Acrescente uma funo ao programa anterior para gerar texto aleatrio baseado na anlise de
Markov. Aqui est um exemplo de exemplo de Emma com o comprimento de prefixo 2.
He was very clever, be it sweetness or be angry, ashamed or only amused, at such a stroke.
She had never thought of Hannah till you were never meant for me? I cannot make
speeches, Emma: he soon cut it all himself.

Para este exemplo, deixei a pontuao anexada s palavras. O resultado quase sintaticamente correto,
mas no exatamente. Semanticamente, quase faz sentido, mas no exatamente.
O que acontece se voc aumentar o comprimento dos prefixos? O texto aleatrio faz mais sentido?
c) Uma vez que o seu programa esteja funcionando, voc pode querer tentar uma mistura: se combinar
o texto de dois ou mais livros, o texto aleatrio gerado misturar o vocabulrio e frases das fontes de
formas interessantes.
Crdito: este estudo de caso baseado em um exemplo de Kernighan and Pike, The Practice of
Programming, Addison-Wesley, 1999.
uma boa ideia tentar fazer este exerccio antes de continuar; depois voc pode baixar a minha soluo
em http://thinkpython2.com/code/markov.py. Tambm vai precisar de
http://thinkpython2.com/code/emma.txt.

13.9 - Estruturas de dados


Usar anlise de Markov para gerar o texto aleatrio divertido, mas tambm h uma razo para este
exerccio: a seleo da estrutura de dados. Na sua soluo para os exerccios anteriores, voc teve que
selecionar:
como representar os prefixos;

como representar a coleo de sufixos possveis;


139

como representar o mapeamento de cada prefixo coleo de possveis sufixos.

O ltimo fcil: um dicionrio a escolha bvia para um mapeamento de chaves a valores


correspondentes.
Para os prefixos, as opes mais bvias so strings, listas de strings ou tuplas de strings.
Para os sufixos, uma opo uma lista; outra um histograma (dicionrio).
Como voc deve escolher? O primeiro passo pensar nas operaes que voc vai precisar implementar
para cada estrutura de dados. Para os prefixos, preciso poder retirar palavras do comeo e acrescentar
no fim. Por exemplo, se o prefixo atual Half a e a prxima palavra bee, voc tem que poder
formar o prximo prefixo, a bee.
Sua primeira escolha pode ser uma lista, pois fcil acrescentar e retirar elementos, mas tambm
precisamos poder usar os prefixos como chaves em um dicionrio, para excluir listas. Com tuplas, voc
no pode acrescentar ou retirar, mas pode usar o operador de adio para formar uma nova tupla:
def shift(prefix, word):
return prefix[1:] + (word,)

shift recebe uma tupla de palavras, prefix, e uma string, word, e forma uma nova tupla que tem todas
as palavras em prefix, exceto a primeira e word adicionada no final.
Para a coleo de sufixos, as operaes que precisamos executar incluem a soma de um novo sufixo
(ou aumento da frequncia de um existente), e a escolha de um sufixo aleatrio.
Acrescentar um novo sufixo igualmente fcil para a implementao da lista ou do histograma.
Escolher um elemento aleatrio de uma lista fcil; escolher de um histograma mais difcil de fazer
de forma eficiente (ver o Exerccio 13.7).
Por enquanto, falamos principalmente sobre a facilidade de implementao, mas h outros fatores a
considerar na escolha das estruturas de dados. Um deles o tempo de execuo. s vezes, h uma
razo terica para esperar que uma estrutura de dados seja mais rpida que outra; por exemplo, eu
mencionei que o operador in mais rpido para dicionrios que para listas, pelo menos quando o
nmero de elementos grande.
Porm, muitas vezes no se sabe de antemo qual implementao ser mais rpida. Uma opo
implementar ambas e ver qual melhor. Esta abordagem chamada de benchmarking. Uma alternativa
prtica escolher a estrutura de dados mais fcil para implementar, e ento ver se rpida o suficiente
para a aplicao desejada. Se for o caso, no preciso continuar. Do contrrio, h ferramentas, como o
mdulo profile, que podem identificar os lugares em um programa que tomam mais tempo de
execuo.
Outro fator a considerar o espao de armazenamento. Por exemplo, usar um histograma para a
coleo de sufixos pode tomar menos espao porque s preciso armazenar cada palavra uma vez, no
140

importa quantas vezes aparea no texto. Em alguns casos, a economia de espao tambm pode fazer o
seu programa rodar mais rpido e, em casos extremos, seu programa pode simplesmente nem rodar se
ficar sem memria. Porm, para muitas aplicaes, o espao uma considerao secundria depois do
tempo de execuo.
Um ltimo comentrio: nessa discusso, a ideia implcita que devemos usar uma estrutura de dados
tanto para anlise como para gerao. Entretanto, como essas fases so separadas, tambm seria
possvel usar uma estrutura para a anlise e ento convert-la em outra estrutura para a gerao. Isso
seria uma vantagem se o tempo poupado durante a gerao excedesse o tempo decorrido na converso.

13.10 - Depurao
Quando estiver depurando um programa, especialmente se estiver trabalhando em um erro difcil, h
cinco coisas que voc pode tentar:
Leitura
Examine seu cdigo, leia-o para voc mesmo e verifique se diz o que voc pensou em dizer.
Execuo
Experimente fazer alteraes e executar verses diferentes. Muitas vezes, ao se expor a coisa
certa no lugar certo do programa, o problema fica bvio, mas pode ser necessrio construir o
scaffolding.
Ruminao
Pense por algum tempo! Qual o tipo do erro: de sintaxe, de tempo de execuo ou semntico?
Quais informaes voc consegue obter a partir das mensagens de erro, ou da sada do programa?
Que tipo de erro pode causar o problema que est vendo? O que voc mudou por ltimo, antes
que o problema aparecesse?
Conversa com o pato de borracha (rubberducking)
Ao explicar o problema a algum, s vezes voc consegue encontrar a resposta antes de terminar
a explicao. Muitas vezes, no preciso nem haver outra pessoa; voc pode falar at com um
pato de borracha. E essa a origem de uma estratgia bem conhecida chamada de depurao do
pato de borracha. No estou inventando isso, veja
https://en.wikipedia.org/wiki/Rubber_duck_debugging.
Retirada
Em um determinado ponto, a melhor coisa a fazer voltar atrs e desfazer as alteraes recentes,
at chegar de volta a um programa que funcione e que voc entenda. Ento voc pode comear a
reconstruir.

Programadores iniciantes s vezes ficam presos em uma dessas atividades e esquecem das outras. Cada
atividade vem com o seu prprio modo de falha.
Por exemplo, a leitura do seu cdigo pode ajudar se o problema um erro tipogrfico, mas no se o
problema for conceitual. Se voc no entende o que o seu programa faz, pode l-lo cem vezes e nunca
ver o erro, porque o erro est na sua cabea.
141

Fazer experincias pode ajudar, especialmente se voc executar testes pequenos e simples. No entanto,
se executar experincias sem pensar ou ler seu cdigo, pode cair em um padro que chamo de
programao aleatria, que o processo de fazer alteraes aleatrias at que o programa faa a
coisa certa. Obviamente, a programao aleatria pode levar muito tempo.
preciso pensar um pouco. A depurao como cincia experimental. Deve haver pelo menos uma
hiptese sobre qual o problema. Se houver duas ou mais possibilidades, tente pensar em um teste que
eliminaria uma delas.
No obstante, at as melhores tcnicas de depurao falharo se houver erros demais, ou se o cdigo
que est tentando corrigir for grande e complicado demais. s vezes, a melhor opo voltar atrs,
simplificando o programa at chegar a algo que funcione e que voc entenda.
Programadores iniciantes muitas vezes relutam em voltar atrs porque no suportam a ideia de eliminar
sequer uma linha de cdigo (mesmo se estiver errada). Para voc se sentir melhor, copie seu programa
em outro arquivo antes de comear a desmont-lo. Ento voc pode copiar as partes de volta, uma a
uma.
Encontrar um erro difcil exige leitura, execuo, ruminao, e, s vezes, a retirada. Se empacar em
alguma dessas atividades, tente as outras.

13.11 - Glossrio
determinista
Relativo a um programa que faz a mesma coisa cada vez que executado, se receber as mesmas
entradas.
pseudoaleatrio
Relativo a uma sequncia de nmeros que parecem ser aleatrios, mas que so gerados por um
programa determinista.
valor padro
Valor dado a um parmetro opcional se no houver nenhum argumento.
ignorar (override)
Substituir um valor padro por um argumento.
benchmarking
Processo de escolha entre estruturas de dados pela implementao de alternativas e testes em uma
amostra de entradas possveis.
depurao do pato de borracha
Depurar explicando o problema a um objeto inanimado como um pato de borracha. Articular o
problema pode ajudar a resolv-lo, mesmo se o pato de borracha no conhecer Python.
142

13.12 - Exerccios
Exerccio 13.9
A classificao de uma palavra a sua posio em uma lista de palavras classificadas por frequncia:
a palavra mais comum tem a classificao 1, a segunda mais comum 2 etc.
A lei de Zipf descreve a relao entre classificaes e frequncias das palavras em linguagens naturais
(http://en.wikipedia.org/wiki/Zipfs_law). Ela prev especificamente que a frequncia, f, da palavra
com classificao r :
f = crs

onde s e c so parmetros que dependem do idioma e do texto. Se voc tomar o logaritmo de ambos os
lados desta equao, obtm:
log f = log c s log r

Se voc traar o log de f contra o log de r, ter uma linha reta com uma elevao -s e interceptar o log
de c.
Escreva um programa que leia um texto em um arquivo, conte as frequncias das palavras e exiba uma
linha para cada palavra, em ordem descendente da frequncia, com log de f e log de r. Use o programa
grfico de sua escolha para traar os resultados e verifique se formam uma linha reta. Voc pode
estimar o valor de s?
Soluo: http://thinkpython2.com/code/zipf.py. Para executar a minha soluo, voc vai precisar do
mdulo de grficos matplotlib. Se voc instalou o Anaconda, j tem o matplotlib; se no tiver,
preciso instal-lo.

Captulo 14: Arquivos


Este captulo apresenta a ideia de programas persistentes, que mantm dados em armazenamento
permanente, e mostra como usar tipos diferentes de armazenamento permanente, como arquivos e
bancos de dados.

14.1 - Persistncia
A maioria dos programas que vimos at agora so transitrios, porque so executados por algum tempo
e produzem alguma sada, mas, quando terminam, seus dados desaparecem. Se executar o programa
novamente, ele comea novamente do zero.
143

Outros programas so persistentes: rodam por muito tempo (ou todo o tempo); mantm pelo menos
alguns dos seus dados em armazenamento permanente (uma unidade de disco rgido, por exemplo); e
se so desligados e reiniciados, continuam de onde pararam.
Exemplos de programas persistentes so sistemas operacionais, que rodam praticamente durante todo o
tempo em que um computador est ligado, e servidores web, que rodam todo o tempo, esperando
pedidos de entrada na rede.
Uma das formas mais simples para programas manterem seus dados lendo e escrevendo arquivos de
texto. J vimos programas que leem arquivos de texto; neste captulo veremos programas que os
escrevem.
Uma alternativa armazenar o estado do programa em um banco de dados. Neste captulo apresentarei
um banco de dados simples e um mdulo, pickle, que facilita o armazenamento de dados de programas.

14.2 - Leitura e escrita


Um arquivo de texto uma sequncia de caracteres armazenados em um meio permanente como uma
unidade de disco rgido, pendrive ou CD-ROM. Vimos como abrir e ler um arquivo em Leitura de
listas de palavras na pgina 133.
Para escrever um arquivo texto, preciso abri-lo com o modo 'wt' como segundo parmetro:
>>> fout = open('output.txt', 'wt')

Se o arquivo j existe, abri-lo em modo de escrita elimina os dados antigos e comea tudo de novo,
ento tenha cuidado! Se o arquivo no existir, criado um arquivo novo.
open retorna um objeto de arquivo que fornece mtodos para trabalhar com o arquivo. O mtodo write
pe dados no arquivo:
>>> line1 = "This here's the wattle,\n"
>>> fout.write(line1)
24

O valor devolvido o nmero de caracteres que foram escritos. O objeto de arquivo monitora a posio
em que est, ento se voc chamar write novamente, os novos dados so acrescentados ao fim do
arquivo:
>>> line2 = "the emblem of our land.\n"
>>> fout.write(line2)
24

Ao terminar de escrever, voc deve fechar o arquivo:


>>> fout.close()
144

Se no fechar o arquivo, ele fechado para voc quando o programa termina.

14.3 - Operador de formatao


O argumento de write tem que ser uma string, ento, se quisermos inserir outros valores em um
arquivo, precisamos convert-los em strings. O modo mais fcil de fazer isso com str:
>>> x = 52
>>> fout.write(str(x))

Uma alternativa usar o operador de formatao, %. Quando aplicado a nmeros inteiros, % o


operador de mdulo. No entanto, quando o primeiro operando uma string, % o operador de
formatao.
O primeiro operando a string de formatao, que contm uma ou vrias sequncias de formatao que
especificam como o segundo operando deve ser formatado. O resultado uma string.
Por exemplo, a sequncia de formatao %d significa que o segundo operando deve ser formatado
como um nmero inteiro decimal:
>>> camels = 42
>>> '%d' % camels
'42'

O resultado a string '42', que no deve ser confundida com o valor inteiro 42.

Uma sequncia de formatao pode aparecer em qualquer lugar na string, ento voc pode embutir um
valor em uma sentena:
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'

Se houver mais de uma sequncia de formatao na string, o segundo argumento tem que ser uma
tupla. Cada sequncia de formatao combinada com um elemento da tupla, nesta ordem.
O seguinte exemplo usa '%d' para formatar um nmero inteiro, '%g' para formatar um nmero de
ponto flutuante e '%s' para formatar qualquer objeto como uma string:
>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'

O nmero de elementos na tupla tem de corresponder ao nmero de sequncias de formatao na


string. Alm disso, os tipos dos elementos tm de corresponder s sequncias de formatao:
>>> '%d %d %d' % (1, 2)
TypeError: not enough arguments for format string
>>> '%d' % 'dollars'
TypeError: %d format: a number is required, not str
145

No primeiro exemplo no h elementos suficientes; no segundo, o elemento do tipo incorreto.


Para obter mais informaes sobre o operador de formato, veja
https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting. Voc pode ler sobre uma
alternativa mais eficiente, o mtodo de formatao de strings, em
https://docs.python.org/3/library/stdtypes.html#str.format.

14.4 - Nomes de arquivo e caminhos


Os arquivos so organizados em diretrios (tambm chamados de pastas). Cada programa em
execuo tem um diretrio atual, que o diretrio-padro da maior parte das operaes. Por
exemplo, quando voc abre um arquivo de leitura, Python o procura no diretrio atual.
O mdulo os fornece funes para trabalhar com arquivos e diretrios (os a abreviao de sistema
operacional em ingls). os.getcwd devolve o nome do diretrio atual:
>>> import os
>>> cwd = os.getcwd()
>>> cwd
'/home/dinsdale'

cwd a abreviao de diretrio de trabalho atual em ingls. O resultado neste exemplo


/home/dinsdale, que o diretrio-padro de um usurio chamado dinsdale.

Uma string como '/home/dinsdale', que identifica um arquivo ou diretrio, chamada de


caminho (path).
Um nome de arquivo simples, como memo.txt, tambm considerado um caminho, mas um
caminho relativo, porque se relaciona ao diretrio atual. Se o diretrio atual /home/dinsdale, o
nome de arquivo memo.txt se referiria a /home/dinsdale/memo.txt.

Um caminho que comea com / no depende do diretrio atual; isso chamado de caminho absoluto.
Para encontrar o caminho absoluto para um arquivo, voc pode usar os.path.abspath:
>>> os.path.abspath('memo.txt')
'/home/dinsdale/memo.txt'

os.path fornece outras funes para trabalhar com nomes de arquivo e caminhos. Por exemplo,
os.path.exists que verifica se um arquivo ou diretrio existe:
>>> os.path.exists('memo.txt')
True

Se existir, os.path.isdir verifica se um diretrio:


>>> os.path.isdir('memo.txt')
False
146

>>> os.path.isdir('/home/dinsdale')
True

De forma similar, os.path.isfile verifica se um arquivo.

os.listdir retorna uma lista dos arquivos (e outros diretrios) no diretrio dado:
>>> os.listdir(cwd)
['music', 'photos', 'memo.txt']

Para demonstrar essas funes, o exemplo seguinte passeia por um diretrio, exibe os nomes de
todos os arquivos e chama a si mesmo recursivamente em todos os diretrios:
def walk(dirname):
for name in os.listdir(dirname):
path = os.path.join(dirname, name)
if os.path.isfile(path):
print(path)
else:
walk(path)

os.path.join recebe um diretrio e um nome de arquivo e os une em um caminho completo.

O mdulo os fornece uma funo chamada walk, que semelhante, s que mais verstil. Como
exerccio, leia a documentao e use-a para exibir os nomes dos arquivos em um diretrio dado e seus
subdiretrios. Voc pode baixar minha soluo em http://thinkpython2.com/code/walk.py.

14.5 - Captura de excees


Muitas coisas podem dar errado quando voc tenta ler e escrever arquivos. Se tentar abrir um arquivo
que no existe, voc recebe um IOError:
>>> fin = open('bad_file')
IOError: [Errno 2] No such file or directory: 'bad\_file'

Se no tiver permisso para acessar um arquivo:


>>> fout = open('/etc/passwd', 'w')
PermissionError: [Errno 13] Permission denied: '/etc/passwd'

E se tentar abrir um diretrio para leitura, recebe


>>> fin = open('/home')
IsADirectoryError: [Errno 21] Is a directory: '/home'

Para evitar esses erros, voc pode usar funes como os.path.exists e os.path.isfile, mas
levaria muito tempo e cdigo para verificar todas as possibilidades (se Errno 21 significa algo, pode
ser que pelo menos 21 coisas podem dar errado).
147

melhor ir em frente e tentar, e lidar com problemas se eles surgirem, que exatamente o que a
instruo try faz. A sintaxe semelhante da instruo ifelse:
try:
fin = open('bad_file')
except:
print('Something went wrong.')

O Python comea executando a clusula try. Se tudo for bem, ele ignora a clusula except e
prossegue. Se ocorrer uma exceo, o programa sai da clusula try e executa a clusula except.

Lidar com excees usando uma instruo try chama-se capturar uma exceo. Neste exemplo, a
clusula except exibe uma mensagem de erro que no muito til. Em geral, a captura de uma
exceo oferece a oportunidade de corrigir o problema ou tentar novamente, ou, ao menos, de terminar
o programa adequadamente.

14.6 - Bancos de dados


Um banco de dados um arquivo organizado para armazenar dados. Muitos bancos de dados so
organizados como um dicionrio, porque mapeiam chaves a valores. A maior diferena entre um banco
de dados e um dicionrio que o banco de dados est em um disco (ou outro armazenamento
permanente), portanto persiste depois que o programa termina.
O mdulo dbm fornece uma interface para criar e atualizar arquivos de banco de dados. Como
exemplo, criarei um banco de dados que contm legendas de arquivos de imagem.
Abrir um banco de dados semelhante abertura de outros arquivos:
>>> import dbm
>>> db = dbm.open('captions', 'c')

O modo c significa que o banco de dados deve ser criado, se ainda no existir. O resultado um
objeto de banco de dados que pode ser usado (para a maior parte das operaes) como um dicionrio.
Quando voc cria um novo item, dbm atualiza o arquivo de banco de dados:
>>> db['cleese.png'] = 'Photo of John Cleese.'

Quando voc acessa um dos itens, dbm l o arquivo:


>>> db['cleese.png']
b'Photo of John Cleese.'

O resultado um objeto bytes, o que explica o prefixo b. Um objeto bytes semelhante a uma
string, em muitos aspectos. Quando voc avanar no Python, a diferena se tornar importante, mas,
por enquanto, podemos ignor-la.
Se fizer outra atribuio a uma chave existente, o dbm substitui o valor antigo:
148

>>> db['cleese.png'] = 'Photo of John Cleese doing a silly walk.'


>>> db['cleese.png']
b'Photo of John Cleese doing a silly walk.'

Alguns mtodos de dicionrio, como keys e items, no funcionam com objetos de banco de dados. No
entanto, a iterao com um loop for, sim:
for key in db:
print(key, db[key])

Como em outros arquivos, voc deve fechar o banco de dados quando terminar:
>>> db.close()

14.7 - Usando o Pickle


Uma limitao de dbm que as chaves e os valores tm que ser strings ou bytes. Se tentar usar algum
outro tipo, vai receber um erro.
O mdulo pickle pode ajudar. Ele traduz quase qualquer tipo de objeto em uma string conveniente
para o armazenamento em um banco de dados, e ento traduz strings de volta em objetos.
pickle.dumps recebe um objeto como parmetro e retorna uma representao de string:
>>> import pickle
>>> t = [1, 2, 3]
>>> pickle.dumps(t)
b'\x80\x03]q\x00(K\x01K\x02K\x03e.'

O formato no bvio para leitores humanos; o objetivo que seja fcil para o pickle interpretar.
pickle.loads reconstitui o objeto:
>>> t1 = [1, 2, 3]
>>> s = pickle.dumps(t1)
>>> t2 = pickle.loads(s)
>>> t2
[1, 2, 3]

Embora o novo objeto tenha o mesmo valor que o antigo, no (em geral) o mesmo objeto:
>>> t1 == t2
True
>>> t1 is t2
False

Em outras palavras, usar o pickle.dumps e pickle.loads tem o mesmo efeito que copiar o
objeto.
Voc pode usar o pickle para guardar variveis que no so strings em um banco de dados. Na
verdade, esta combinao to comum que foi encapsulada em um mdulo chamado shelve.
149

14.8 - Pipes
A maior parte dos sistemas operacionais fornece uma interface de linha de comando, conhecida como
shell. Shells normalmente fornecem comandos para navegar nos sistemas de arquivos e executar
programas. Por exemplo, em Unix voc pode alterar diretrios com cd, exibir o contedo de um
diretrio com ls e abrir um navegador web digitando (por exemplo) firefox.

Qualquer programa que possa ser aberto no shell tambm pode ser aberto no Python usando um objeto
pipe, que representa um programa em execuo.
Por exemplo, o comando Unix ls -l normalmente exibe o contedo do diretrio atual no formato
longo. Voc pode abrir ls com os.popen[1]:
>>> cmd = 'ls -l'
>>> fp = os.popen(cmd)

O argumento uma string que contm um comando shell. O valor de retorno um objeto que se
comporta como um arquivo aberto. possvel ler a sada do processo ls uma linha por vez com
readline ou receber tudo de uma vez com read:
>>> res = fp.read()

Ao terminar, feche o pipe como se fosse um arquivo:


>>> stat = fp.close()
>>> print(stat)
None

O valor de retorno o status final do processo ls; None significa que terminou normalmente (sem
erros).
Por exemplo, a maior parte dos sistemas Unix oferece um comando chamado md5sum, que l o
contedo de um arquivo e calcula uma assinatura digital. Voc pode ler sobre o MD5 em
http://en.wikipedia.org/wiki/Md5. Este comando fornece uma forma eficiente de verificar se dois
arquivos tm o mesmo contedo. A probabilidade de dois contedos diferentes produzirem a mesma
assinatura digital muito pequena (isto , muito pouco provvel que acontea antes do colapso do
universo).
Voc pode usar um pipe para executar o md5sum do Python e receber o resultado:
>>> filename = 'book.tex'
>>> cmd = 'md5sum ' + filename
>>> fp = os.popen(cmd)
>>> res = fp.read()
>>> stat = fp.close()
>>> print(res)
1e0033f0ed0656636de0d75144ba32e0 book.tex
>>> print(stat)
150

None

14.9 - Escrevendo mdulos


Qualquer arquivo que contenha cdigo do Python pode ser importado como um mdulo. Por exemplo,
vamos supor que voc tenha um arquivo chamado wc.py com o seguinte cdigo:
def linecount(filename):
count = 0
for line in open(filename):
count += 1
return count

print(linecount('wc.py'))

Quando este programa executado, ele l a si mesmo e exibe o nmero de linhas no arquivo, que 7.
Voc tambm pode import-lo desta forma:
>>> import wc
7

Agora voc tem um objeto de mdulo wc:


>>> wc
<module 'wc' from 'wc.py'>

O objeto de mdulo fornece o linecount:


>>> wc.linecount('wc.py')
7

Ento assim que se escreve mdulos no Python.


O nico problema com este exemplo que quando voc importa o mdulo, ele executa o cdigo de
teste no final. Normalmente, quando se importa um mdulo, ele define novas funes, mas no as
executa.
Os programas que sero importados como mdulos muitas vezes usam a seguinte expresso:
if __name__ == '__main__':
print(linecount('wc.py'))

__name__ uma varivel integrada, estabelecida quando o programa inicia. Se o programa estiver
rodando como um script, __name__ tem o valor '__main__'; neste caso, o cdigo de teste
executado. Do contrrio, se o mdulo est sendo importado, o cdigo de teste ignorado.
Como exerccio, digite este exemplo em um arquivo chamado wc.py e execute-o como um script.
Ento execute o interpretador do Python e import wc. Qual o valor de __name__ quando o mdulo
est sendo importado?
151

Ateno: se voc importar um mdulo que j tenha sido importado, o Python no faz nada. Ele no rel
o arquivo, mesmo se tiver sido alterado.
Se quiser recarregar um mdulo, voc pode usar a funo integrada reload, mas isso pode causar
problemas, ento o mais seguro reiniciar o interpretador e importar o mdulo novamente.

14.10 - Depurao
Quando estiver lendo e escrevendo arquivos, voc pode ter problemas com whitespace. Esses erros
podem ser difceis para depurar, porque os espaos, tabulaes e quebras de linha normalmente so
invisveis:
>>> s = '1 2\t 3\n 4'
>>> print(s)
1 2 3
4

A funo integrada repr pode ajudar. Ela recebe qualquer objeto como argumento e retorna uma
representao em string do objeto. Para strings, representa caracteres de whitespace com sequncias de
barras invertidas:
>>> print(repr(s))
'1 2\t 3\n 4'

Isso pode ser til para a depurao.


Outro problema que voc pode ter que sistemas diferentes usam caracteres diferentes para indicar o
fim de uma linha. Alguns sistemas usam newline, representado por \n. Outros usam um caractere de
retorno, representado por \r. Alguns usam ambos. Se mover arquivos entre sistemas diferentes, essas
inconsistncias podem causar problemas.
Para a maior parte dos sistemas h aplicaes para converter de um formato a outro. Voc pode
encontr-los (e ler mais sobre o assunto) em http://en.wikipedia.org/wiki/Newline. Ou, claro, voc
pode escrever um por conta prpria.

14.11 - Glossrio
persistente
Relativo a um programa que roda indefinidamente e mantm pelo menos alguns dos seus dados
em armazenamento permanente.
operador de formatao
Um operador, %, que recebe uma string de formatao e uma tupla e gera uma string que inclui
os elementos da tupla formatada como especificado pela string de formatao.
152

string de formatao
String usada com o operador de formatao, que contm sequncias de formatao.
sequncia de formatao
Sequncia de caracteres em uma string de formatao, como %d, que especifica como um valor
deve ser formatado.
arquivo de texto
Sequncia de caracteres guardados em armazenamento permanente, como uma unidade de disco
rgido.
diretrio
Uma coleo de arquivos nomeada, tambm chamada de pasta.
caminho
String que identifica um arquivo.
caminho relativo
Caminho que inicia no diretrio atual.
caminho absoluto
Caminho que inicia no diretrio de posio mais alta (raiz) no sistema de arquivos.
capturar
Impedir uma exceo de encerrar um programa usando as instrues try e except.
banco de dados
Um arquivo cujo contedo organizado como um dicionrio, com chaves que correspondem a
valores.
objeto bytes
Objeto semelhante a uma string.
shell
Programa que permite aos usurios digitar comandos e execut-los para iniciar outros programas.
objeto pipe
Objeto que representa um programa em execuo, permitindo que um programa do Python
execute comandos e leia os resultados.

14.12 - Exerccios
Exerccio 14.1
Escreva uma funo chamada sed que receba como argumentos uma string-padro, uma string de
substituio e dois nomes de arquivo; ela deve ler o primeiro arquivo e escrever o contedo no segundo
arquivo (criando-o, se necessrio). Se a string-padro aparecer em algum lugar do arquivo, ela deve ser
substituda pela string de substituio.
Se ocorrer um erro durante a abertura, leitura, escrita ou fechamento dos arquivos, seu programa deve
capturar a exceo, exibir uma mensagem de erro e encerrar.
Soluo: http://thinkpython2.com/code/sed.py.
153

Exerccio 14.2
Se voc baixar minha soluo do Exerccio 12.2 em http://thinkpython2.com/code/anagram_sets.py,
ver que ela cria um dicionrio que mapeia uma string ordenada de letras lista de palavras que podem
ser soletradas com aquelas letras. Por exemplo, 'opst' mapeia lista ['opts', 'post',
'pots', 'spot', 'stop', 'tops'].

Escreva um mdulo que importe anagram_sets e fornea duas novas funes: store_anagrams
deve guardar o dicionrio de anagramas em uma prateleira (objeto criado pelo mdulo sheve);
read_anagrams deve procurar uma palavra e devolver uma lista dos seus anagramas.

Soluo: http://thinkpython2.com/code/anagram_db.py.

Exerccio 14.3
Em uma grande coleo de arquivos MP3 pode haver mais de uma cpia da mesma msica, guardada
em diretrios diferentes ou com nomes de arquivo diferentes. A meta deste exerccio procurar
duplicatas.
1. Escreva um programa que procure um diretrio e todos os seus subdiretrios, recursivamente, e
retorne uma lista de caminhos completos de todos os arquivos com um dado sufixo (como
.mp3). Dica: os.path fornece vrias funes teis para manipular nomes de caminhos e de
arquivos.
2. Para reconhecer duplicatas, voc pode usar md5sum para calcular uma soma de controle para
cada arquivo. Se dois arquivos tiverem a mesma soma de controle, provavelmente tm o mesmo
contedo.
3. Para conferir o resultado, voc pode usar o comando Unix diff.

Soluo: http://thinkpython2.com/code/find_duplicates.py.

Captulo 15: Classes e objetos


A esta altura voc j sabe como usar funes para organizar cdigo e tipos integrados para organizar
dados. O prximo passo aprender programao orientada a objeto, que usa tipos definidos pelos
programadores para organizar tanto o cdigo quanto os dados. A programao orientada a objeto um
tpico abrangente; ser preciso passar por alguns captulos para abordar o tema.
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/Point1.py; as
solues para os exerccios esto disponveis em http://thinkpython2.com/code/Point1_soln.py.
154

15.1 - Tipos definidos pelos programadores


J usamos muitos tipos integrados do Python; agora vamos definir um tipo prprio. Como exemplo,
criaremos um tipo chamado Point, que representa um ponto no espao bidimensional.

Na notao matemtica, os pontos muitas vezes so escritos entre parnteses, com uma vrgula
separando as coordenadas. Por exemplo, (0,0) representa a origem e (x, y) representa o ponto que est x
unidades direita e y unidades acima da origem.
H vrias formas para representar pontos no Python:
Podemos armazenar as coordenadas separadamente em duas variveis, x e y.

Podemos armazenar as coordenadas como elementos em uma lista ou tupla.

Podemos criar um tipo para representar pontos como objetos.

Criar um tipo mais complicado que outras opes, mas tem vantagens que logo ficaro evidentes.
Um tipo definido pelo programador tambm chamado de classe. Uma definio de classe pode ser
assim:
class Point:
"""Represents a point in 2-D space."""

O cabealho indica que a nova classe se chama Point. O corpo uma docstring que explica para que
a classe serve. Voc pode definir variveis e mtodos dentro de uma definio de classe, mas
voltaremos a isso depois.
Definir uma classe denominada Point cria um objeto de classe:
>>> Point
<class '__main__.Point'>

Como Point definido no nvel superior, seu nome completo __main__.Point.

O objeto de classe como uma fbrica para criar objetos. Para criar um Point, voc chama Point
como se fosse uma funo:
>>> blank = Point()
>>> blank
<__main__.Point object at 0xb7e9d3ac>

O valor de retorno uma referncia a um objeto Point, ao qual atribumos blank.

Criar um objeto chama-se instanciao, e o objeto uma instncia da classe.


Quando voc exibe uma instncia, o Python diz a que classe ela pertence e onde est armazenada na
memria (o prefixo o 0x significa que o nmero seguinte est em formato hexadecimal).
155

Cada objeto uma instncia de alguma classe, ento objeto e instncia so intercambiveis. Porm,
neste captulo uso instncia para indicar que estou falando sobre um tipo definido pelo programador.

15.2 - Atributos
Voc pode atribuir valores a uma instncia usando a notao de ponto:
>>> blank.x = 3.0
>>> blank.y = 4.0

Essa sintaxe semelhante usada para selecionar uma varivel de um mdulo, como math.pi ou
string.whitespace. Nesse caso, entretanto, estamos atribuindo valores a elementos nomeados de um
objeto. Esses elementos chamam-se atributos.
Em ingls, quando um substantivo, a palavra AT-trib-ute pronunciada com nfase na primeira
slaba, ao contrrio de a-TRIB-ute, que um verbo.
O diagrama seguinte mostra o resultado dessas atribuies. Um diagrama de estado que mostra um
objeto e seus atributos chama-se diagrama de objeto; veja a Figura 15.1.

Figura 15.1 Diagrama de um objeto Point.

A varivel blank refere-se a um objeto Point, que contm dois atributos. Cada atributo refere-se a um
nmero de ponto flutuante.
Voc pode ler o valor de um atributo usando a mesma sintaxe:
>>> blank.y
4.0
>>> x = blank.x
>>> x
3.0

A expresso blank.x significa V ao objeto a que blank se refere e pegue o valor de x. No


exemplo, atribumos este valor a uma varivel x. No h nenhum conflito entre a varivel x e o
atributo x.

Voc pode usar a notao de ponto como parte de qualquer expresso. Por exemplo:
>>> '(%g, %g)' % (blank.x, blank.y)
'(3.0, 4.0)'
>>> distance = math.sqrt(blank.x ** 2 + blank.y ** 2)
>>> distance
156

5.0

Voc pode passar uma instncia como argumento da forma habitual. Por exemplo:
def print_point(p):
print('(%g, %g)' % (p.x, p.y))

print_point toma um ponto como argumento e o exibe em notao matemtica. Para invoc-lo,
voc pode passar blank como argumento:
>>> print_point(blank)
(3.0, 4.0)

Dentro da funo, p um alias para blank, ento, se a funo altera p, blank tambm muda.

Como exerccio, escreva uma funo chamada distance_between_points, que toma dois
pontos como argumentos e retorna a distncia entre eles.

15.3 - Retngulos
s vezes, bvio quais deveriam ser os atributos de um objeto, mas outras preciso decidir entre as
possibilidades. Por exemplo, vamos supor que voc esteja criando uma classe para representar
retngulos. Que atributos usaria para especificar a posio e o tamanho de um retngulo? Voc pode
ignorar ngulo; para manter as coisas simples, suponha que o retngulo seja vertical ou horizontal.
H duas possibilidades, no mnimo:
Voc pode especificar um canto do retngulo (ou o centro), a largura e a altura.

Voc pode especificar dois cantos opostos.

Nesse ponto difcil dizer qual opo melhor, ento implementaremos a primeira, como exemplo.
Aqui est a definio de classe:
class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner.
"""

A docstring lista os atributos: width e height so nmeros; corner um objeto Point que especifica o
canto inferior esquerdo.
Para representar um retngulo, voc tem que instanciar um objeto Rectangle e atribuir valores aos
atributos:
box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
157

box.corner.x = 0.0
box.corner.y = 0.0

A expresso box.corner.x significa V ao objeto ao qual box se refere e pegue o atributo


denominado corner; ento v a este objeto e pegue o atributo denominado x.

A Figura 15.2 mostra o estado deste objeto. Um objeto que um atributo de outro objeto integrado.
A Figura 10.1 mostra o diagrama de estado para cheeses, numbers e empty.

Figura 15.2 Diagrama de um objeto Rectangle.

15.4 - Instncias como valores de retorno


As funes podem retornar instncias. Por exemplo, find_center recebe um Rectangle como
argumento e devolve um Point, que contm as coordenadas do centro do retngulo:
def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p

Aqui est um exemplo que passa box como um argumento para find_center e atribui o ponto
resultante varivel center:
>>> center = find_center(box)
>>> print_point(center)
(50, 100)

15.5 - Objetos so mutveis


Voc pode alterar o estado de um objeto fazendo uma atribuio a um dos seus atributos. Por exemplo,
para mudar o tamanho de um retngulo sem mudar sua posio, voc pode alterar os valores de width e
height:
box.width = box.width + 50
box.height = box.height + 100
158

Voc tambm pode escrever funes que alteram objetos. Por exemplo, grow_rectangle recebe um
objeto Rectangle e dois nmeros, dwidth e dheight, e adiciona os nmeros largura e altura do
retngulo:
def grow_rectangle(rect, dwidth, dheight):
rect.width += dwidth
rect.height += dheight

Eis um exemplo que demonstra o efeito:


>>> box.width, box.height
(150.0, 300.0)
>>> grow_rectangle(box, 50, 100)
>>> box.width, box.height
(200.0, 400.0)

Dentro da funo, rect um alias de box, ento quando a funo altera rect, box aponta para o
objeto alterado.
Como exerccio, escreva uma funo chamada move_rectangle que toma um Rectangle e dois
nmeros chamados dx e dy. Ela deve alterar a posio do retngulo, adicionando dx coordenada x de
corner e adicionando dy coordenada y de corner.

15.6 - Cpia
Alias podem tornar um programa difcil de ler porque as alteraes em um lugar podem ter efeitos
inesperados em outro lugar. difcil monitorar todas as variveis que podem referir-se a um dado
objeto.
Em vez de usar alias, copiar o objeto pode ser uma alternativa. O mdulo copy contm uma funo
chamada copy que pode duplicar qualquer objeto:
>>> p1 = Point()
>>> p1.x = 3.0
>>> p1.y = 4.0
>>> import copy
>>> p2 = copy.copy(p1)

p1 e p2 contm os mesmos dados, mas no so o mesmo Point:


>>> print_point(p1)
(3, 4)
>>> print_point(p2)
(3, 4)
>>> p1 is p2
False
>>> p1 == p2
False
159

O operador is indica que p1 e p2 no so o mesmo objeto, que o que esperamos. Porm, voc
poderia ter esperado que == fosse apresentado como True, porque esses pontos contm os mesmos
dados. Nesse caso, pode ficar desapontado ao saber que, para instncias, o comportamento padro do
operador == o mesmo que o do operador is; ele verifica a identidade dos objetos, no a sua
equivalncia. Isso acontece porque, para tipos definidos pelo programador, o Python no sabe o que
deve ser considerado equivalente. Pelo menos, ainda no.
Se voc usar copy.copy para duplicar um retngulo, descobrir que ele copia o objeto Rectangle,
mas no o Point embutido nele:
>>> box2 = copy.copy(box)
>>> box2 is box
False
>>> box2.corner is box.corner
True

A Figura 15.3 mostra como fica o diagrama de objeto. Esta operao chama-se cpia superficial porque
copia o objeto e qualquer referncia que contenha, mas no os objetos integrados.

Figura 15.3 Diagrama: dois objetos Rectangle compartilhando o mesmo Point.

Para a maior parte das aplicaes, no isso que voc quer. Nesse exemplo, invocar
grow_rectangle em um dos Rectangles no afetaria o outro, mas invocar move_rectangle em
qualquer um deles afetaria a ambos! Esse comportamento confuso e propenso a erros.
Felizmente, o mdulo copy oferece um mtodo chamado deepcopy que copia no s o objeto, mas
tambm os objetos aos quais ele se refere, e os objetos aos quais estes se referem, e assim por diante.
Voc no se surpreender ao descobrir que esta operao se chama cpia profunda.
>>> box3 = copy.deepcopy(box)
>>> box3 is box
False
>>> box3.corner is box.corner
False
box3 e box so objetos completamente separados.

Como exerccio, escreva uma verso de move_rectangle que cria e retorne um novo retngulo em
vez de alterar o antigo.
160

15.7 - Depurao
Ao comear a trabalhar com objetos, provavelmente voc encontrar algumas novas excees. Se
tentar acessar um atributo que no existe, recebe um AttributeError:
>>> p = Point()
>>> p.x = 3
>>> p.y = 4
>>> p.z
AttributeError: Point instance has no attribute 'z'

Se no estiver certo sobre o tipo que um objeto , pode perguntar:


>>> type(p)
<class '__main__.Point'>

Voc tambm pode usar isinstance para verificar se um objeto uma instncia de uma classe:
>>> isinstance(p, Point)
True

Caso no tenha certeza se um objeto tem determinado atributo, voc pode usar a funo integrada
hasattr:
>>> hasattr(p, 'x')
True
>>> hasattr(p, 'z')
False

O primeiro argumento pode ser qualquer objeto; o segundo argumento uma string com o nome do
atributo.
Voc tambm pode usar uma instruo try para ver se o objeto tem os atributos de que precisa:
try:
x = p.x
except AttributeError:
x = 0

Essa abordagem pode facilitar a escrita de funes que atuam com tipos diferentes; voc ver mais
informaes sobre isso em Polimorfismo, na pgina 248.

15.8 - Glossrio
classe
Tipo definido pelo programador. Uma definio de classe cria um objeto de classe.
objeto de classe
Objeto que contm a informao sobre um tipo definido pelo programador. O objeto de classe
pode ser usado para criar instncias do tipo.
instncia
161

Objeto que pertence a uma classe.


instanciar
Criar um objeto.
atributo
Um dos valores denominados associados a um objeto.
objeto integrado
Objeto que armazenado como um atributo de outro objeto.
cpia superficial
Copiar o contedo de um objeto, inclusive qualquer referncia a objetos integrados;
implementada pela funo copy no mdulo copy.
cpia profunda
Copiar o contedo de um objeto, bem como qualquer objeto integrado, e qualquer objeto
integrado a estes, e assim por diante; implementado pela funo deepcopy no mdulo copy.
diagrama de objeto
Diagrama que mostra objetos, seus atributos e os valores dos atributos.

15.9 - Exerccios
Exerccio 15.1
1. Escreva uma definio para uma classe denominada Circle, com os atributos center e radius,
onde center um objeto Point e radius um nmero.

2. Instancie um objeto Circle, que represente um crculo com o centro em 150, 100 e raio 75.

3. Escreva uma funo denominada point_in_circle, que tome um Circle e um Point e


retorne True, se o ponto estiver dentro ou no limite do crculo.

4. Escreva uma funo chamada rect_in_circle, que tome um Circle e um Rectangle e


retorne True, se o retngulo estiver totalmente dentro ou no limite do crculo.

5. Escreva uma funo denominada rect_circle_overlap, que tome um Circle e um


Rectangle e retorne True, se algum dos cantos do retngulo cair dentro do crculo. Ou, em uma
verso mais desafiadora, retorne True se alguma parte do retngulo cair dentro do crculo.

Soluo: http://thinkpython2.com/code/Circle.py.

Exerccio 15.2
1. Escreva uma funo chamada draw_rect que receba um objeto Turtle e um Rectangle
e use o Turtle para desenhar o retngulo. Veja no Captulo 4 os exemplos de uso de objetos
Turtle.

2. Escreva uma funo chamada draw_circle, que tome um Turtle e um Circle e desenhe o
crculo.
162

Captulo 16: Classes e funes


Agora que sabemos como criar tipos, o prximo passo deve ser escrever funes que recebam objetos
definidos pelo programador como parmetros e os retornem como resultados. Neste captulo tambm
vou apresentar o estilo funcional de programao e dois novos planos de desenvolvimento de
programas.
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/Time1.py. As
solues para os exerccios esto em http://thinkpython2.com/code/Time1_soln.py.

16.1 - Time
Para ter mais um exemplo de tipo definido pelo programador, criaremos uma classe chamada Time
(hora), que registra um horrio no dia. A definio da classe assim:
class Time:
"""Represents the time of day.
attributes: hour, minute, second
"""

Podemos criar um objeto Time e ter atributos para horas, minutos e segundos:
time = Time()
time.hour = 11
time.minute = 59
time.second = 30

O diagrama de estado do objeto Time est na Figura 16.1.

Figura 16.1 Diagrama de um objeto Time.

Como exerccio, escreva uma funo chamada print_time, que receba um objeto Time e o exiba na
forma hour:minute:second. Dica: a sequncia de formatao '%.2d' exibe um nmero inteiro com,
pelo menos, dois dgitos, incluindo um zero esquerda, se for necessrio.
Escreva uma funo booleana chamada is_after, que receba dois objetos Time, t1 e t2, e devolva
True se t1 for cronologicamente depois de t2 e False se no for. Desafio: no use uma instruo
if.
163

16.2 - Funes puras


Nas prximas sees, vamos escrever duas funes que adicionam valores de tempo. Elas demonstram
dois tipos de funes: funes puras e modificadores. Tambm demonstram um plano de
desenvolvimento que chamarei de prottipo e correo, que uma forma de atacar um problema
complexo comeando com um prottipo simples e lidando com as complicaes de forma incremental.
Aqui est um prottipo simples de add_time:
def add_time(t1, t2):
sum = Time()
sum.hour = t1.hour + t2.hour
sum.minute = t1.minute + t2.minute
sum.second = t1.second + t2.second
return sum

A funo cria um novo objeto Time, inicializa seus atributos e retorna uma referncia ao novo objeto.
A funo pura chamada assim porque no altera nenhum dos objetos passados a ela como
argumentos; alm disso, ela no tem efeitos, como exibir um valor ou receber entradas de usurio,
apenas retorna um valor.
Para testar esta funo, criarei objetos Time: start, que contm o tempo de incio de um filme, como
Monty Python e o clice sagrado, e duration, que contm o tempo de execuo do filme, que de 1
hora e 35 minutos.
add_time calcula quando o filme acaba:
>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 0
>>> duration = Time()
>>> duration.hour = 1
>>> duration.minute = 35
>>> duration.second = 0
>>> done = add_time(start, duration)
>>> print_time(done)
10:80:00

O resultado, 10:80:00, pode no ser o que voc esperava. O problema que esta funo no trata casos
onde o nmero de segundos ou minutos maior que 60. Quando isso acontece, precisamos transportar
os segundos extras coluna dos minutos ou os minutos extras coluna das horas.
Aqui est uma verso melhorada:
def add_time(t1, t2):
sum = Time()
sum.hour = t1.hour + t2.hour
sum.minute = t1.minute + t2.minute
sum.second = t1.second + t2.second
164

if sum.second >= 60:


sum.second -= 60
sum.minute += 1
if sum.minute >= 60:
sum.minute -= 60
sum.hour += 1
return sum

Embora esta funo esteja correta, um pouco extensa. Veremos uma alternativa menor mais adiante.

16.3 - Modificadores
s vezes til uma funo alterar os objetos que recebe como parmetros. Nesse caso, as mudanas
so visveis a quem chama a funo. As funes que fazem isso chamam-se modificadores.
increment, que acrescenta um dado nmero de segundos a um objeto Time, pode ser escrita
naturalmente como um modificador. Aqui est um primeiro esboo:
def increment(time, seconds):
time.second += seconds
if time.second >= 60:
time.second -= 60
time.minute += 1
if time.minute >= 60:
time.minute -= 60
time.hour += 1

A primeira linha executa a operao bsica; o resto lida com os casos especiais que vimos antes.
Esta funo est correta? O que acontece se second for muito mais que 60?

Neste caso no basta transportar uma vez, temos que continuar fazendo isso at que time.second seja
menos de 60. Uma soluo substituir a instruo if pela instruo while. Isso tornaria a funo
correta, mas no muito eficiente. Como exerccio, escreva uma verso correta de increment que no
contenha loops.
O que se faz com modificadores tambm pode ser feito com funes puras. Na verdade, algumas
linguagens de programao s permitem funes puras. H evidncias de que os programas que usam
funes puras so mais rpidos para serem desenvolvidos e menos propensos a erros que programas
que usam modificadores. No entanto, modificadores so convenientes de vez em quando, e os
programas funcionais tendem a ser menos eficientes.
De forma geral, recomendo que voc escreva funes puras sempre que achar razovel e recorra a
modificadores s se houver alguma vantagem clara. Esta abordagem pode ser chamada de
programao funcional.
Como exerccio, escreva uma verso pura de increment que cria e retorna um objeto Time em vez de
alterar o parmetro.
165

16.4 - Prototipao versus planejamento


O plano de desenvolvimento que estou demonstrando chama-se prottipo e correo. Para cada
funo, escrevi um prottipo que executa o clculo bsico e ento testa a funo, corrigindo erros no
decorrer do caminho.
Esta abordagem pode ser eficaz, especialmente se voc ainda no tem uma compreenso profunda do
problema. Porm, as correes incrementais podem gerar cdigo que se complica desnecessariamente
(pois trata de muitos casos especiais) e pouco confiveis (j que difcil saber se todos os erros foram
encontrados).
Uma alternativa o desenvolvimento planejado, no qual a compreenso de alto nvel do problema pode
facilitar muito a programao. Neste caso, descobre-se que um objeto Time , na verdade, um nmero
de trs dgitos na base 60 (veja http://en.wikipedia.org/wiki/Sexagesimal)! O atributo second a
coluna de unidades, o atributo minute a coluna dos 60, e o atributo hour a coluna do 3.600.
Quando escrevemos add_time e increment, estvamos na verdade fazendo adies na base 60, e
por isso transportvamos os resultados de uma coluna seguinte.
Essa observao sugere outra abordagem para o problema inteiro podemos converter objetos Time
em nmeros inteiros e aproveitar o fato de que o computador sabe trabalhar com aritmtica de nmeros
inteiros.
Aqui est uma funo que converte objetos Time em nmeros inteiros:
def time_to_int(time):
minutes = time.hour * 60 + time.minute
seconds = minutes * 60 + time.second
return seconds

E aqui est uma funo que converte um nmero inteiro em um Time (lembre-se de que divmod
divide o primeiro argumento pelo segundo e devolve o quociente e o resto como uma tupla):
def int_to_time(seconds):
time = Time()
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time

Voc pode ter que pensar um pouco e fazer alguns testes, para se convencer de que essas funes esto
corretas. Um modo de test-las ver se time_to_int(int_to_time(x)) == x para muitos
valores de x. Este um exemplo de uma verificao de consistncia.

Uma vez que esteja convencido de que esto corretas, voc pode us-las para reescrever add_time:
def add_time(t1, t2):
seconds = time_to_int(t1) + time_to_int(t2)
return int_to_time(seconds)
166

Esta verso mais curta que a original, e mais fcil de verificar. Como exerccio, reescreva
increment usando time_to_int e int_to_time.

Em algumas situaes, converter da base 60 para a base 10 e de volta mais difcil que apenas lidar
com as horas. A converso de base mais abstrata; nossa intuio para lidar com valores temporais
melhor.
No entanto, se tivermos discernimento para lidar com horas como nmeros de base 60 e investirmos
esforo em escrever as funes de converso (time_to_int e int_to_time), chegamos a um
programa que mais curto, mais fcil de ler e depurar, e mais confivel.
Tambm mais fcil acrescentar recursos depois. Por exemplo, imagine subtrair dois objetos Time
para encontrar a durao entre eles. Uma abordagem ingnua seria implementar a subtrao com
transporte. Porm, usar funes de converso seria mais fcil e, provavelmente, mais correto.
Ironicamente, tornar um problema mais difcil (ou mais geral) facilita (porque h menos casos especiais
e menos oportunidades de erro).

16.5 - Depurao
Um objeto Time bem formado se os valores de minute e second estiverem entre 0 e 60
(incluindo 0, mas no 60) e se hour for positivo. hour e minute devem ser valores inteiros, mas
podemos permitir que second tenha uma parte fracionria.

Requisitos como esses chamam-se invariveis porque sempre devem ser verdadeiros. Para dizer de
outra forma, se no forem verdadeiros, algo deu errado.
Escrever cdigo para verificar requisitos invariveis pode ajudar a descobrir erros e encontrar suas
causas. Por exemplo, voc pode ter uma funo como valid_time, que receba um objeto Time e retorne
False se ele violar um requisito invarivel:
def valid_time(time):
if time.hour < 0 or time.minute < 0 or time.second < 0:
return False
if time.minute >= 60 or time.second >= 60:
return False
return True

No incio de cada funo voc pode verificar os argumentos para ter certeza de que so vlidos:
def add_time(t1, t2):
if not valid_time(t1) or not valid_time(t2):
raise ValueError('invalid Time object in add_time')

seconds = time_to_int(t1) + time_to_int(t2)


return int_to_time(seconds)
167

Ou voc pode usar uma instruo assert, que verifica determinado requisito invarivel e cria uma
exceo se ela falhar:
def add_time(t1, t2):
assert valid_time(t1) and valid_time(t2)
seconds = time_to_int(t1) + time_to_int(t2)
return int_to_time(seconds)

Instrues assert so teis porque distinguem o cdigo que lida com condies normais do cdigo que
verifica erros.

16.6 - Glossrio
prottipo e correo
Plano de desenvolvimento no qual a escrita do programa parte de um esboo inicial, e depois
segue ao teste e correo de erros, conforme sejam encontrados.
desenvolvimento planejado
Plano de desenvolvimento que implica uma compreenso de alto nvel do problema e mais
planejamento que desenvolvimento incremental ou desenvolvimento prototipado.
funo pura
Funo que no altera nenhum dos objetos que recebe como argumento. A maior parte das
funes puras gera resultado.
modificador
Funo que modifica um ou vrios dos objetos que recebe como argumento. A maior parte dos
modificadores so nulos; isto , retornam None.
programao funcional
Estilo de projeto de programa no qual a maioria das funes so puras.
invarivel
Condio que sempre deve ser verdadeira durante a execuo de um programa.
instruo assert
Instruo que verifica uma condio e levanta uma exceo se esta falhar.

16.7 - Exerccios
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/Time1.py; as
solues para os exerccios esto disponveis em http://thinkpython2.com/code/Time1_soln.py.

Exerccio 16.1
Escreva uma funo chamada mul_time que receba um objeto Time e um nmero e retorne um novo
objeto Time que contenha o produto do Time original e do nmero.

Ento use mul_time para escrever uma funo que receba um objeto Time representando o tempo
at o fim de uma corrida e um nmero que represente a distncia, e retorne um objeto Time com o
passo mdio (tempo por milha).
168

Exerccio 16.2
O mdulo datetime fornece objetos time que so semelhantes aos objetos Time deste captulo,
mas ele oferece um grande conjunto de mtodos e operadores. Leia a documentao em
http://docs.python.org/3/library/datetime.html.
1. Use o mdulo datetime para escrever um programa que receba a data atual e exiba o dia da
semana.
2. Escreva um programa que receba um aniversrio como entrada e exiba a idade do usurio e o
nmero de dias, horas, minutos e segundos at o seu prximo aniversrio.
3. Para duas pessoas nascidas em dias diferentes, h um dia em que a idade de uma equivale a
duas vezes a da outra. Este o Dia Duplo delas. Escreva um programa que receba dois
aniversrios e calcule o Dia Duplo dos aniversariantes.
4. Para um desafio um pouco maior, escreva a verso mais geral que calcule o dia em que uma
pessoa N vezes mais velha que a outra.

Captulo 17: Classes e mtodos


Embora estejamos usando alguns recursos orientadas a objeto do Python, os programas dos dois
ltimos captulos no so realmente orientados a objeto, porque no representam as relaes entre os
tipos definidos pelo programador e as funes que os produzem. O prximo passo transformar essas
funes em mtodos que tornem as relaes claras.
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/Time2.py e
as solues para os exerccios esto em http://thinkpython2.com/code/Point2_soln.py.

17.1 - Recursos orientados a objeto


Python uma linguagem de programao orientada a objeto, ou seja, ela oferece recursos de
programao orientada a objeto que tem a seguintes caractersticas:
Os programas incluem definies de classes e mtodos.

A maior parte dos clculos expressa em termos de operaes em objetos.

Os objetos muitas vezes representam coisas no mundo real, e os mtodos muitas vezes
correspondem s formas em que as coisas no mundo real interagem.
Por exemplo, a classe Time definida no Captulo 16 corresponde forma em que as pessoas registram a
hora do dia, e as funes que definimos correspondem aos tipos de coisas que as pessoas fazem com os
horrios. De forma similar, as classes Point e Rectangle no Captulo 15 correspondem aos conceitos
matemticos de ponto e retngulo.
169

Por enquanto, no aproveitamos os recursos que o Python oferece para programao orientada a objeto.
Esses recursos no so estritamente necessrios; a maioria deles oferece uma sintaxe alternativa para
coisas que j fizemos. No entanto, em muitos casos, a alternativa mais concisa e representa de forma
mais exata a estrutura do programa.
Por exemplo, em Time1.py no h nenhuma conexo bvia entre a definio de classe e as definies
de funo que seguem. Com um pouco de ateno, evidente que cada funo recebe pelo menos um
objeto Time como argumento.
Essa observao a motivao para usar mtodos; um mtodo uma funo associada a determinada
classe. Vimos mtodos de string, listas, dicionrios e tuplas. Neste captulo definiremos mtodos para
tipos definidos pelo programador.
Mtodos so semanticamente o mesmo que funes, mas h duas diferenas sintticas:
Os mtodos so definidos dentro de uma definio de classe para tornar clara a relao entre a
classe e o mtodo.
A sintaxe para invocar um mtodo diferente da sintaxe para chamar uma funo.

Nas prximas sees tomaremos as funes dos dois captulos anteriores e as transformaremos em
mtodos. Essa transformao puramente mecnica; voc pode faz-la seguindo uma srie de passos.
Se estiver vontade para fazer a converso entre uma forma e outra, sempre poder escolher a melhor
forma para contemplar os seus objetivos.

17.2 - Exibio de objetos


No Captulo 16 definimos uma classe chamada Time em Time, na pgina 231, e voc escreveu uma
funo denominada print_time:
class Time:
"Represents the time of day.
def print_time(time):
print(%.2d:%.2d:%.2d % (time.hour, time.minute, time.second))
Para chamar esta funo, voc precisa passar um objeto Time como argumento:
>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 00
170

>>> print_time(start)
09:45:00
Para fazer de print_time um mtodo, tudo o que precisamos fazer mover a definio da funo para
dentro da definio da classe. Note a alterao na endentao:
class Time:
def print_time(time):
print(%.2d:%.2d:%.2d % (time.hour, time.minute, time.second))
Agora h duas formas de chamar print_time. A primeira forma (e menos comum) usar a sintaxe de
funo:
>>> Time.print_time(start)
09:45:00
Nesse uso da notao de ponto, Time o nome da classe, e print_time o nome do mtodo. start
passado como um parmetro.
A segunda forma (e mais concisa) usar a sintaxe de mtodo:
>>> start.print_time()
09:45:00
Nesse uso da notao de ponto, print_time o nome do mtodo (novamente), e start o objeto no qual
o mtodo invocado, que se chama de sujeito. Assim como em uma sentena, onde o sujeito o foco
da escrita, o sujeito de uma invocao de mtodo o foco do mtodo.
Dentro do mtodo, o sujeito atribudo ao primeiro parmetro, portanto, neste caso, start atribudo a
time.
Por conveno, o primeiro parmetro de um mtodo chama-se self, ento seria mais comum escrever
print_time desta forma:
class Time:
def print_time(self):
print(%.2d:%.2d:%.2d % (self.hour, self.minute, self.second))
A razo dessa conveno uma metfora implcita:
A sintaxe de uma chamada de funo, print_time(start), sugere que a funo o agente ativo.
Ela diz algo como: Ei, print_time! Aqui est um objeto para voc exibir.
171

Na programao orientada a objeto, os objetos so os agentes ativos. Uma invocao de mtodo


como start.print_time() diz: Ei, start! Por favor, exiba-se.
Essa mudana de perspectiva pode ser mais polida, mas no bvio que seja til. Nos exemplos que
vimos at agora, pode no ser. Porm, s vezes, deslocar a responsabilidade das funes para os objetos
permite escrever funes (ou mtodos) mais versteis e facilita a manuteno e reutilizao do cdigo.
Como exerccio, reescreva time_to_int (de Prototipao versus planejamento, na pgina 234) como
um mtodo. Voc pode ficar tentado a reescrever int_to_time como um mtodo tambm, mas isso no
faz muito sentido porque no haveria nenhum objeto sobre o qual invoc-lo.

17.3 - Outro exemplo


Aqui est uma verso de increment (de Modificadores, na pgina 233) reescrita como mtodo:
# dentro da classe Time:
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
Essa verso assume que time_to_int seja escrita como mtodo. Alm disso, observe que uma funo
pura, no um modificador.
assim que eu invocaria increment:
>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
O sujeito, start, atribudo ao primeiro parmetro, self. O argumento, 1337, atribudo ao segundo
parmetro, seconds.
Esse mecanismo pode ser confuso, especialmente se voc fizer um erro. Por exemplo, se invocar
increment com dois argumentos, recebe:
>>> end = start.increment(1337, 460)
TypeError: increment() takes 2 positional arguments but 3 were given
A mensagem de erro inicialmente confusa, porque h s dois argumentos entre parnteses. No
entanto, o sujeito tambm considerado um argumento, ento, somando tudo, so trs.
172

A propsito, um argumento posicional o que no tem um nome de parmetro; isto , no um


argumento de palavra-chave. Nesta chamada da funo:
sketch(parrot, cage, dead=True)
parrot e cage so posicionais, e dead um argumento de palavra-chave.

17.4 - Um exemplo mais complicado


Reescrever is_after (de Time, na pgina 231) ligeiramente mais complicado, porque ela recebe dois
objetos Time como parmetros. Nesse caso, a conveno denominar o primeiro parmetro self e o
segundo parmetro other:
# dentro da classe Time:
def is_after(self, other):
return self.time_to_int() > other.time_to_int()
Para usar este mtodo, voc deve invoc-lo para um objeto e passar outro como argumento:
>>> end.is_after(start)
True
Uma vantagem desta sintaxe que quase literal em ingls: o fim depois da partida?.

17.5 - Mtodo init


O mtodo init (abreviao da palavra em ingls para inicializao) um mtodo especial, invocado
quando um objeto instanciado. Seu nome completo __init__ (dois caracteres de sublinhado,
seguidos de init, e mais dois sublinhados). Um mtodo init da classe Time pode ser algo assim:
# dentro da classe Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
comum que os parmetros de __init__ tenham os mesmos nomes que os atributos. A instruo
self.hour = hour
guarda o valor do parmetro hour como um atributo de self.
Os parmetros so opcionais, ento, se voc chamar Time sem argumentos, recebe os valores-padro:
173

>>> time = Time()


>>> time.print_time()
00:00:00
Se incluir um argumento, ele ignora hour.
>>> time = Time (9)
>>> time.print_time()
09:00:00
Se fornecer dois argumentos, hour e minute sero ignorados:
>>> time = Time(9, 45)
>>> time.print_time()
09:45:00
E se voc fornecer trs argumentos, os trs valores-padro sero ignorados.
Como exerccio, escreva um mtodo init da classe Point que receba x e y como parmetros opcionais e
os relacione aos atributos correspondentes.

17.6 - Mtodo __str__


__str__ um mtodo especial, como __init__, usado para retornar uma representao de string de um
objeto.
Por exemplo, aqui est um mtodo str para objetos Time:
# dentro da classe Time:
def __str__(self):
return %.2d:%.2d:%.2d % (self.hour, self.minute, self.second)
Ao exibir um objeto com print, o Python invoca o mtodo str:
>>> time = Time(9, 45)
>>> print(time)
09:45:00
Quando escrevo uma nova classe, quase sempre comeo escrevendo __init__, o que facilita a
instanciao de objetos, e __str__, que til para a depurao.
Como exerccio, escreva um mtodo str da classe Point. Crie um objeto Point e exiba-o.
174

17.7 - Sobrecarga de operadores


Ao definir outros mtodos especiais, voc pode especificar o comportamento de operadores nos tipos
definidos pelo programador. Por exemplo, se voc definir um mtodo chamado __add__ para a classe
Time de Time, pode usar o operador + em objetos Time.
A definio pode ser assim:
# dentro da classe Time:
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
Voc pode us-lo assim:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
Ao aplicar o operador + a objetos Time, o Python invoca __add__. Ao exibir o resultado, o Python
invoca __str__. Ou seja, h muita coisa acontecendo nos bastidores!
Alterar o comportamento de um operador para que funcione com tipos definidos pelo programador
chama-se sobrecarga de operadores. Para cada operador no Python h um mtodo especial
correspondente, como __add__. Para obter mais informaes, veja
http://docs.python.org/3/reference/datamodel.html#specialnames.
Como exerccio, escreva um mtodo add para a classe Point.

17.8 - Despacho por tipo


Na seo anterior, acrescentamos dois objetos Time, mas voc tambm pode querer acrescentar um
nmero inteiro a um objeto Time. A seguir, veja uma verso de __add__, que verifica o tipo de other e
invoca add_time ou increment:
# dentro da classe Time:
def __add__(self, other):
if isinstance(other, Time):
return self.add_time(other)
175

else:
return self.increment(other)
def add_time(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
A funo construda isinstance recebe um valor e um objeto de classe e retorna True se o valor for uma
instncia da classe.
Se other for um objeto Time, __add__ invoca add_time. Do contrrio, assume que o parmetro seja um
nmero e invoca increment. Essa operao chama-se despacho por tipo porque despacha a operao a
mtodos diferentes, baseados no tipo dos argumentos.
Veja exemplos que usam o operador + com tipos diferentes:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
10:07:17
Infelizmente, esta implementao da adio no comutativa. Se o nmero inteiro for o primeiro
operando, voc recebe
>>> print(1337 + start)
TypeError: unsupported operand type(s) for +: int and instance
O problema que, em vez de pedir ao objeto Time que adicione um nmero inteiro, o Python est
pedindo que um nmero inteiro adicione um objeto Time, e ele no sabe como fazer isso. Entretanto, h
uma soluo inteligente para este problema: o mtodo especial __radd__, que significa adio
direita. Esse mtodo invocado quando um objeto Time aparece no lado direito do operador +. Aqui
est a definio:
# dentro da classe Time:
176

def __radd__(self, other):


return self.__add__(other)
E assim que ele usado:
>>> print(1337 + start)
10:07:17
Como exerccio, escreva um mtodo add para Points que funcione com um objeto Point ou com uma
tupla:
Se o segundo operando for um Point, o mtodo deve retornar um novo Point cuja coordenada x
a soma das coordenadas x dos operandos, e o mesmo se aplica s coordenadas de y.
Se o segundo operando for uma tupla, o mtodo deve adicionar o primeiro elemento da tupla
coordenada de x e o segundo elemento coordenada de y, retornando um novo Point com o
resultado.

17.9 - Polimorfismo
O despacho por tipo til, mas (felizmente) nem sempre necessrio. Muitas vezes, voc pode evit-lo
escrevendo funes que funcionem corretamente para argumentos de tipos diferentes.
Muitas das funes que escrevemos para strings tambm funcionam para outros tipos de sequncia. Por
exemplo, em Um dicionrio como uma coleo de contadores, na pgina 163, usamos histogram para
contar o nmero de vezes que cada letra aparece numa palavra:
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c]+1
return d
Essa funo tambm funciona com listas, tuplas e at dicionrios, desde que os elementos de s sejam
hashable, ento eles podem ser usados como chaves em d:
>>> t = [spam, egg, spam, spam, bacon, spam]
177

>>> histogram(t)
{bacon: 1, egg: 1, spam: 4}
As funes que funcionam com vrios tipos chamam-se polimrficas. O polimorfismo pode facilitar a
reutilizao do cdigo. Por exemplo, a funo integrada sum, que adiciona os elementos de uma
sequncia, funciona s se os elementos da sequncia forem compatveis com adio.
Como os objetos Time oferecem o mtodo add, eles funcionam com sum:
>>> t1 = Time(7, 43)
>>> t2 = Time(7, 41)
>>> t3 = Time(7, 37)
>>> total = sum([t1, t2, t3])
>>> print(total)
23:01:00
Em geral, se todas as operaes dentro de uma funo forem compatveis com um dado tipo, no
haver problemas.
O melhor tipo de polimorfismo o no intencional, quando voc descobre que uma funo que j
escreveu pode ser aplicada a um tipo para o qual ela no tinha planejada.

17.10 - Interface e implementao


Uma das metas do projeto orientado a objeto facilitar a manuteno do programa, para que voc
possa mant-lo funcionando quando outras partes do sistema forem alteradas, e tambm poder alterar o
programa para satisfazer novas condies.
Um princpio de projeto que ajuda a atingir essa meta manter as interfaces separadas das
implementaes. Para objetos, isso quer dizer que os mtodos que uma classe oferece no devem
depender de como os atributos so representados.
Por exemplo, neste captulo desenvolvemos uma classe que representa uma hora do dia. Os mtodos
fornecidos por esta classe incluem time_to_int, is_after e add_time.
Podemos implementar esses mtodos de vrias formas. Os detalhes da implementao dependem de
como representamos as horas. Neste captulo, os atributos de um objeto Time so hour, minute e
second.
Como alternativa, podemos substituir esses atributos por um nmero inteiro nico que represente o
nmero de segundos desde a meia-noite. Essa implementao faria com que alguns mtodos, como
is_after, fossem mais fceis de escrever, mas dificultaria o uso de outros mtodos.
178

Pode acontecer que, depois de implementar uma nova classe, voc descubra uma implementao
melhor. Se outras partes do programa estiverem usando a sua classe, mudar a interface pode ser
trabalhoso e induzir a erros.
No entanto, se projetou a interface cuidadosamente, pode alterar a implementao sem mudar a
interface, e no ser preciso mudar outras partes do programa.

17.11 - Depurao
legal acrescentar atributos a objetos em qualquer ponto da execuo de um programa, mas se voc
tiver objetos do mesmo tipo que no tm os mesmos atributos, fcil cometer erros. uma boa ideia
inicializar todos os atributos de um objeto no mtodo init.
Caso no tenha certeza se um objeto tem um determinado atributo, voc pode usar a funo integrada
hasattr (ver Depurao, na pgina 236).
Outra forma de acessar atributos com a funo integrada vars, que recebe um objeto e retorna um
dicionrio que mapeia os nomes dos atributos (como strings) aos seus valores:
>>> p = Point(3, 4)
>>> vars(p)
{y: 4, x: 3}
Para facilitar a depurao, pode ser til usar esta funo:
def print_attributes(obj):
for attr in vars(obj):
print(attr, getattr(obj, attr))
print_attributes atravessa o dicionrio e imprime cada nome de atributo e o seu valor correspondente.
A funo integrada getattr recebe um objeto e um nome de atributo (como uma string) e devolve o
valor do atributo.

17.12 - Glossrio
linguagem orientada a objeto
Linguagem que fornece recursos, como tipos definidos pelo programador e mtodos, que facilitam a
programao orientada a objeto.
programao orientada a objeto
Estilo de programao na qual os dados e as operaes que os manipulam so organizadas em classes e
mtodos.
179

mtodo
Funo criada dentro de uma definio de classe e invocada em instncias desta classe.
sujeito
Objeto sobre o qual um mtodo invocado.
argumento posicional
Argumento que no inclui um nome de parmetro, portanto no um argumento de palavra-chave.
sobrecarga de operador
Alterao do comportamento de um operador como + para que funcione com um tipo definido pelo
programador.
despacho por tipo
Modelo de programao que invoca funes diferentes dependendo do tipo do operando.
polimrfico
Pertinente a uma funo que pode funcionar com mais de um tipo.
ocultamento de informao
Princpio segundo o qual a interface fornecida por um objeto no deve depender da sua implementao,
especialmente em relao representao dos seus atributos.

17.13 - Exerccios
Exerccio 17.1
Baixe o cdigo deste captulo em http://thinkpython2.com/code/Time2.py. Altere os atributos de Time
para que um nmero inteiro nico represente os segundos decorridos desde a meia-noite. Ento altere
os mtodos (e a funo int_to_time) para funcionar com a nova implementao. Voc no deve
modificar o cdigo de teste em main. Ao terminar, a sada deve ser a mesma que antes.
Soluo: http://thinkpython2.com/code/Time2_soln.py.

Exerccio 17.2
Este exerccio uma histria com moral sobre um dos erros mais comuns e difceis de encontrar no
Python. Escreva uma definio de classe chamada Kangaroo com os seguintes mtodos:
1. Um mtodo __init__ que inicialize um atributo chamado pouch_contents em uma lista vazia.
2. Um mtodo chamado put_in_pouch que receba um objeto de qualquer tipo e o acrescente a
pouch_contents.
3. Um mtodo __str__ que retorne uma representao de string do objeto Kangaroo e os contedos
de pouch (bolsa).
180

Teste o seu cdigo criando dois objetos Kangaroo, atribuindo-os a variveis chamadas kanga e roo, e
ento acrescentando roo ao contedo da bolsa de kanga.
Baixe http://thinkpython2.com/code/BadKangaroo.py. Ele contm uma soluo para o problema
anterior com um defeito bem grande e bem feio. Encontre e corrija o defeito.
Se no conseguir achar a soluo, voc pode baixar http://thinkpython2.com/code/GoodKangaroo.py,
que explica o problema e demonstra uma soluo.

Captulo 18: Herana


O termo mais associado com a programao orientada a objeto herana. A herana a capacidade de
definir uma nova classe que seja uma verso modificada de uma classe existente. Neste captulo
demonstrarei a herana usando classes que representam jogos de cartas, baralhos e mos de pquer.
Se voc no joga pquer, pode ler sobre ele em http://en.wikipedia.org/wiki/Poker, mas no
necessrio; vou dizer tudo o que precisa saber para os exerccios.
Os exemplos de cdigo deste captulo esto disponveis em http://thinkpython2.com/code/Card.py.

18.1 - Objetos Card


H 52 cartas em um baralho, cada uma das quais pertencendo a 1 dos 4 naipes e a 1 dos 13 valores. Os
naipes so espadas, copas, ouros e paus (no bridge, em ordem descendente). A ordem dos valores s,
2, 3, 4, 5, 6, 7, 8, 9, 10, valete, dama e rei. Dependendo do jogo que estiver jogando, um s pode ser
mais alto que o rei ou mais baixo que 2.
Se quisssemos definir um novo objeto para representar uma carta de jogo, os atributos bvios seriam
rank (valor) e suit (naipe). Mas no to bvio qual tipo de atributo deveriam ser. Uma possibilidade
usar strings com palavras como Spade (Espadas) para naipes e Queen (Dama) para valores. Um
problema com esta implementao que no seria fcil comparar cartas para ver qual valor ou naipe
tem classificao mais alta em relao aos outros.
Uma alternativa usar nmeros inteiros para codificar os valores e os naipes. Neste contexto,
codificar significa que vamos definir um mapeamento entre nmeros e naipes, ou entre nmeros e
valores. Este tipo de codificao no tem nada a ver com criptografia.
Por exemplo, esta tabela mostra os naipes e os cdigos de nmero inteiro correspondentes:
Spades (Espadas) 3
Hearts (Copas) 2
Diamonds (Ouros) 1
Clubs (Paus) 0
181

Este cdigo facilita a comparao entre as cartas; como naipes mais altos mapeiam a nmeros mais
altos, podemos comparar naipes aos seus cdigos.
O mapeamento de valores at bvio; cada um dos valores numricos mapeado ao nmero inteiro
correspondente, e para cartas com figuras:
Jack (Valete) 11
Queen (Dama) 12
King (Rei) 13
Estou usando o smbolo para deixar claro que esses mapeamentos no so parte do programa em
Python. Eles so parte do projeto do programa, mas no aparecem explicitamente no cdigo.
A definio de classe para Card (carta) assim:
class Card:
"Represents a standard playing card.
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
Como sempre, o mtodo init recebe um parmetro opcional de cada atributo. A carta padro 2 de
paus.
Para criar um Card, voc chama Card com o naipe e valor desejados:
queen_of_diamonds = Card(1, 12)

18.2 - Atributos de classe


Para exibir objetos Card de uma forma que as pessoas possam ler com facilidade, precisamos de um
mapeamento dos cdigos de nmero inteiro aos naipes e valores correspondentes. Uma forma natural
de fazer isso com listas de strings. Atribumos essas listas a atributos de classe:
# dentro da classe Card:
suit_names = [Clubs, Diamonds, Hearts, Spades]
rank_names = [None, Ace, 2, 3, 4, 5, 6, 7,
8, 9, 10, Jack, Queen, King]
def __str__(self):
return %s of %s % (Card.rank_names[self.rank],
182

Card.suit_names[self.suit])
Variveis como suit_names e rank_names, que so definidas dentro de uma classe, mas fora de
qualquer mtodo, chamam-se atributos de classe porque so associadas com o objeto de classe Card.
Este termo as distingue de variveis como suit e rank, chamadas de atributos de instncia porque so
associados com determinada instncia.
Ambos os tipos de atributo so acessados usando a notao de ponto. Por exemplo, em __str__, self
um objeto Card, e self.rank o seu valor. De forma semelhante, Card um objeto de classe, e
Card.rank_names uma lista de strings associadas com a classe.
Cada carta tem seu prprio suit e rank, mas h s uma cpia de suit_names e rank_names.
Juntando tudo, a expresso Card.rank_names[self.rank] significa use o rank (valor) do atributo do
objeto self como um ndice na lista rank_names da classe Card e selecione a string adequada.
O primeiro elemento de rank_names None, porque no h nenhuma carta com valor zero. Incluindo
None para ocupar uma varivel, conseguimos fazer um belo mapeamento onde o ndice 2 associado
string 2, e assim por diante. Para evitar ter que usar esse truque, poderamos usar um dicionrio em
vez de uma lista.
Com os mtodos que temos por enquanto, podemos criar e exibir cartas:
>>> card1 = Card(2, 11)
>>> print(card1)
Jack of Hearts
A Figura 18.1 um diagrama do objeto de classe Card e uma instncia de Card. Card um objeto de
classe; seu tipo type. card1 uma instncia de Card, ento seu tipo Card. Para economizar espao,
no inclu o contedo de suit_names e rank_names.
Figura 18.1 Diagrama de objeto.

18.3 - Comparao de cartas


Para tipos integrados, h operadores relacionais (<, >, == etc.) que comparam valores e determinam
quando um maior, menor ou igual a outro. Para tipos definidos pelo programador, podemos ignorar o
comportamento dos operadores integrados fornecendo um mtodo denominado __lt__, que representa
menos que.
__lt__ recebe dois parmetros, self e other, e True se self for estritamente menor que other.
183

A ordem correta das cartas no bvia. Por exemplo, qual melhor, o 3 de paus ou o 2 de ouros? Uma
tem o valor mais alto, mas a outra tem um naipe mais alto. Para comparar cartas, preciso decidir o
que mais importante, o valor ou o naipe.
A resposta pode depender de que jogo voc est jogando, mas, para manter a simplicidade, vamos fazer
a escolha arbitrria de que o naipe mais importante, ento todas as cartas de espadas so mais
importantes que as de ouros, e assim por diante.
Com isto decidido, podemos escrever __lt__:
# dentro da classe Card:
def __lt__(self, other):
# conferir os naipes
if self.suit < other.suit: return True
if self.suit > other.suit: return False
# os naipes so os mesmos conferir valores
return self.rank < other.rank
Voc pode escrever isso de forma mais concisa usando uma comparao de tuplas:
# dentro da classe Card:
def __lt__(self, other):
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2
Como exerccio, escreva um mtodo __lt__ para objetos Time. Voc pode usar uma comparao de
tuplas, mas tambm pode usar a comparao de nmeros inteiros.

18.4 - Baralhos
Agora que temos Card, o prximo passo definir Deck (baralho). Como um baralho composto de
cartas, natural que um baralho contenha uma lista de cartas como atributo.
Veja a seguir uma definio de classe para Deck. O mtodo init cria o atributo cards e gera o conjunto
padro de 52 cartas:
class Deck:
def __init__(self):
184

self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)
A forma mais fcil de preencher o baralho com um loop aninhado. O loop exterior enumera os naipes
de 0 a 3. O loop interior enumera os valores de 1 a 13. Cada iterao cria um novo Card com o naipe e
valor atual, e a acrescenta a self.cards.

18.5 - Exibio do baralho


Aqui est um mtodo __str__ para Deck:
# dentro da classe Deck:
def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return \n.join(res)
Este mtodo demonstra uma forma eficiente de acumular uma string grande: a criao de uma lista de
strings e a utilizao do mtodo de string join. A funo integrada str invoca o mtodo __str__ em cada
carta e retorna a representao da string.
Como invocamos join em um caractere newline, as cartas so separadas por quebras de linha. O
resultado esse:
>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs

10 of Spades
185

Jack of Spades
Queen of Spades
King of Spades
Embora o resultado aparea em 52 linhas, na verdade ele uma string longa com quebras de linha.

18.6 - Adio, remoo, embaralhamento e classificao


Para lidar com as cartas, gostaramos de ter um mtodo que removesse uma carta do baralho e a
devolvesse. O mtodo de lista pop oferece uma forma conveniente de fazer isso:
# dentro da classe Deck:
def pop_card(self):
return self.cards.pop()
Como pop retira a ltima carta na lista, estamos lidando com o fundo do baralho.
Para adicionar uma carta, podemos usar o mtodo de lista append:
# dentro da classe Deck:
def add_card(self, card):
self.cards.append(card)
Um mtodo como esse, que usa outro mtodo sem dar muito trabalho, s vezes chamado de folheado.
A metfora vem do trabalho em madeira, onde o folheado uma camada fina de madeira de boa
qualidade colada superfcie de uma madeira mais barata para melhorar a aparncia.
Nesse caso, add_card um mtodo fino que expressa uma operao de lista em termos adequados a
baralhos. Ele melhora a aparncia ou interface da implementao.
Em outro exemplo, podemos escrever um mtodo Deck denominado shuffle, usando a funo shuffle
do mdulo random:
# dentro da classe Deck:
def shuffle(self):
random.shuffle(self.cards)
No se esquea de importar random.
Como exerccio, escreva um mtodo de Deck chamado sort, que use o mtodo de lista sort para
classificar as cartas em um Deck. sort usa o mtodo __lt__ que definimos para determinar a ordem.
186

18.7 - Herana
A herana a capacidade de definir uma nova classe que seja uma verso modificada de uma classe
existente. Como exemplo, digamos que queremos que uma classe represente uma mo, isto , as
cartas mantidas por um jogador. Uma mo semelhante a um baralho: ambos so compostos por uma
coleo de cartas, e ambos exigem operaes como adicionar e remover cartas.
Uma mo tambm diferente de um baralho; h operaes que queremos para mos que no fazem
sentido para um baralho. Por exemplo, no pquer poderamos comparar duas mos para ver qual ganha.
No bridge, poderamos calcular a pontuao de uma mo para fazer uma aposta.
Essa relao entre classes semelhante, mas diferente adequa-se herana. Para definir uma nova
classe que herda algo de uma classe existente, basta colocar o nome da classe existente entre
parnteses:
class Hand(Deck):
"Represents a hand of playing cards.
Esta definio indica que Hand herda de Deck; isso significa que podemos usar mtodos como
pop_card e add_card para Hand bem como para Deck.
Quando uma nova classe herda de uma existente, a existente chama-se pai e a nova classe chama-se
filho.
Neste exemplo, Hand herda __init__ de Deck, mas na verdade no faz o que queremos: em vez de
preencher a mo com 52 cartas novas, o mtodo init de Hand deve inicializar card com uma lista vazia.
Se fornecermos um mtodo init na classe Hand, ele ignora o da classe Deck:
# dentro da classe Hand:
def __init__(self, label=):
self.cards = []
self.label = label
Ao criar Hand, o Python invoca este mtodo init, no o de Deck.
>>> hand = Hand(new hand)
>>> hand.cards
[]
>>> hand.label
new hand
187

Outros mtodos so herdados de Deck, portanto podemos usar pop_card e add_card para lidar com uma
carta:
>>> deck = Deck()
>>> card = deck.pop_card()
>>> hand.add_card(card)
>>> print(hand)
King of Spades
Um prximo passo natural seria encapsular este cdigo em um mtodo chamado move_cards:
# dentro da classe Deck:
def move_cards(self, hand, num):
for i in range(num):
hand.add_card(self.pop_card())
move_cards recebe dois argumentos, um objeto Hand e o nmero de cartas com que vai lidar. Ele altera
tanto self como hand e retorna None.
Em alguns jogos, as cartas so movidas de uma mo a outra, ou de uma mo de volta ao baralho.
possvel usar move_cards para algumas dessas operaes: self pode ser um Deck ou Hand, e hand,
apesar do nome, tambm pode ser um Deck.
A herana um recurso til. Alguns programas que poderiam ser repetitivos sem herana podem ser
escritos de forma mais elegante com ela. A herana pode facilitar a reutilizao de cdigo, j que voc
pode personalizar o comportamento de classes pais sem ter que alter-las. Em alguns casos, a estrutura
de herana reflete a estrutura natural do problema, o que torna o projeto mais fcil de entender.
De outro lado, a herana pode tornar os programas difceis de ler. Quando um mtodo invocado, s
vezes no est claro onde encontrar sua definio. O cdigo relevante pode ser espalhado por vrios
mdulos. Alm disso, muitas das coisas que podem ser feitas usando a herana podem ser feitas sem
elas, s vezes, at de forma melhor.

18.8 - Diagramas de classe


Por enquanto vimos diagramas de pilha, que mostram o estado de um programa e diagramas de objeto,
que mostram os atributos de um objeto e seus valores. Esses diagramas representam um retrato da
execuo de um programa, ento eles mudam no decorrer da execuo do programa.
188

Eles tambm so altamente detalhados; para alguns objetivos, detalhados demais. Um diagrama de
classe uma representao mais abstrata da estrutura de um programa. Em vez de mostrar objetos
individuais, ele mostra classes e as relaes entre elas.
H vrios tipos de relaes entre as classes:
Os objetos de uma classe podem conter referncias a objetos em outra classe. Por exemplo, cada
Rectangle contm uma referncia a um Point, e cada Deck contm referncias a muitos Cards.
Esse tipo de relao chama-se HAS-A (tem um), com a ideia de um Rectangle tem um Point.
Uma classe pode herdar de outra. Esta relao chama-se IS-A ( um), com a ideia de um Hand
um tipo de Deck.
Uma classe pode depender de outra no sentido de que os objetos em uma classe possam receber
objetos na segunda classe como parmetros ou usar esses objetos como parte de um clculo.
Este tipo de relao chama-se dependncia.
Um diagrama de classe uma representao grfica dessas relaes. Por exemplo, a Figura 18.2 mostra
as relaes entre Card, Deck e Hand.
Figura 18.2 Diagrama de classe.
A flecha com um tringulo oco representa uma relao IS-A; nesse caso, indica que Hand herda de
Deck.
A ponta de flecha padro representa uma relao HAS-A; nesse caso, um Deck tem referncias a
objetos Card.
Uma estrela (*) perto da ponta de flecha uma multiplicidade; ela indica quantos Cards um Deck tem.
Uma multiplicidade pode ser um nmero simples como 52, um intervalo como 5..7 ou uma estrela, que
indica que um Deck pode ter qualquer nmero de Cards.
No h nenhuma dependncia neste diagrama. Elas normalmente apareceriam com uma flecha
tracejada. Ou, se houver muitas dependncias, s vezes elas so omitidas.
Um diagrama mais detalhado poderia mostrar que um Deck na verdade contm uma lista de Cards, mas
os tipos integrados como lista e dict no so normalmente includos em diagramas de classe.

18.9 - Encapsulamento de dados


Os captulos anteriores demonstram um plano de desenvolvimento que poderamos chamar de projeto
orientado a objeto. Identificamos os objetos de que precisamos como Point, Rectangle e Time e
definimos classes para represent-los. Em cada caso h uma correspondncia bvia entre o objeto e
alguma entidade no mundo real (ou, pelo menos, no mundo matemtico).
189

Mas, s vezes, menos bvio quais objetos voc precisa e como eles devem interagir. Nesse caso
necessrio um plano de desenvolvimento diferente. Da mesma forma em que descobrimos interfaces de
funo por encapsulamento e generalizao, podemos descobrir interfaces de classe por
encapsulamento de dados.
A anlise de Markov, de Anlise de Markov, na pgina 200, apresenta um bom exemplo. Se baixar o
meu cdigo em http://thinkpython2.com/code/markov.py, voc vai ver que ele usa duas variveis
globais suffix_map e prefix que so lidas e escritas a partir de vrias funes.
suffix_map = {}
prefix = ()
Como essas variveis so globais, s podemos executar uma anlise de cada vez. Se lermos dois textos,
seus prefixos e sufixos seriam acrescentados s mesmas estruturas de dados (o que geraria textos
interessantes).
Para executar anlises mltiplas e guard-las separadamente, podemos encapsular o estado de cada
anlise em um objeto. assim que fica:
class Markov:
def __init__(self):
self.suffix_map = {}
self.prefix = ()
Em seguida, transformamos as funes em mtodos. Por exemplo, aqui est process_word:
def process_word(self, word, order=2):
if len(self.prefix) < order:
self.prefix += (word,)
return
try:
self.suffix_map[self.prefix].append(word)
except KeyError:
# se no houver entradas deste prefixo, crie uma.
self.suffix_map[self.prefix] = [word]
self.prefix = shift(self.prefix, word)
190

Transformar um programa como esse alterando o projeto sem mudar o comportamento outro
exemplo de refatorao (veja Refatorao, na pgina 70).
Este exemplo sugere um plano de desenvolvimento para projetar objetos e mtodos:
1. Comece escrevendo funes que leiam e criem variveis globais (quando necessrio).
2. Uma vez que o programa esteja funcionando, procure associaes entre variveis globais e
funes que as usem.
3. Encapsule variveis relacionadas como atributos de objeto.
4. Transforme as funes associadas em mtodos da nova classe.
Como exerccio, baixe o meu cdigo de Markov de http://thinkpython2.com/code/markov.py e siga os
passos descritos acima para encapsular as variveis globais como atributos de uma nova classe
chamada Markov.
Soluo: http://thinkpython2.com/code/Markov.py (observe o M maisculo).

18.10 - Depurao
A herana pode dificultar a depurao porque quando voc invoca um mtodo em um objeto, pode ser
difcil compreender qual mtodo ser invocado.
Suponha que esteja escrevendo uma funo que funcione com objetos Hand. Voc gostaria que ela
funcionasse com todos os tipos de Hand, como PokerHands, BridgeHands etc. Se invocar um mtodo
como shuffle, poder receber o que foi definido em Deck, mas se alguma das subclasses ignorar este
mtodo, voc receber outra verso. Este comportamento pode ser bom, mas tambm confuso.
A qualquer momento em que no esteja seguro a respeito do fluxo de execuo do seu programa, a
soluo mais simples acrescentar instrues de exibio no incio dos mtodos em questo. Se
Deck.shuffle exibir uma mensagem que diz algo como Running Deck.shuffle, ento no decorrer da
execuo do programa ele monitora seu fluxo.
Uma alternativa usar esta funo, que recebe um objeto e um nome de mtodo (como uma string) e
retorna a classe que fornece a definio do mtodo:
def find_defining_class(obj, meth_name):
for ty in type(obj).mro():
if meth_name in ty.__dict__:
return ty
Aqui est um exemplo:
>>> hand = Hand()
191

>>> find_defining_class(hand, shuffle)


<class Card.Deck>
Ento o mtodo shuffle deste Hand o de Deck.
find_defining_class usa o mtodo mro para obter a lista de objetos de classe (tipos) onde os mtodos
sero procurados. MRO significa ordem de resoluo do mtodo, que a sequncia de classes que
o Python pesquisa para descobrir um nome de mtodo.
Aqui est uma sugesto de projeto: quando voc ignora um mtodo, a interface do novo mtodo deve
ser a mesma que a do antigo. Ela deve receber os mesmos parmetros, retornar o mesmo tipo e
obedecer s mesmas precondies e ps-condies. Se seguir esta regra, voc descobrir que qualquer
funo projetada para funcionar com uma instncia de uma classe pai, como Deck, tambm funcionar
com instncias de classes filho como Hand e PokerHand.
Se violar esta regra, o que se chama de princpio de substituio de Liskov, seu cdigo cair como
(desculpe) um castelo de cartas.

18.11 - Glossrio
codificar
Representar um conjunto de valores usando outro conjunto de valores construindo um mapeamento
entre eles.
atributo de classe
Atributo associado a um objeto de classe. Os atributos de classe so definidos dentro de uma definio
de classe, mas fora de qualquer mtodo.
atributo de instncia
Atributo associado a uma instncia de uma classe.
folheado
Mtodo ou funo que apresenta uma interface diferente para outra funo sem fazer muitos clculos.
herana
Capacidade de definir uma nova classe que seja uma verso modificada de uma classe definida
anteriormente.
classe-pai
Classe da qual uma classe-filho herda.
classe-filho
Nova classe criada por herana de uma classe existente; tambm chamada de subclasse.
192

relao IS-A
Relao entre uma classe-filho e sua classe-pai.
relao HAS-A
Relao entre duas classes onde as instncias de uma classe contm referncias a instncias da outra.
dependncia
Relao entre duas classes onde as instncias de uma classe usam instncias de outra classe, mas no as
guardam como atributos.
diagrama de classe
Diagrama que mostra as classes em um programa e as relaes entre elas.
multiplicidade
Notao em um diagrama de classe que mostra, para uma relao HAS-A, quantas referncias dela so
instncias de outra classe.
encapsulamento de dados
Plano de desenvolvimento de programa que envolve um prottipo usando variveis globais e uma
verso final que transforma as variveis globais em atributos de instncia.

18.12 - Exerccios
Exerccio 18.1
Para o seguinte programa, projete um diagrama de classe UML que mostre estas classes e as relaes
entre elas.
class PingPongParent:
pass
class Ping(PingPongParent):
def __init__(self, pong):
self.pong = pong
class Pong(PingPongParent):
def __init__(self, pings=None):
if pings is None:
self.pings = []
else:
self.pings = pings
193

def add_ping(self, ping):


self.pings.append(ping)
pong = Pong()
ping = Ping(pong)
pong.add_ping(ping)

Exerccio 18.2
Escreva um mtodo Deck chamado deal_hands que receba dois parmetros: o nmero de mos e o
nmero de cartas por mo. Ele deve criar o nmero adequado de objetos Hand, lidar com o nmero
adequado de cartas por mo e retornar uma lista de Hands.

Exerccio 18.3
A seguir, as mos possveis no pquer, em ordem crescente de valor e ordem decrescente de
probabilidade:
par:
Duas cartas com o mesmo valor.
dois pares:
Dois pares de cartas com o mesmo valor.
trinca:
Trs cartas com o mesmo valor.
sequncia:
Cinco cartas com valores em sequncia (os ases podem ser altos ou baixos, ento Ace-2-3-4-5 uma
sequncia, assim como 10-Jack-Queen-King-Ace, mas Queen-King-Ace-2-3 no .)
flush:
Cinco cartas com o mesmo naipe.
full house:
Trs cartas com um valor, duas cartas com outro.
quadra:
Quatro cartas com o mesmo valor.
straight flush:
194

Cinco cartas em sequncia (como definido acima) e com o mesmo naipe.


A meta desses exerccios estimar a probabilidade de ter estas vrias mos.
1. Baixe os seguintes arquivos de http://thinkpython2.com/code:
Card.py:
Verso completa das classes Card, Deck e Hand deste captulo.
PokerHand.py:
Uma implementao incompleta de uma classe que representa uma mo de pquer e cdigo para test-
la.
1. Se executar PokerHand.py, voc ver que o programa cria mos de pquer com 7 cartas e
verifica se alguma delas contm um flush. Leia este cdigo com ateno antes de continuar.
2. Acrescente mtodos a PokerHand.py chamados has_pair, has_twopair, etc. que retornem True
ou False conforme a mo cumpra os critrios em questo. Seu cdigo deve funcionar
corretamente para mos que contenham qualquer nmero de cartas (embora 5 e 7 sejam as
quantidades mais comuns).
3. Escreva um mtodo chamado classify que descubra a classificao do valor mais alto para uma
mo e estabelea o atributo label em questo. Por exemplo, uma mo de 7 cartas poderia conter
um flush e um par; ela deve ser marcada como flush.
4. Quando se convencer de que os seus mtodos de classificao esto funcionando, o prximo
passo deve ser estimar as probabilidades de vrias mos. Escreva uma funo em PokerHand.py
que embaralhe cartas, divida-as em mos, classifique as mos e conte o nmero de vezes em
que vrias classificaes aparecem.
5. Exiba uma tabela das classificaes e suas probabilidades. Execute seu programa com nmeros
cada vez maiores de mos at que os valores de sada convirjam a um grau razovel de exatido.
Compare seus resultados com os valores em http://en.wikipedia.org/wiki/Hand_rankings.
Soluo: http://thinkpython2.com/code/PokerHandSoln.py.

Captulo 19: Extra


Uma das minhas metas com este livro ensinar o mnimo possvel de Python. Quando havia duas
formas de fazer algo, escolhia uma e evitava mencionar a outra. Ou, s vezes, usava a segunda como
exerccio.
195

Agora quero voltar a algumas coisas boas que ficaram para trs. O Python oferece vrios recursos que
no so realmente necessrios voc pode escrever um bom cdigo sem eles mas com eles possvel
escrever um cdigo mais conciso, legvel ou eficiente e, s vezes, todos os trs.

19.1 - Expresses condicionais


Vimos instrues condicionais em Execuo condicional, na pgina 78. As instrues condicionais
muitas vezes so usadas para escolher um entre dois valores; por exemplo:
if x > 0:
y = math.log(x)
else:
y = float(nan)
Esta instruo verifica se x positivo. Nesse caso, ela calcula math.log. Do contrrio, math.log causaria
um ValueError. Para evitar interromper o programa, geramos um NaN, que um valor de ponto
flutuante especial que representa um No nmero.
Podemos escrever essa instruo de forma mais concisa usando uma expresso condicional:
y = math.log(x) if x > 0 else float(nan)
Voc quase pode ler esta linha como se tivesse sido escrita em ingls: y recebe log-x se x for maior
que 0; do contrrio, ele recebe NaN.
As funes recursivas por vezes podem ser reescritas usando expresses condicionais. Por exemplo,
aqui est uma verso recursiva de factorial:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
Podemos reescrev-la assim:
def factorial(n):
return 1 if n == 0 else n * factorial(n-1)
Outro uso de expresses condicionais lidar com argumentos opcionais. Por exemplo, aqui est o
mtodo init de GoodKangaroo (veja o Exerccio 17.2):
196

def __init__(self, name, contents=None):


self.name = name
if contents == None:
contents = []
self.pouch_contents = contents
Podemos reescrev-lo assim:
def __init__(self, name, contents=None):
self.name = name
self.pouch_contents = [] if contents == None else contents
Em geral, possvel substituir uma instruo condicional por uma expresso condicional se ambos os
ramos contiverem expresses simples que sejam retornadas ou atribudas mesma varivel.

19.2 - Abrangncia de listas


Em Mapeamento, filtragem e reduo, na pgina 147, vimos os padres de filtragem e mapeamento.
Por exemplo, esta funo toma uma lista de strings, mapeia o mtodo de string capitalize aos
elementos, e retorna uma nova lista de strings:
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res
Podemos escrever isso de forma mais concisa usando abrangncia de listas (list comprehension):
def capitalize_all(t):
return [s.capitalize() for s in t]
Os operadores de colchete indicam que estamos construindo uma nova lista. A expresso dentro dos
colchetes especifica os elementos da lista, e a clusula for indica qual sequncia estamos atravessando.
A sintaxe da abrangncia de listas um pouco esquisita porque a varivel de loop, s nesse exemplo,
aparece na expresso antes de chegarmos definio.
Abrangncias de listas tambm podem ser usadas para filtragem. Por exemplo, esta funo s seleciona
os elementos de t que so maisculos, e retorna uma nova lista:
197

def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res
Podemos reescrev-la usando abrangncia de listas:
def only_upper(t):
return [s for s in t if s.isupper()]
Abrangncias de listas so concisas e fceis de ler, pelo menos para expresses simples. E so
normalmente mais rpidas que os loops for equivalentes, s vezes muito mais rpidas. Ento, se voc
ficar irritado comigo por no ter mencionado isso antes, eu entendo.
Porm, em minha defesa, as abrangncias de listas so mais difceis de depurar porque no possvel
ter instrues de exibio dentro do loop. Sugiro que voc as use s se o clculo for simples o
suficiente para que acerte j de primeira. E para principiantes isso significa nunca.

19.3 - Expresses geradoras


Expresses geradoras so semelhantes s abrangncias de listas, mas com parnteses em vez de
colchetes:
>>> g = (x**2 for x in range(5))
>>> g
<generator object <genexpr> at 0x7f4c45a786c0>
O resultado um objeto gerador que sabe como fazer iteraes por uma sequncia de valores. No
entanto, ao contrrio de uma abrangncia de listas, ele no calcula todos os valores de uma vez; espera
pelo pedido. A funo integrada next recebe o prximo valor do gerador:
>>> next(g)
0
>>> next(g)
1
198

Quando voc chega no fim da sequncia, next cria uma exceo StopIteration. Tambm possvel usar
um loop for para fazer a iterao pelos valores:
>>> for val in g:
print(val)
4
9
16
O objeto gerador monitora a posio em que est na sequncia, portanto o loop for continua de onde
next parou. Uma vez que o gerador se esgotar, ele continua criando StopException:
>>> next(g)
StopIteration
As expresses geradoras muitas vezes so usadas com funes como sum, max e min:
>>> sum(x**2 for x in range(5))
30

19.4 - any e all


O Python tem uma funo integrada, any, que recebe uma sequncia de valores booleanos e retorna
True se algum dos valores for True. Ela funciona em listas:
>>> any([False, False, True])
True
Entretanto, muitas vezes usada com expresses geradoras:
>>> any(letter == t for letter in monty)
True
Esse exemplo no muito til porque faz a mesma coisa que o operador in. Porm, podemos usar any
para reescrever algumas das funes de pesquisa que escrevemos em Busca, na pgina 136. Por
exemplo, poderamos escrever avoids dessa forma:
def avoids(word, forbidden):
return not any(letter in forbidden for letter in word)
A funo quase pode ser lida como uma frase em ingls: word evita forbidden se no houver nenhuma
letra proibida em word.
199

Usar any com uma expresso geradora eficiente porque o programa interrompido imediatamente se
encontrar um valor True, ento no preciso avaliar a sequncia inteira.
O Python oferece outra funo integrada, all, que retorna True se todos os elementos da sequncia
forem True. Como exerccio, use all para reescrever uses_all de Busca, na pgina 136.

19.5 - Conjuntos
Na seo Subtrao de dicionrio, da pgina 198, uso dicionrios para encontrar as palavras que
aparecem em um documento, mas no numa lista de palavras. A funo que escrevi recebe d1, que
contm as palavras do documento como chaves e d2, que contm a lista de palavras. Ela retorna um
dicionrio que contm as chaves de d1 que no esto em d2:
def subtract(d1, d2):
res = dict()
for key in d1:
if key not in d2:
res[key] = None
return res
Em todos esses dicionrios, os valores no so None porque nunca os usamos. O resultado que
desperdiamos espao de armazenamento.
O Python fornece outro tipo integrado, chamado set (conjunto), que se comporta como uma coleo de
chaves de dicionrio sem valores. Acrescentar elementos a um conjunto rpido; assim como verificar
a adeso. E os conjuntos fornecem mtodos e operadores para calcular operaes de conjuntos.
Por exemplo, a subtrao de conjuntos est disponvel como um mtodo chamado difference ou como
um operador, -. Portanto, podemos reescrever subtract desta forma:
def subtract(d1, d2):
return set(d1) - set(d2)
O resultado um conjunto em vez de um dicionrio, mas, para operaes como iterao, o
comportamento o mesmo.
Alguns exerccios neste livro podem ser feitos de forma concisa e eficiente com conjuntos. Por
exemplo, aqui est uma soluo para has_duplicates, do Exerccio 10.7, que usa um dicionrio:
def has_duplicates(t):
d = {}
200

for x in t:
if x in d:
return True
d[x] = True
return False
Quando um elemento aparece pela primeira vez, ele acrescentado ao dicionrio. Se o mesmo
elemento aparece novamente, a funo retorna True.
Usando conjuntos, podemos escrever a mesma funo dessa forma:
def has_duplicates(t):
return len(set(t)) < len(t)
Um elemento s pode aparecer em um conjunto uma vez, portanto, se um elemento em t aparecer mais
de uma vez, o conjunto ser menor que t. Se no houver duplicatas, o conjunto ter o mesmo tamanho
que t.
Tambm podemos usar conjuntos para fazer alguns exerccios no Captulo 9. Por exemplo, aqui est
uma verso de uses_only com um loop:
def uses_only(word, available):
for letter in word:
if letter not in available:
return False
return True
uses_only verifica se todas as cartas em word esto em available. Podemos reescrev-la assim:
def uses_only(word, available):
return set(word) <= set(available)
O operador <= verifica se um conjunto um subconjunto ou outro, incluindo a possibilidade de que
sejam iguais, o que verdade se todas as letras de word aparecerem em available.
Como exerccio, reescreva avoids usando conjuntos.
201

19.6 - Contadores
Um contador como um conjunto, exceto que se um elemento aparecer mais de uma vez, o contador
acompanha quantas vezes ele aparece. Se tiver familiaridade com a ideia matemtica de um
multiconjunto, um contador uma forma natural de representar um multiconjunto.
Contadores so definidos em um mdulo-padro chamado collections, portanto preciso import-lo.
Voc pode inicializar um contador com uma string, lista ou alguma outra coisa que seja compatvel com
iterao:
>>> from collections import Counter
>>> count = Counter(parrot)
>>> count
Counter({r: 2, t: 1, o: 1, p: 1, a: 1})
Os contadores comportam-se como dicionrios de muitas formas; eles mapeiam cada chave ao nmero
de vezes que aparece. Como em dicionrios, as chaves tm de ser hashable.
Ao contrrio de dicionrios, os contadores no causam uma exceo se voc acessar um elemento que
no aparece. Em vez disso, retornam 0:
>>> count[d]
0
Podemos usar contadores para reescrever is_anagram do Exerccio 10.6:
def is_anagram(word1, word2):
return Counter(word1) == Counter(word2)
Se duas palavras forem anagramas, elas contm as mesmas letras com as mesmas contagens, ento seus
contadores so equivalentes.
Os contadores oferecem mtodos e operadores para executar operaes similares s dos conjuntos,
incluindo adio, subtrao, unio e interseco. E eles fornecem um mtodo muitas vezes til,
most_common, que retorna uma lista de pares frequncia-valor, organizados do mais ao menos comum:
>>> count = Counter(parrot)
>>> for val, freq in count.most_common(3):
print(val, freq)
r2
p1
202

a1

19.7 - defaultdict
O mdulo collections tambm tem defaultdict, que se parece com um dicionrio, exceto pelo fato de
que se voc acessar uma chave que no existe, um novo valor pode ser gerado automaticamente.
Quando voc cria um defaultdict, fornece uma funo usada para criar valores. Uma funo usada para
criar objetos s vezes chamada de factory (fbrica). As funes integradas que criam listas, conjuntos
e outros tipos podem ser usadas como fbricas:
>>> from collections import defaultdict
>>> d = defaultdict(list)
Note que o argumento list, que um objeto de classe, no list(), que uma nova lista. A funo que
voc fornece no chamada a menos que voc acesse uma chave que no existe:
>>> t = d[new key]
>>> t
[]
A nova lista, que estamos chamando de t, tambm adicionada ao dicionrio. Ento, se alterarmos t, a
mudana aparece em d:
>>> t.append(new value)
>>> d
defaultdict(<class list>, {new key: [new value]})
Se estiver fazendo um dicionrio de listas, voc pode escrever um cdigo mais simples usando
defaultdict. Na minha soluo para o Exerccio 12.2, que voc pode ver em
http://thinkpython2.com/code/anagram_sets.py, fao um dicionrio que mapeia uma string organizada
de letras a uma lista de palavras que pode ser soletrada com essas letras. Por exemplo, opst mapeia
para a lista [opts, post, pots, spot, stop, tops].
Aqui est o cdigo original:
def all_anagrams(filename):
d = {}
for line in open(filename):
203

word = line.strip().lower()
t = signature(word)
if t not in d:
d[t] = [word]
else:
d[t].append(word)
return d
Isso pode ser simplificado usando setdefault, que voc poderia ter usado no Exerccio 11.2:
def all_anagrams(filename):
d = {}
for line in open(filename):
word = line.strip().lower()
t = signature(word)
d.setdefault(t, []).append(word)
return d
O problema dessa soluo que ela faz uma lista nova a cada vez, mesmo que no seja necessrio. Para
listas, isso no grande coisa, mas se a funo fbrica for complicada, poderia ser.
Podemos evitar este problema e simplificar o cdigo usando um defaultdict:
def all_anagrams(filename):
d = defaultdict(list)
for line in open(filename):
word = line.strip().lower()
t = signature(word)
d[t].append(word)
return d
A minha soluo para o Exerccio 18.3, que voc pode baixar em
http://thinkpython2.com/code/PokerHandSoln.py, usa setdefault na funo has_straightflush. O
problema dessa soluo criar um objeto Hand cada vez que passa pelo loop, seja ele necessrio ou
no. Como exerccio, reescreva-a usando um defaultdict.
204

19.8 - Tuplas nomeadas


Muitos objetos simples so basicamente colees de valores relacionados. Por exemplo, o objeto Point,
definido no Captulo 15, contm dois nmeros, x e y. Ao definir uma classe como essa, normalmente
voc comea com um mtodo init e um mtodo str:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return (%g, %g) % (self.x, self.y)
muito cdigo para transmitir pouca informao. O Python tem uma forma mais concisa de dizer a
mesma coisa:
from collections import namedtuple
Point = namedtuple(Point, [x, y])
O primeiro argumento o nome da classe que voc quer criar. O segundo uma lista dos atributos que
o objeto Point deve ter, como strings. O valor de retorno de namedtuple um objeto de classe:
>>> Point
<class __main__.Point>
Point fornece automaticamente mtodos como __init__ e __str__ ento no preciso escrev-los.
Para criar um objeto Point, voc usa a classe Point como uma funo:
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
O mtodo init atribui os argumentos a atributos usando os nomes que voc forneceu. O mtodo str
exibe uma representao do objeto Point e seus atributos.
Voc pode acessar os elementos da tupla nomeada pelo nome:
>>> p.x, p.y
(1, 2)
Mas tambm pode tratar uma tupla nomeada como uma tupla:
205

>>> p[0], p[1]


(1, 2)
>>> x, y = p
>>> x, y
(1, 2)
Tuplas nomeadas fornecem uma forma rpida de definir classes simples. O problema que classes
simples no ficam sempre simples. Mais adiante voc poder decidir que quer acrescentar mtodos a
uma tupla nomeada. Nesse caso, voc poder definir uma nova classe que herde da tupla nomeada:
class Pointier(Point):
# adicionar mais mtodos aqui
Ou poder mudar para uma definio de classe convencional.

19.9 - Reunindo argumentos de palavra-chave


Em Tuplas com argumentos de comprimento varivel, na pgina 181, vimos como escrever uma
funo que rene seus argumentos em uma tupla:
def printall(*args):
print(args)
Voc pode chamar esta funo com qualquer nmero de argumentos posicionais (isto , argumentos
que no tm palavras-chave):
>>> printall(1, 2.0, 3)
(1, 2.0, 3)
Porm, o operador * no rene argumentos de palavra-chave:
>>> printall(1, 2.0, third=3)
TypeError: printall() got an unexpected keyword argument third
Para reunir argumentos de palavra-chave, voc pode usar o operador **:
def printall(*args, **kwargs):
print(args, kwargs)
Voc pode chamar o parmetro de coleta de palavra-chave, como quiser, mas kwargs uma escolha
comum. O resultado um dicionrio que mapeia palavras-chave a valores:
206

>>> printall(1, 2.0, third=3)


(1, 2.0) {third: 3}
Se tiver um dicionrio de palavras-chave e valores, pode usar o operador de disperso, **, para chamar
uma funo:
>>> d = dict(x=1, y=2)
>>> Point(**d)
Point(x=1, y=2)
Sem o operador de disperso, a funo trataria d como um nico argumento posicional, e ento
atribuiria d a x e se queixaria porque no h nada para atribuir a y:
>>> d = dict(x=1, y=2)
>>> Point(d)
Traceback (most recent call last):
File <stdin>, line 1, in <module>
TypeError: __new__() missing 1 required positional argument: y
Quando estiver trabalhando com funes com um grande nmero de parmetros, muitas vezes til
criar e usar dicionrios que especifiquem as opes usadas frequentemente.

19.10 - Glossrio
expresso condicional
Expresso que contm um de dois valores, dependendo de uma condio.
abrangncia de listas
Expresso com um loop for entre colchetes que produz uma nova lista.
expresso geradora
Uma expresso com um loop for entre parnteses que produz um objeto gerador.
multiconjunto
Entidade matemtica que representa um mapeamento entre os elementos de um conjunto e o nmero de
vezes que aparecem.
fbrica (factory)
Funo normalmente passada como parmetro, usada para criar objetos.
207

19.11 - Exerccios
Exerccio 19.1
Esta uma funo que calcula o coeficiente binominal recursivamente:
def binomial_coeff(n, k):
"Compute the binomial coefficient n choose k.
n: number of trials
k: number of successes
returns: int

if k == 0:
return 1
if n == 0:
return 0
res = binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)
return res
Reescreva o corpo da funo usando expresses condicionais aninhadas.
Uma observao: esta funo no muito eficiente porque acaba calculando os mesmos valores vrias
vezes. Voc pode torn-lo mais eficiente com memos (veja Memos, na pgina 169). No entanto, vai
ver que mais difcil usar memos se escrev-la usando expresses condicionais.

Apndice A: Depurao
Durante a depurao, voc deve distinguir entre tipos diferentes de erros para rastre-los mais
rapidamente:
Erros de sintaxe so descobertos pelo interpretador quando ele est traduzindo o cdigo-fonte
para cdigo de bytes. Eles indicam que h algo errado com a estrutura do programa. Exemplo: a
omisso dos dois pontos no fim de uma instruo def gera a mensagem um tanto redundante
SyntaxError: invalid syntax.
Erros de tempo de execuo so produzidos pelo interpretador se algo der errado durante a
execuo do programa. A maior parte das mensagens de erro de tempo de execuo inclui
informaes sobre onde o erro ocorreu e o que as funes estavam fazendo. Exemplo: a
208

recursividade infinita eventualmente leva ao erro de tempo de execuo maximum recursion


depth exceeded.
Erros semnticos so problemas com um programa que executado sem produzir mensagens de
erro, mas que no faz a coisa certa. Exemplo: uma expresso que no pode ser avaliada na
ordem esperada, produzindo um resultado incorreto.
O primeiro passo da depurao compreender com que tipo de erro voc est lidando. Embora as
prximas sees sejam organizadas pelo tipo de erro, algumas tcnicas so aplicveis em mais de uma
situao.

A.1 - Erros de sintaxe


Os erros de sintaxe normalmente so fceis de corrigir, uma vez que voc descubra quais so.
Infelizmente, as mensagens de erro muitas vezes no so teis. As mensagens mais comuns so
SyntaxError: invalid syntax e SyntaxError: invalid token, e nenhuma das duas muito informativa.
Por outro lado, a mensagem diz onde no programa o problema ocorreu. E, na verdade, diz a voc onde
o Python notou um problema, que no necessariamente onde o erro est. s vezes, o erro est antes
da posio da mensagem de erro, muitas vezes na linha precedente.
Se estiver construindo o programa incrementalmente, voc ter uma boa ideia sobre onde encontrar o
erro. Estar na ltima linha que acrescentou.
Se estiver copiando o cdigo de um livro, comece comparando com ateno o seu cdigo e o do livro.
Verifique cada caractere. Ao mesmo tempo, lembre-se de que o livro pode estar errado, ento, se vir
algo que parece um erro de sintaxe, pode ser mesmo.
Aqui esto algumas formas de evitar os erros de sintaxe mais comuns:
1. Confira se no est usando uma palavra-chave do Python para um nome de varivel.
2. Verifique se h dois pontos no fim do cabealho de cada instruo composta, incluindo
instrues for, while, if e def.
3. Confira se as strings no cdigo tm as aspas correspondentes. Verifique se todas as aspas so
retas, em vez de curvas.
4. Se tiver strings com vrias linhas com aspas triplas (simples ou duplas), confira se fechou a
string adequadamente. Uma string no fechada pode causar um erro de invalid token no fim do
seu programa, ou pode tratar a parte seguinte do programa como uma string at chegar string
seguinte. No segundo caso, o programa pode nem produzir uma mensagem de erro!
5. Um operador inicial aberto (, { ou [ faz o Python continuar at a linha seguinte, como se esta
fosse parte da instruo atual. Geralmente, um erro ocorre quase imediatamente na linha
seguinte.
209

6. Confira se h o clssico = em vez do == dentro de uma condicional.


7. Verifique a endentao para ter certeza de que est alinhada como deveria. O Python pode lidar
com espaos e tabulaes, mas, se mistur-los, isso pode causar problemas. A melhor forma de
evitar esse problema usar um editor de texto que identifique o Python e gere endentao
consistente.
8. Se h caracteres no ASCII no cdigo (incluindo strings e comentrios), isso pode causar
problemas, embora o Python 3 normalmente lide com caracteres no ASCII. Tenha cuidado se
colar texto de uma pgina web ou outra fonte.
Se nada funcionar, v para a prxima seo

A.1.1 - Continuo fazendo alteraes e no faz nenhuma diferena


Se o interpretador disser que h um erro e voc no o encontra, pode ser que voc e o interpretador no
estejam olhando para o mesmo cdigo. Verifique o seu ambiente de programao para ter certeza de
que o programa que est editando o mesmo que o Python est tentando executar.
Se no estiver certo, tente pr um erro de sintaxe bvio e deliberado no incio do programa. Agora
execute-o novamente. Se o interpretador no encontrar o novo erro, voc no est executando o novo
cdigo.
H alguns culpados provveis:
Voc editou o arquivo e esqueceu de salvar as alteraes antes de execut-lo novamente. Alguns
ambientes de programao fazem isso para voc, mas alguns no fazem.
Voc mudou o nome do arquivo, mas ainda est executando o nome antigo.

Algo no seu ambiente de desenvolvimento est configurado incorretamente.

Se estiver escrevendo um mdulo e usando import, confira se no usou o mesmo nome no seu
mdulo que os dos mdulos padro do Python.
Se voc estiver usando import para ler um mdulo, lembre-se de que preciso reiniciar o
interpretador ou usar reload para ler um arquivo alterado. Se importar o mdulo novamente, ele
no faz nada.
Se j esgotou as possibilidades e no conseguiu descobrir o que est acontecendo, uma abordagem
comear novamente com um programa como Hello, World!, para ter certeza de que consegue
executar um programa conhecido. Ento, gradualmente acrescente as partes do programa original ao
novo.
210

A.2 - Erros de tempo de execuo


Uma vez que o seu programa esteja sintaticamente correto, o Python pode l-lo e, pelo menos, comear
a execut-lo. O que poderia dar errado?

A.2.1 - Meu programa no faz nada


Este problema mais comum quando o seu arquivo composto de funes e classes, mas na verdade
no invoca uma funo para comear a execuo. Isso pode ser intencional se voc s planeja importar
este mdulo para fornecer classes e funes.
Se no for intencional, tenha certeza de que h uma chamada de funo no programa, e que o fluxo de
execuo o alcana (veja Fluxo da execuo a seguir).

A.2.2 - Meu programa trava


Se um programa parar e parecer que no est fazendo nada, ele est travado. Muitas vezes isso
significa que est preso em um loop ou recurso infinita.
Se houver determinado loop que voc suspeita ser o problema, acrescente uma instruo print
imediatamente antes do loop que diga entrando no loop, e outra imediatamente depois que
diga saindo do loop.
Execute o programa. Se receber a primeira mensagem e a segunda no, voc tem um loop infinito.
V seo Loop infinito mais adiante.
Na maior parte do tempo, a recursividade infinita far com que o programa seja executado
durante algum tempo e ento gere um erro RuntimeError: Maximum recursion depth
exceeded. Se isso acontecer, v seo Recursividade infinita mais adiante.
Se no estiver recebendo este erro, mas suspeita que h um problema com um mtodo ou funo
recursiva, voc ainda pode usar as tcnicas da seo Recursividade infinita.
Se nenhum desses passos funcionar, comece a testar outros loops e outras funes e mtodos
recursivos.
Se isso no funcionar, ento possvel que voc no entenda o fluxo de execuo do seu
programa. V seo Fluxo de execuo mais adiante.
Loop infinito
Se voc acha que h um loop infinito e talvez saiba qual loop est causando o problema, acrescente
uma instruo print no fim do loop que exiba os valores das variveis na condio e o valor da
condio.
Por exemplo:
211

while x > 0 and y < 0 :


# faz algo com x
# faz algo com y
print(x: , x)
print(y: , y)
print(condition: , (x > 0 and y < 0))
Agora, quando executar o programa, voc ver trs linhas de sada para cada vez que o programa
passar pelo loop. Na ltima vez que passar pelo loop, a condio deve ser False. Se o loop continuar,
voc poder ver os valores de x e y, e compreender porque no esto sendo atualizados corretamente.
Recursividade infinita
Na maioria das vezes, a recursividade infinita faz o programa rodar durante algum tempo e ento
produzir um erro de Maximum recursion depth exceeded.
Se suspeitar que uma funo est causando recursividade infinita, confira se h um caso-base. Deve
haver alguma condio que faz a funo retornar sem fazer uma invocao recursiva. Do contrrio,
voc ter que repensar o algoritmo e identificar um caso-base.
Se houver um caso-base, mas o programa no parece alcan-lo, acrescente uma instruo print no
incio da funo para exibir os parmetros. Agora, quando executar o programa, voc ver algumas
linhas de sada cada vez que a funo for invocada, e ver os valores dos parmetros. Se os parmetros
no estiverem se movendo em direo ao caso-base, voc ter algumas ideias sobre a razo disso.
Fluxo de execuo
Se no tiver certeza de como o fluxo de execuo est se movendo pelo seu programa, acrescente
instrues print ao comeo de cada funo com uma mensagem como entrada na funo foo, onde
foo o nome da funo.
Agora, quando executar o programa, ele exibir cada funo que for invocada.

A.2.3 - Quando executo o programa recebo uma exceo


Se algo der errado durante o tempo de execuo, o Python exibe uma mensagem que inclui o nome da
exceo, a linha do programa onde o problema ocorreu, e um traceback.
O traceback identifica a funo que est rodando atualmente, e a funo que a chamou, assim como a
funo que chamou esta, e assim por diante. Em outras palavras, ele traa a sequncia de chamadas de
funo que fez com que voc chegasse onde est, incluindo o nmero da linha no seu arquivo onde
cada chamada ocorreu.
212

O primeiro passo examinar o lugar no programa onde o erro ocorreu e ver se consegue compreender o
que aconteceu. Esses so alguns dos erros de tempo de execuo mais comuns:
NameError:
Voc est tentando usar uma varivel que no existe no ambiente atual. Confira se o nome est escrito
corretamente e de forma consistente. E lembre-se de que as variveis locais so locais; voc no pode
se referir a elas a partir do exterior da funo onde so definidas.
TypeError:
H vrias causas possveis:
Voc est tentando usar um valor de forma inadequada. Exemplo: indexar uma string, lista ou
tupla com algo diferente de um nmero inteiro.
No h correspondncia entre os itens em uma string de formatao e os itens passados para
converso. Isso pode acontecer se o nmero de itens no tiver correspondncia ou uma
converso invlida for chamada.
Voc est passando o nmero incorreto de argumentos a uma funo. Para mtodos, olhe para a
definio do mtodo e verifique se o primeiro parmetro self. Ento olhe para a invocao do
mtodo; confira se est invocando o mtodo a um objeto com o tipo correto e fornecendo os
outros argumentos corretamente.
KeyError:
Voc est tentando acessar um elemento de um dicionrio usando uma chave que o dicionrio no
contm. Se as chaves forem strings, lembre-se de que letras maisculas so diferentes de minsculas.
AttributeError:
Voc est tentando acessar um atributo ou mtodo que no existe. Verifique a ortografia! Voc pode
usar a funo integrada vars para listar os atributos que existem mesmo.
Se um AttributeError indicar que um objeto do tipo NoneType, fica subentendido que None. Ento
o problema no o nome do atributo, mas o objeto.
Pode ser que o objeto seja none porque voc se esqueceu de retornar um valor de uma funo; se
chegar ao fim de uma funo sem chegar a uma instruo return, ela retorna None. Outra causa comum
usar o resultado de um mtodo de lista, como sort, que retorne None.
IndexError:
O ndice que voc est usando para acessar uma lista, string ou tupla maior que o seu comprimento
menos um. Imediatamente antes do local do erro, acrescente uma instruo print para exibir o valor do
ndice e o comprimento do array. O array do tamanho certo? O ndice tem o valor certo?
213

O depurador do Python (pdb) til para rastrear excees porque permite examinar o estado do
programa imediatamente antes do erro. Voc pode ler sobre o pdb em
https://docs.python.org/3/library/pdb.html.

A.2.4 - Acrescentei tantas instrues print que fui inundado pelos resultados
Um dos problemas com a utilizao de instrues print para a depurao que voc pode terminar
enterrado pelos resultados. H duas formas de prosseguir: simplifique a sada ou simplifique o
programa.
Para simplificar a sada, voc pode retirar ou transformar as instrues print que no esto ajudando em
comentrios, ou combin-las, ou formatar a sada para que seja mais fcil de entender.
Para simplificar o programa, h vrias coisas que voc pode fazer. Em primeiro lugar, reduza o
problema no qual o programa est trabalhando. Por exemplo, se est fazendo uma busca em uma lista,
procure em uma lista pequena. Se o programa receber entradas do usurio, d a entrada mais simples
possvel que cause o problema.
Em segundo lugar, limpe o programa. Retire o cdigo morto e reorganize o programa para torn-lo o
mais fcil possvel de ler. Por exemplo, se voc suspeitar que o problema est em uma parte
profundamente aninhada do programa, tente reescrever aquela parte com uma estrutura mais simples.
Se suspeitar de uma funo grande, tente quebr-la em funes menores para test-las separadamente.
Muitas vezes, o prprio processo de encontrar o caso de teste mnimo leva voc ao problema. Se
descobrir que um programa funciona em uma situao, mas no em outra, isso d uma pista sobre o que
est acontecendo.
De forma similar, reescrever uma parte do cdigo pode ajudar a encontrar erros sutis. Se fizer uma
alterao que voc ache que no vai afetar o programa, mas que acabe afetando, isso pode ajud-lo.

A.3 - Erros semnticos


De algumas formas, os erros semnticos so os mais difceis de depurar, porque o interpretador no
fornece nenhuma informao sobre qual o problema. S voc sabe o que o programa deve fazer.
O primeiro passo fazer uma conexo entre o texto do programa e o comportamento que est vendo.
Voc precisa de uma hiptese sobre o que o programa est fazendo de fato. Uma das coisas que torna
isso difcil que os computadores so rpidos.
Pode ser que voc queira diminuir a velocidade do programa para ser equivalente humana; com
alguns depuradores possvel fazer isso. No entanto, o tempo que leva para inserir instrues print bem
colocadas muitas vezes curto em comparao ao da configurao do depurador, insero e remoo
de marcaes e colocao do compasso do programa onde o erro est ocorrendo.
214

A.3.1 - Meu programa no funciona


Voc deve se perguntar o seguinte:
H algo que o programa deveria fazer, mas que no parece acontecer? Encontre a seo do
cdigo que executa a funo em questo e confira se est sendo executada quando voc acha
que deveria.
Algo est acontecendo, mas no o que deveria? Encontre o cdigo no seu programa que executa
a funo em questo e veja se est sendo executada na hora errada.
Uma seo do cdigo est produzindo um efeito que no o esperado? Tenha certeza de que
entende o cdigo em questo, especialmente se envolver funes ou mtodos de outros mdulos
do Python. Leia a documentao das funes que chama. Teste-as escrevendo casos de teste
simples e verificando os resultados.
Para programar, preciso ter um modelo mental de como os programas funcionam. Se escrever um
programa que no faz o que espera, muitas vezes o problema no est no programa, est no seu modelo
mental.
A melhor forma de corrigir o seu modelo mental quebrar o programa nos seus componentes
(normalmente as funes e mtodos) e testar cada componente em separado. Uma vez que encontre a
discrepncia entre o seu modelo e a realidade, poder resolver o problema.
Naturalmente, voc deveria construir e testar componentes conforme desenvolva o programa. Assim, se
encontrar um problema, deve haver s uma pequena quantidade de cdigo novo que no sabe se est
correto.

A.3.2 - Tenho uma baita expresso cabeluda e ela no faz o que espero
Escrever expresses complexas timo enquanto so legveis, mas elas podem ser difceis de depurar.
Muitas vezes uma boa ideia quebrar uma expresso complexa em uma srie de atribuies a variveis
temporrias.
Por exemplo:
self.hands[i].addCard(self.hands[self.findNeighbor(i)].popCard())
A expresso pode ser reescrita assim:
neighbor = self.findNeighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard(pickedCard)
215

A verso explcita mais fcil de ler porque os nomes das variveis oferecem documentao adicional,
e mais fcil de depurar porque voc pode verificar os tipos das variveis intermedirias e exibir seus
valores.
Outro problema que pode ocorrer com grandes expresses que a ordem da avaliao pode no ser o

que voc espera. Por exemplo, se estiver traduzindo a expresso para o Python, poderia escrever:
y = x / 2 * math.pi
Isso no est correto porque a multiplicao e a diviso tm a mesma precedncia e so avaliadas da
esquerda para a direita. Ento, assim que essa expresso calculada: x/2.
Uma boa forma de depurar expresses acrescentar parnteses para tornar a ordem da avaliao
explcita:
y = x / (2 * math.pi)
Sempre que no tiver certeza sobre a ordem da avaliao, use parnteses. Alm de o programa ficar
correto (quanto execuo do que era pretendido), ele tambm ser mais legvel para outras pessoas
que no memorizaram a ordem de operaes.

A.3.3 - Tenho uma funo que no retorna o que espero


Se tiver uma instruo return com uma expresso complexa, no h possibilidade de exibir o resultado
antes do retorno. Novamente, voc pode usar uma varivel temporria. Por exemplo, em vez de:
return self.hands[i].removeMatches()
voc poderia escrever:
count = self.hands[i].removeMatches()
return count
Agora voc tem a oportunidade de exibir o valor de count antes do retorno.

A.3.4 - Estou perdido e preciso de ajuda


Em primeiro lugar, afaste-se do computador por alguns minutos. Computadores emitem ondas que
afetam o crebro, causando estes sintomas:
frustrao e raiva;

crenas supersticiosas (o computador me odeia) e pensamento mgico (o programa s


funciona quando uso o meu chapu virado para trs);
programao aleatria (a tentativa de programar escrevendo todos os programas possveis e
escolhendo aquele que faz a coisa certa).
216

Se estiver sofrendo algum desses sintomas, levante-se e d uma volta. Quando se acalmar, pense no
programa. O que ele est fazendo? Quais so algumas causas possveis para esse comportamento?
Quando foi a ltima vez que tinha um programa funcionando, e o que fez depois disso?
s vezes leva tempo para encontrar um erro. Com frequncia encontro erros quando estou longe do
computador e deixo a minha mente vagar. Os melhores lugares para encontrar erros so os trens, o
chuveiro e a cama, logo antes de adormecer.

A.3.5 - Srio, preciso mesmo de ajuda


Acontece. Mesmo os melhores programadores ocasionalmente empacam. Pode ocorrer de voc
trabalhar tanto em um programa que no consegue enxergar o erro. Precisa de outro par de olhos.
Antes de trazer mais algum, no se esquea de se preparar. Seu programa deve ser o mais simples
possvel, e deve estar funcionando com a menor entrada possvel que cause o erro. Deve ter instrues
print nos lugares adequados (e a sada que produzem deve ser compreensvel). Voc deve entender o
problema o suficiente para descrev-lo de forma concisa.
Ao trazer algum para ajudar, lembre-se de dar as informaes de que a pessoa possa precisar:
Se houver uma mensagem de erro, qual e que parte do programa indica?

Qual foi a ltima coisa que fez antes de este erro ocorrer? Quais foram as ltimas linhas de
cdigo que escreveu, ou qual o novo caso de teste que falhou?
O que tentou at agora e o que aprendeu?

Quando encontrar o erro, pense por um segundo no que poderia ter feito para encontr-lo mais rpido.
Na prxima vez em que vir algo similar, poder encontrar o erro mais rapidamente.
Lembre-se, a meta no s fazer o programa funcionar. A meta aprender como fazer o programa
funcionar.

Apndice B: Anlise de algoritmos


Este apndice um excerto editado de Think Complexity, por Allen B. Downey, tambm publicado
pela OReilly Media (2012). Depois de ler este livro aqui, pode ser uma boa ideia l-lo tambm.
Anlise de algoritmos um ramo da Cincia da Computao que estuda o desempenho de algoritmos,
especialmente suas exigncias de tempo de execuo e requisitos de espao. Veja
http://en.wikipedia.org/wiki/Analysis_of_algorithms.
A meta prtica da anlise de algoritmos prever o desempenho de algoritmos diferentes para guiar
decises de projeto.
217

Durante a campanha presidencial dos Estados Unidos de 2008, pediram ao candidato Barack Obama
para fazer uma entrevista de emprego improvisada quando visitou a Google. O diretor executivo, Eric
Schmidt, brincou, pedindo a ele a forma mais eficiente de classificar um milho de nmeros inteiros
de 32 bits. Aparentemente, Obama tinha sido alertado porque respondeu na hora: Creio que a
ordenao por bolha (bubble sort) no seria a escolha certa. Veja http://bit.ly/1MpIwTf.
Isso verdade: a ordenao por bolha conceitualmente simples, mas lenta para grandes conjuntos de
dados. A resposta que Schmidt procurava provavelmente ordenao radix (radix sort)
(http://en.wikipedia.org/wiki/Radix_sort)[2].
A meta da anlise de algoritmos fazer comparaes significativas entre algoritmos, mas h alguns
problemas:
O desempenho relativo dos algoritmos pode depender de caractersticas do hardware; ento um
algoritmo pode ser mais rpido na Mquina A, e outro na Mquina B. A soluo geral para este
problema especificar um modelo de mquina e analisar o nmero de passos ou operaes que
um algoritmo exige sob um modelo dado.
O desempenho relativo pode depender dos detalhes do conjunto de dados. Por exemplo, alguns
algoritmos de ordenao rodam mais rpido se os dados j foram parcialmente ordenados;
outros algoritmos rodam mais devagar neste caso. Uma forma comum de evitar este problema
analisar o pior caso. s vezes til analisar o desempenho de casos mdios, mas isso
normalmente mais difcil, e pode no ser bvio qual conjunto de casos deve ser usado para a
mdia.
O desempenho relativo tambm depende do tamanho do problema. Um algoritmo de ordenao
que rpido para pequenas listas pode ser lento para longas listas. A soluo habitual para este
problema expressar o tempo de execuo (ou o nmero de operaes) como uma funo do
tamanho de problema e funes de grupo em categorias que dependem de sua velocidade de
crescimento quando o tamanho de problema aumenta.
Uma coisa boa sobre este tipo de comparao que ela prpria para a classificao simples de
algoritmos. Por exemplo, se souber que o tempo de execuo do algoritmo A tende a ser proporcional
ao tamanho da entrada n, e o algoritmo B tende a ser proporcional a n2, ento espero que A seja mais
rpido que B, pelo menos para valores grandes de n.
Esse tipo de anlise tem algumas desvantagens, mas falaremos disso mais adiante.

B.1 - Ordem de crescimento


Vamos supor que voc analisou dois algoritmos e expressou seus tempos de execuo em relao ao
tamanho da entrada: o algoritmo A leva 100n+1 passos para resolver um problema com o tamanho n; o
algoritmo B leva n2 + n + 1 passos.
218

A tabela seguinte mostra o tempo de execuo desses algoritmos para tamanhos de problema
diferentes:

Tamanho da entrada Tempo de execuo do algoritmo A Tempo de execuo do algoritmo B


10 1 001 111
100 10 001 10 101
1 000 100 001 1 001 001
10 000 1 000 001 > 1010
Ao chegar em n=10, o algoritmo A parece bem ruim; ele quase dez vezes mais longo que o algoritmo
B. No entanto, para n=100 eles so bem parecidos, e, para valores maiores, A muito melhor.
A razo fundamental que para grandes valores de n, qualquer funo que contenha um termo n2 ser
mais rpida que uma funo cujo termo principal seja n. O termo principal o que tem o expoente mais
alto.
Para o algoritmo A, o termo principal tem um grande coeficiente, 100, que a razo de B ser melhor
que A para um valor pequeno de n. Entretanto, apesar dos coeficientes, sempre haver algum valor de n
em que an2 > bn, para valores de a e b.

O mesmo argumento se aplica aos termos que no so principais. Mesmo se o tempo de execuo do
algoritmo A fosse n+1000000, ainda seria melhor que o algoritmo B para um valor suficientemente
grande de n.
Em geral, esperamos que um algoritmo com um termo principal menor seja um algoritmo melhor para
grandes problemas, mas, para problemas menores, pode haver um ponto de desvio onde outro
algoritmo seja melhor. A posio do ponto de desvio depende dos detalhes dos algoritmos, das entradas
e do hardware; ento, ele normalmente ignorado para os propsitos da anlise algortmica. Porm,
isso no significa que voc pode se esquecer dele.
Se dois algoritmos tiverem o mesmo termo principal de ordem, difcil dizer qual melhor; mais uma
vez, a resposta depende dos detalhes. Assim, para a anlise algortmica, funes com o mesmo termo
principal so consideradas equivalentes, mesmo se tiverem coeficientes diferentes.
Uma ordem de crescimento um conjunto de funes cujo comportamento de crescimento
considerado equivalente. Por exemplo, 2n, 100n e n+1 pertencem mesma ordem de crescimento, que
se escreve O(n) em notao Grande-O e muitas vezes chamada de linear, porque cada funo no
conjunto cresce linearmente em relao a n.
Todas as funes com o termo principal n2 pertencem a O(n2); elas so chamadas de quadrticas.
219

A tabela seguinte mostra algumas ordens de crescimento mais comuns na anlise algortmica, em
ordem crescente de complexidade.

Ordem de crescimento Nome


O(1) constante
O(logb n) logartmica (para qualquer b)
O(n) linear
O(n logb n) log-linear
O(n2) quadrtica
O(n3) cbica
O(cn) exponencial (para qualquer c)
Para os termos logartmicos, a base do logaritmo no importa; a alterao de bases o equivalente da
multiplicao por uma constante, o que no altera a ordem de crescimento. De forma similar, todas as
funes exponenciais pertencem mesma ordem de crescimento, apesar da base do expoente. As
funes exponenciais crescem muito rapidamente, ento os algoritmos exponenciais s so teis para
pequenos problemas.

Exerccio B.1
Leia a pgina da Wikipdia sobre a notao Grande-O (Big-Oh notation) em
http://en.wikipedia.org/wiki/Big_O_notation e responda s seguintes perguntas:
1. Qual a ordem de crescimento de n3 + n2? E de 1000000n3 + n2? Ou de n3 + 1000000n2?
2. Qual a ordem de crescimento de (n2 + n) . (n + 1)? Antes de comear a multiplicar, lembre-se de
que voc s precisa do termo principal.
3. Se f est em O(g), para alguma funo no especificada g, o que podemos dizer de af+b?
4. Se f1 e f2 esto em O(g), o que podemos dizer a respeito de f1 + f2?
5. Se f1 est em O(g) e f2 est em O(h), o que podemos dizer a respeito de f1 + f2?
6. Se f1 est em O(g) e f2 O(h), o que podemos dizer a respeito de f1 . f2?
Programadores que se preocupam com o desempenho muitas vezes consideram esse tipo de anlise
difcil de engolir. A razo para isso : s vezes os coeficientes e os termos no principais fazem muita
diferena. Os detalhes do hardware, a linguagem de programao e as caractersticas da entrada fazem
grande diferena. E para pequenos problemas, o comportamento assinttico irrelevante.
Porm, se mantiver essas questes em mente, a anlise algortmica pode ser uma ferramenta til. Pelo
menos para grandes problemas, os melhores algoritmos so normalmente melhores, e, s vezes,
muito melhores. A diferena entre dois algoritmos com a mesma ordem de crescimento normalmente
um fator constante, mas a diferena entre um bom algoritmo e um algoritmo ruim ilimitada!
220

B.2 - Anlise de operaes bsicas do Python


No Python, a maior parte das operaes aritmticas tem um tempo constante; a multiplicao
normalmente leva mais tempo que a adio e a subtrao, e a diviso leva at mais tempo, mas esses
tempos de execuo no dependem da magnitude dos operandos. Os nmeros inteiros muito grandes
so uma exceo; nesse caso, o tempo de execuo aumenta com o nmero de dgitos.
Operaes de indexao ler ou escrever elementos em uma sequncia ou dicionrio tambm tm
tempo constante, no importa o tamanho da estrutura de dados.
Um loop for que atravesse uma sequncia ou dicionrio normalmente linear, desde que todas as
operaes no corpo do loop sejam de tempo constante. Por exemplo, somar os elementos de uma lista
linear:
total = 0
for x in t:
total += x
A funo integrada sum tambm linear porque faz a mesma coisa, mas tende a ser mais rpida porque
uma implementao mais eficiente; na linguagem da anlise algortmica, tem um coeficiente principal
menor.
Via de regra, se o corpo de um loop est em O(na), ento o loop inteiro est em O(na + 1). A exceo
se voc puder mostrar que o loop encerra depois de um nmero constante de iteraes. Se um loop
executado k vezes, no importa o valor de n, ento o loop est em O(na), mesmo para valores grandes
de k.
A multiplicao por k no altera a ordem de crescimento, nem a diviso. Ento, se o corpo de um loop
est em O(na) e executado n/k vezes, o loop est em O(na + 1), mesmo para valores grandes de k.
A maior parte das operaes de strings e tuplas so lineares, exceto a indexao e len, que so de tempo
constante. As funes integradas min e max so lineares. O tempo de execuo de uma operao de
fatia proporcional ao comprimento da sada, mas no depende do tamanho da entrada.
A concatenao de strings linear; o tempo de execuo depende da soma dos comprimentos dos
operandos.
Todos os mtodos de string so lineares, mas se os comprimentos das strings forem limitados por uma
constante por exemplo, operaes em caracteres nicos so consideradas de tempo constante. O
mtodo de string join linear; o tempo de execuo depende do comprimento total das strings.
A maior parte dos mtodos de lista so lineares, mas h algumas excees:
221

A soma de um elemento ao fim de uma lista de tempo constante em mdia; quando o espao
acaba, ela ocasionalmente copiada a uma posio maior, mas o tempo total de operaes n
O(n), portanto o tempo mdio de cada operao O(1).
A remoo de um elemento do fim de uma lista de tempo constante.

A ordenao O(n log n).

A maior parte das operaes e mtodos de dicionrio so de tempo constante, mas h algumas
excees:
O tempo de execuo de update proporcional ao tamanho do dicionrio passado como
parmetro, no o dicionrio que est sendo atualizado.
keys, values e items so de tempo constante porque retornam iteradores. Porm, se fizer um
loop pelos iteradores, o loop ser linear.
O desempenho de dicionrios um dos milagres menores da cincia da computao. Vemos como
funcionam em Hashtables, na pgina 302.

Exerccio B.2
Leia a pgina da Wikipdia sobre algoritmos de ordenao em
http://en.wikipedia.org/wiki/Sorting_algorithm e responda s seguintes perguntas:
1. O que um tipo de comparao? Qual a melhor opo nos casos de pior cenrio de ordem de
crescimento para um tipo de comparao? Qual a melhor opo nos casos de pior cenrio de ordem
de crescimento para qualquer algoritmo de ordenao?
2. Qual a ordem de crescimento do tipo bolha, e por que Barack Obama acha que no a escolha
certa?
3. Qual a ordem de crescimento do tipo radix? Quais so as precondies necessrias para us-la?
4. O que um tipo estvel e qual sua importncia na prtica?
5. Qual o pior algoritmo de ordenao (que tenha um nome)?
6. Que algoritmo de ordenao a biblioteca C usa? Que algoritmo de ordenao o Python usa? Esses
algoritmos so estveis? Voc pode ter que pesquisar no Google para encontrar essas respostas.
1. Muitos dos tipos de no comparao so lineares, ento, por que o Python usa um tipo de
comparao O(n log n)?
222

B.3 - Anlise de algoritmos de busca


Uma busca um algoritmo que recebe uma coleo e um item de objetivo e determina se o objetivo
est na coleo, muitas vezes retornando o ndice do objetivo.
O algoritmo de busca mais simples uma busca linear, que atravessa os itens da coleo em ordem,
parando se encontrar o objetivo. No pior caso, ele tem que atravessar a coleo inteira, ento o tempo
de execuo linear.
O operador in para sequncias usa uma busca linear; assim como mtodos de string como find e count.
Se os elementos da sequncia estiverem em ordem, voc pode usar uma busca por bisseo, que
O(log n). A busca por bisseo semelhante ao algoritmo que voc poderia usar para procurar uma
palavra em um dicionrio (um dicionrio de papel, no a estrutura de dados). Em vez de comear no
incio e verificar cada item em ordem, voc comea com o item do meio e verifica se a palavra que est
procurando vem antes ou depois. Se vier antes, ento procura na primeira metade da sequncia. Se no,
procura na segunda metade. Seja como for, voc corta o nmero de itens restantes pela metade.
Se a sequncia tiver um milho de itens, sero necessrios cerca de 20 passos para encontrar a palavra
ou concluir que no est l. Ento aproximadamente 50 mil vezes mais rpido que uma busca linear.
A busca por bisseo pode ser muito mais rpida que a busca linear, mas preciso que a sequncia
esteja em ordem, o que pode exigir trabalho extra.
H outra estrutura de dados chamada hashtable, que at mais rpida voc pode fazer uma busca em
tempo constante e ela no exige que os itens estejam ordenados. Os dicionrios do Python so
implementados usando hashtables e por isso a maior parte das operaes de dicionrio, incluindo o
operador in, so de tempo constante.

B.4 - Hashtables
Para explicar como hashtables funcionam e por que o seu desempenho to bom, comeo com uma
implementao simples de um mapa e vou melhor-lo gradualmente at que seja uma hashtable.
Uso o Python para demonstrar essas implementaes, mas, na vida real, eu no escreveria um cdigo
como esse no Python; bastaria usar um dicionrio! Assim, para o resto deste captulo, voc tem que
supor que os dicionrios no existem e que quer implementar uma estrutura de dados que faa o mapa
de chaves a valores. As operaes que precisa implementar so:
add(k, v):
Adiciona um novo item que mapeia a chave k ao valor v. Com um dicionrio de Python, d, essa
operao escrita d[k] = v.
get(k):
223

Procura e retorna o valor que corresponde chave k. Com um dicionrio de Python, d, esta operao
escrita d [k] ou d.get(k).
Por enquanto, vou supor que cada chave s aparea uma vez. A implementao mais simples desta
interface usa uma lista de tuplas, onde cada tupla um par chave-valor:
class LinearMap:
def __init__(self):
self.items = []
def add(self, k, v):
self.items.append((k, v))
def get(self, k):
for key, val in self.items:
if key == k:
return val
raise KeyError
add acrescenta uma tupla chave-valor lista de itens, o que tem tempo constante.
get usa um loop for para buscar na lista: se encontrar a chave-alvo, retorna o valor correspondente; do
contrrio, exibe um KeyError. Ento get linear.
Uma alternativa manter uma lista ordenada por chaves. Assim, get poderia usar uma busca por
bisseo, que O(log n). Porm, inserir um novo item no meio de uma lista linear, ento isso pode
no ser a melhor opo. H outras estruturas de dados que podem implementar add e get em tempo
logartmico, mas isso no to bom como tempo constante, ento vamos continuar.
Uma forma de melhorar LinearMap quebrar a lista de pares chave-valor em listas menores. Aqui est
uma implementao chamada BetterMap, que uma lista de cem LinearMaps. Como veremos em um
segundo, a ordem de crescimento para get ainda linear, mas BetterMap um passo no caminho em
direo a hashtables:
class BetterMap:
def __init__(self, n=100):
self.maps = []
for i in range(n):
self.maps.append(LinearMap())
224

def find_map(self, k):


index = hash(k) % len(self.maps)
return self.maps[index]
def add(self, k, v):
m = self.find_map(k)
m.add(k, v)
def get(self, k):
m = self.find_map(k)
return m.get(k)
__init__ cria uma lista de n LinearMaps.
find_map usada por add e get para saber em qual mapa o novo item deve ir ou em qual mapa fazer a
busca.
find_map usa a funo integrada hash, que recebe quase qualquer objeto do Python e retorna um
nmero inteiro. Uma limitao desta implementao que ela s funciona com chaves hashable. Tipos
mutveis como listas e dicionrios no so hashable.
Objetos hashable considerados equivalentes retornam o mesmo valor hash, mas o oposto no
necessariamente verdade: dois objetos com valores diferentes podem retornar o mesmo valor hash.
find_map usa o operador mdulo para manter os valores hash no intervalo de 0 a len(self.maps), ento
o resultado um ndice legal na lista. Naturalmente, isso significa que muitos valores hash diferentes
sero reunidos no mesmo ndice. Entretanto, se a funo hash dispersar as coisas de forma consistente
(que o que as funes hash foram projetadas para fazer), ento esperamos ter n/100 itens por
LinearMap.
Como o tempo de execuo de LinearMap.get proporcional ao nmero de itens, esperamos que
BetterMap seja aproximadamente cem vezes mais rpido que LinearMap. A ordem de crescimento
ainda linear, mas o coeficiente principal menor. Isto bom, mas no to bom quanto uma hashtable.
Aqui (finalmente) est a ideia crucial que faz hashtables serem rpidas: se puder limitar o comprimento
mximo de LinearMaps, LinearMap.get de tempo constante. Tudo o que voc precisa fazer rastrear
o nmero de itens e quando o nmero de itens por LinearMap exceder o limite, alterar o tamanho da
hashtable acrescentando LinearMaps.
Aqui est uma implementao de uma hashtable:
class HashMap:
225

def __init__(self):
self.maps = BetterMap(2)
self.num = 0
def get(self, k):
return self.maps.get(k)
def add(self, k, v):
if self.num == len(self.maps.maps):
self.resize()
self.maps.add(k, v)
self.num += 1
def resize(self):
new_maps = BetterMap(self.num * 2)
for m in self.maps.maps:
for k, v in m.items:
new_maps.add(k, v)
self.maps = new_maps
Cada HashMap contm um BetterMap; __init__ inicia com apenas dois LinearMaps e inicializa num,
que monitora o nmero de itens.
get apenas despacha para BetterMap. O verdadeiro trabalho acontece em add, que verifica o nmero de
itens e o tamanho de BetterMap: se forem iguais, o nmero mdio de itens por LinearMap um, ento
resize chamada.
resize faz um novo BetterMap duas vezes maior que o anterior, e ento redispersa os itens do mapa
antigo no novo.
A redisperso necessria porque alterar o nmero de LinearMaps muda o denominador do operador
mdulo em find_map. Isso significa que alguns objetos que costumavam ser dispersos no mesmo
LinearMap sero separados (que o que queramos, certo?).
A redisperso linear, ento resize linear, o que pode parecer ruim, j que prometi que add seria de
tempo constante. Entretanto, lembre-se de que no temos que alterar o tamanho a cada vez, ento add
normalmente de tempo constante e s ocasionalmente linear. O volume total de trabalho para executar
add n vezes proporcional a n, ento o tempo mdio de cada add de tempo constante!
226

Para ver como isso funciona, pense em comear com uma HashTable vazia e adicione uma sequncia
de itens. Comeamos com dois LinearMaps, ento as duas primeiras adies so rpidas (no
necessrio alterar o tamanho). Digamos que elas tomem uma unidade do trabalho cada uma. A prxima
adio exige uma alterao de tamanho, ento temos de redispersar os dois primeiros itens (vamos
chamar isso de mais duas unidades de trabalho) e ento acrescentar o terceiro item (mais uma unidade).
Acrescentar o prximo item custa uma unidade, ento o total, por enquanto, de seis unidades de
trabalho para quatro itens.
O prximo add custa cinco unidades, mas os trs seguintes so s uma unidade cada um, ento o total
de 14 unidades para as primeiras oito adies.
O prximo add custa nove unidades, mas ento podemos adicionar mais sete antes da prxima
alterao de tamanho, ento o total de 30 unidades para as primeiras 16 adies.
Depois de 32 adies, o custo total de 62 unidades, e espero que voc esteja comeando a ver um
padro. Depois de n adies, nas quais n uma potncia de dois, o custo total de 2n-2 unidades, ento
o trabalho mdio por adio um pouco menos de duas unidades. Quando n uma potncia de dois,
esse o melhor caso; para outros valores de n, o trabalho mdio um pouco maior, mas isso no
importante. O importante que seja O(1).
A Figura 21.1 mostra graficamente como isso funciona. Cada bloco representa uma unidade de
trabalho. As colunas mostram o trabalho total para cada adio na ordem da esquerda para a direita: os
primeiros dois adds custam uma unidade, o terceiro custa trs unidades etc.
Figura 21.1 O custo de adies em uma hashtable.
O trabalho extra de redisperso aparece como uma sequncia de torres cada vez mais altas com um
aumento de espao entre elas. Agora, se derrubar as torres, espalhando o custo de alterar o tamanho por
todas as adies, poder ver graficamente que o custo total depois de n adies de 2n 2.
Uma caracterstica importante deste algoritmo que quando alteramos o tamanho da HashTable, ela
cresce geometricamente; isto , multiplicamos o tamanho por uma constante. Se voc aumentar o
tamanho aritmeticamente somando um nmero fixo de cada vez o tempo mdio por add linear.
Voc pode baixar minha implementao de HashMap em http://thinkpython2.com/code/Map.py, mas
lembre-se de que no h razo para us-la; se quiser um mapa, basta usar um dicionrio do Python.

B.5 - Glossrio
anlise de algoritmos
Forma de comparar algoritmos quanto s suas exigncias de espao e/ou tempo de execuo.
modelo de mquina
Representao simplificada de um computador usada para descrever algoritmos.
227

pior caso
Entrada que faz um dado algoritmo rodar mais lentamente (ou exigir mais espao).
termo principal
Em um polinmio, o termo com o expoente mais alto.
ponto de desvio
Tamanho do problema em que dois algoritmos exigem o mesmo tempo de execuo ou espao.
ordem de crescimento
Conjunto de funes em que todas crescem em uma forma considerada equivalente para os propsitos
da anlise de algoritmos. Por exemplo, todas as funes que crescem linearmente pertencem mesma
ordem de crescimento.
notao Grande-O (Big-Oh notation)
Notao para representar uma ordem de crescimento; por exemplo, O(n) representa o conjunto de
funes que crescem linearmente.
linear
Algoritmo cujo tempo de execuo proporcional ao tamanho do problema, pelo menos para grandes
tamanhos de problema.
quadrtico
Algoritmo cujo tempo de execuo proporcional a n2, onde n uma medida de tamanho do problema.
busca
Problema de localizar um elemento de uma coleo (como uma lista ou dicionrio) ou de decidir que
no est presente.
hashtable
Estrutura de dados que representa uma coleo de pares chave-valor e executa buscas em tempo
constante.

B.6 - Sobre o autor


Allen Downey professor de Cincia da Computao no Olin College of Engineering. Ele j ensinou
no Wellesley College, Colby College e na U.C. Berkeley. doutor em Cincia da Computao pela
U.C. Berkeley e mestre e graduado pelo MIT.
Colofo
O animal na capa de Pense em Python um papagaio-da-carolina, tambm conhecido como periquito-
da-carolina (Conuropsis carolinensis). Este papagaio habitava o sudeste dos Estados Unidos e foi o
nico papagaio continental a habitar regies acima do norte do Mxico. Um dia, vivia no norte, em
228

locais to distantes quanto Nova Iorque e os Grandes Lagos, embora fosse encontrado principalmente
na regio da Flrida s Carolinas.
O papagaio-da-carolina era quase todo verde com a cabea amarela e, na maturidade, tinha uma
colorao laranja na testa e na face. Seu tamanho mdio variava de 31 a 33 cm. Tinha uma voz alta,
ruidosa e palrava constantemente enquanto se alimentava. Habitava troncos de rvores ocos perto de
brejos e barrancos. O papagaio-da-carolina era um animal muito gregrio, que vivia em pequenos
grupos os quais podiam chegar a vrias centenas quando se alimentavam.
Infelizmente, essas reas de alimentao muitas vezes eram as plantaes de agricultores, que
disparavam nos pssaros para mant-los longe das plantas. A natureza social dos pssaros fazia com
que eles voassem ao resgate de qualquer papagaio ferido, permitindo aos agricultores derrubar bandos
inteiros de cada vez. Alm disso, suas penas eram usadas para embelezar chapus de senhoras, e alguns
papagaios eram mantidos como mascotes. Uma combinao desses fatores levou o papagaio-da-
carolina a tornar-se raro no fim dos anos 1800, e as doenas de aves domsticas podem ter contribudo
para diminuir seu nmero. Pelos anos 1920, a espcie estava extinta.
Hoje, h mais de 700 espcimes de papagaios-da-carolina conservados em museus no mundo inteiro.
Muitos dos animais nas capas de livros da OReilly esto em perigo de extino; todos eles so
importantes para o mundo. Para saber mais sobre como voc pode ajudar, acesse animals.oreilly.com. A
imagem da capa do livro Johnsons Natural History.
[1] popen foi descartado, ou seja, devemos parar de us-lo e comear a usar o mdulo subprocess.
Entretanto, para casos simples, eu considero subprocess mais complicado que o necessrio. Ento vou
continuar usando popen at que o removam.
[2] Mas se fizerem uma pergunta como essa em uma entrevista, creio que a melhor resposta A forma
mais rpida de classificar um milho de nmeros inteiros usar qualquer funo de ordenao
oferecida pela linguagem que estou usando. Se o desempenho bom o suficiente para a grande maioria
das aplicaes, mas a minha aplicao acabasse sendo lenta demais, eu usaria algum recurso para
investigar onde o tempo est sendo gasto. Se parecesse que um algoritmo mais rpido teria um efeito
significativo sobre o desempenho, ento procuraria uma boa implementao do tipo radix.

Crditos da edio brasileira


Editor: Rubens Prates
Traduo: Sheila Gomes
Reviso Gramatical: Smirna Cavalheiro
Editorao Eletrnica: Carolina Kuwabata
Assistente Editorial: Priscila A. Yoshimatsu
Este pdf: LR
229

Histrico
Allen Downey publicou o original em ingls sob uma licena Creative Commons Atribuio-
NoComercial CC BY-NC 3.0. A Novatec oferece comunidade esta traduo nos termos da mesma
licena do original, CC BY-NC 3.0, atendendo ao desejo do autor oferecer seu trabalho atravs de uma
licena livre.
Esta verso digital de Pense em Python em portugus foi gerada por Luciano Ramalho da
ThoughtWorks a partir de arquivos cedidos por Rubens Prates da Editora Novatec.
PensePython2e is maintained by PenseAllen. This page was generated by GitHub Pages.