Capítulo 4: Condicionais e recursividade

4.1 O operador módulo

O operador módulo trabalha com inteiros (e expressões que têm inteiros como resultado) e produz o resto da divisão do primeiro pelo segundo. Em Python, o operador módulo é um símbolo de porcentagem (%). A sintaxe é a mesma que a de outros operadores:

>>> quociente = 7 / 3
>>> print quociente
2
>>> resto = 7 % 3
>>> print resto
1

Então, 7 dividido por 3 é 2 e o resto é 1.

O operador módulo se revela surpreendentemente útil. Por exemplo, você pode checar se um número é divisível por outro - se x % y dá zero, então x é divisível por y.

Você também pode extrair o algarismo ou algarismos mais à direita de um número. Por exemplo, x % 10 resulta o algarismo mais à direita de x (na base 10). Similarmente, x % 100 resulta nos dois dígitos mais à direita.

4.2 Expressões booleanas

Uma expressão booleana é uma expressão que é verdadeira (True) ou é falsa (False). Em Python, uma expressão que é verdadeira tem o valor 1, e uma expressão que é falsa tem o valor 0.

O operador == compara dois valores e produz uma expressão booleana:

>>> 5 == 5
True
>>> 5 == 6
False

No primeiro comando, os dois operadores são iguais, então a expressão avalia como True (verdadeiro); no segundo comando, 5 não é igual a 6, então temos False (falso).

O operador == é um dos operadores de comparação; os outros são:

x != y        # x é diferente de 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 esses operadores provavelmente sejam familiares a você, os símbolos em Python são diferentes dos símbolos da matemática. Um erro comum é usar um sinal de igual sozinho (=) em vez de um duplo (==). Lembre-se de que = é um operador de atribuição e == é um operador de comparação. Também não existem coisas como =< ou =>.

4.3 Operadores lógicos

Existem três operadores lógicos: and, or, not (e, ou, não). A semântica (significado) destes operadores é similar aos seus significados em inglês (ou português). Por exemplo, x > 0 and x < 10 é verdadeiro somente se x for maior que 0 e menor que 10.

n%2 == 0 or n%3 == 0 é verdadeiro se qualquer das condições for verdadeira, quer dizer, se o número n for divisível por 2 ou por 3.

Finalmente, o operador lógico not nega uma expressão booleana, assim, not(x > y) é verdadeiro se (x > y) for falso, quer dizer, se x for menor ou igual a y.

A rigor, os operandos de operadores lógicos deveriam ser expressões booleanas, mas Python não é muito rigoroso. Qualquer número diferente de zero é interpretado como verdadeiro (True):

>>> x = 5
>>> x and 1
1
>>> y = 0
>>> y and 1
0

Em geral, esse tipo de coisa não é considerado de bom estilo. Se você precisa comparar um valor com zero, deve fazê-lo explicitamente.

4.4 Execução condicional

Para poder escrever programas úteis, quase sempre precisamos da habilidade de checar condições e mudar o comportamento do programa de acordo com elas. As instruções condicionais nos dão essa habilidade. A forma mais simples é a instrução if (se):

if x > 0
  print "x é positivo"

A expressão booleana depois da instrução if é chamada de condição. Se ela é verdadeira (true), então a instrução endentada é executada. Se não, nada acontece.

Assim como outras instruções compostas, a instrução if é constituída de um cabeçalho e de um bloco de instruções:

CABECALHO:
  PRIMEIRO COMANDO
  ...
  ULTIMO COMANDO

O cabeçalho começa com uma nova linha e termina com dois pontos (:). Os comandos ou instruções endentados que seguem são chamados de bloco. A primeira instrução não endentada marca o fim do bloco. Um bloco de comandos dentro de um comando composto ou instrução composta é chamado de corpo do comando.

Não existe limite para o número de instruções que podem aparecer no corpo de uma instrução if, mas tem que haver pelo menos uma. Ocasionalmente, é útil ter um corpo sem nenhuma instrução (usualmente, como um delimitador de espaço para código que você ainda não escreveu). Nesse caso, você pode usar o comando pass, que indica ao Python: “passe por aqui sem fazer nada”.

4.5 Execução alternativa

Um segundo formato da instrução if é a execução alternativa, na qual existem duas possibilidades e a condição determina qual delas será executada. A sintaxe se parece com:

if x % 2 == 0:
  print x, "é par"
else:
  print x, "é impar"

Se o resto da divisão de x por 2 for 0, então sabemos que x é par, e o programa exibe a mensagem para esta condição. Se a condição é falsa, o segundo grupo de instruções é executado. Desde que a condição deva ser verdadeira (True) ou falsa (False), precisamente uma das alternativas vai ser executada. As alternativas são chamadas ramos (branches), porque existem ramificações no fluxo de execução.

Por final, se você precisa checar a paridade de números com frequência, pode colocar este código dentro de uma função:

def imprimeParidade(x):
  if x % 2 == 0:
    print x, "é par"
  else:
    print x, "é impar"

Para qualquer valor de x, imprimeParidade exibe uma mensagem apropriada. Quando você a chama, pode fornecer uma expressão de resultado inteiro como um argumento:

>>> imprimeParidade(17)
>>> imprimeParidade(y+1)

4.6 Condicionais encadeados

Às vezes existem mais de duas possibilidades e precisamos de mais que dois ramos. Uma condicional encadeada é uma maneira de expressar uma operação dessas:

if x < y:
  print x, "é menor que", y
elif x > y:
  print x, "é maior que", y
else:
  print x, "e", y, "são iguais"

elif é uma abreviação de “else if” (“senão se”). De novo, precisamente um ramo será executado. Não existe limite para o número de instruções elif, mas se existir uma instrução else ela tem que vir por último:

if escolha == 'A':
  funcaoA()
elif escolha == 'B':
  funcaoB()
elif escolha == 'C':
  funcaoC()
else:
  print "Escolha inválida."

Cada condição é checada na ordem. Se a primeira é falsa, a próxima é checada, e assim por diante. Se uma delas é verdadeira, o ramo correspondente é executado, e a instrução termina. Mesmo que mais de uma condição seja verdadeira, apenas o primeiro ramo verdadeiro executa.

Como exercício, coloque os exemplos acima em funções chamadas comparar(x, y) e executar(escolha).

4.7 Condicionais aninhados

Um condicional também pode ser aninhado dentro de outra. Poderíamos ter escrito o exemplo tricotômico (dividido em três) como segue:

if x == y:
  print x, "e", y, "são iguais"
else:
  if x < y:
    print x, "é menor que", y
  else:
    print x, "é maior que", y

O condicional mais externo tem dois ramos. O primeiro ramo contém uma única instrução de saída. O segundo ramo contém outra instrução if, que por sua vez tem dois ramos. Os dois ramos são ambos instruções de saída, embora pudessem conter instruções condicionais também.

Embora a endentação das instruções torne a estrutura aparente, condicionais aninhados tornam-se difíceis de ler rapidamente. Em geral, é uma boa ideia evitar o aninhamento quando for possível.

Operadores lógicos frequentemente fornecem uma maneira de simplificar instruções condicionais aninhadas. Por exemplo, podemos reescrever o código a seguir usando uma única condicional:

if 0 < x:
  if x < 10:
    print "x é um número positivo de um só algarismo."

A instrução print é executada somente se a fizermos passar por ambos os condicionais, então, podemos usar um operador and:

if 0 < x and x < 10:
  print "x é um número positivo de um só algarismo."

Esses tipos de condições são comuns, assim, Python provê uma sintaxe alternativa que é similar à notação matemática:

if 0 < x < 10:
  print "x é um número positivo de um só algarismo."

4.8 A instrução return

O comando return permite terminar a execução de uma função antes que ela alcance seu fim. Uma razão para usá-lo é se você detectar uma condição de erro:

import math

def imprimeLogaritmo(x):
  if x <= 0:
    print "Somente números positivos, por favor."
    return

  resultado = math.log(x)
  print "O log de x é ", resultado

A função imprimeLogaritmo recebe um parâmetro de nome x. A primeira coisa que ela faz é checar se x é menor ou igual a 0, neste caso ela exibe uma mensagem de erro e então usa return para sair da função. O fluxo de execução imediatamente retorna ao ponto chamador, quer dizer, de onde a função foi chamada, e as linhas restantes da função não são executadas.

Lembre-se que para usar uma função do módulo de matemática, math, você tem de importá-lo.

4.9 Recursividade

Já mencionamos que é válido uma função chamar outra função, e você viu vários exemplos disso. Mas ainda não tínhamos dito que também é válido uma função chamar a si mesma. Talvez não seja óbvio porque isso é bom, mas trata-se de uma das coisas mais mágicas e interessantes que um programa pode fazer. Por exemplo, dê uma olhada na seguinte função:

def contagemRegressiva(n):
  if n == 0:
    print "Fogo!"
  else:
    print n
    contagemRegressiva(n-1)

contagemRegressiva espera que o parâmetro, n, seja um inteiro positivo. Se n for 0, ela produz como saída a palavra “Fogo!”. De outro modo, ela produz como saída n e então chama uma função de nome contagemRegressiva – ela mesma – passando n-1 como argumento.

O que acontece se chamarmos essa função da seguinte maneira:

>>> contagemRegressiva(3)

A execução de contagemRegressiva começa com n=3, e desde que n não é 0, produz como saída o valor 3, e então chama a si mesma...

A execução de contagemRegressiva começa com n=2, e desde que n não é 0, produz como saída o valor 2, e então chama a si mesma...

A execução de contagemRegressiva começa com n=1, e desde que n não é 0, produz como saída o valor 1, e então chama a si mesma...

A execução de contagemRegressiva começa com n=0, e desde que n é 0, produz como saída a palavra “Fogo!” e então retorna.

A contagemRegressiva que tem n=1 retorna.

A contagemRegressiva que tem n=2 retorna.

A contagemRegressiva que tem n=3 retorna.

E então estamos de volta em __main__ (que viagem!). Assim, a saída completa se parece com:

3
2
1
Fogo!

Como um segundo exemplo, dê uma olhada novamente nas funções novaLinha e tresLinhas:

def novaLinha():
  print

def tresLinhas():
  novaLinha()
  novaLinha()
  novaLinha()

Muito embora isso funcione, não seria muito útil se precisássemos gerar como saída 2 novas linhas, ou 106. Uma alternativa melhor seria esta:

def nLinhas(n):
  if n > 0:
    print
    nLinhas(n-1)

Esse programa é similar a contagemRegressiva. Sempre que n for maior que 0, ele gera como saída uma nova linha e então chama a si mesmo para gerar como saída n-1 linhas adicionais. Deste modo, o número total de novas linhas é 1 + (n-1) que, se você estudou álgebra direitinho, vem a ser o próprio n.

O processo de uma função chamando a si mesma é chamado de recursividade, e tais funções são ditas recursivas.

4.10 Diagramas de pilha para funções recursivas

Na Seção 3.11, usamos um diagrama de pilha para representar o estado de um programa durante uma chamada de função. O mesmo tipo de diagrama pode ajudar a interpretar uma função recursiva.

Toda vez que uma função é chamada, Python cria um novo quadro (frame) para a função, que contém as variáveis locais e parâmetros da função. Para uma função recursiva, terá que existir mais de um quadro na pilha ao mesmo tempo.

Esta figura mostra um diagrama de pilha para contagemRegressiva, chamada com n = 3:

_images/04_01_pilha2.png

Como de costume, no topo da pilha está o quadro para __main__. Ele está vazio porque nem criamos qualquer variável em __main__ nem passamos qualquer valor para ele.

Os quatro quadros contagemRegressiva têm valores diferentes para o parâmetro n. A parte mais em baixo na pilha, onde n=0, é chamada de caso base. Ele não faz uma chamada recursiva, então não há mais quadros.

Como exercício, desenhe um diagrama de pilha para nLinhas chamada com n=4.

4.11 Recursividade infinita

Se uma recursividade nunca chega ao caso base, ela prossegue fazendo chamadas recursivas para sempre, e o programa nunca termina. Isto é conhecido como recursividade infinita, e geralmente não é considerada uma boa ideia. Aqui está um programa mínimo com uma recursividade infinita:

def recursiva():
  recursiva()

Na maioria dos ambientes de programação, um programa com recursividade infinita na verdade não roda para sempre. Python reporta uma mensagem de erro quando a profundidade máxima de recursividade é alcançada:

File "<stdin>", line 2, in recursiva
(98 repetitions omitted)
File "<stdin>", line 2, in recursiva
RuntimeError: Maximum recursion depth exceeded

Este traceback é um pouco maior do que aquele que vimos no capítulo anterior. Quando o erro ocorre, existem 100 quadros recursiva na pilha!

Como exercício, escreva uma função com recursividade infinita e rode-a no interpretador Python.

4.12 Entrada pelo teclado

Os programas que temos escrito até agora são um pouco crus, no sentido de não aceitarem dados entrados pelo usuário. Eles simplesmente fazem a mesma coisa todas as vezes.

Python fornece funções nativas que pegam entradas pelo teclado. A mais simples é chamada raw_input. Quando esta função é chamada, o programa pára e espera que o usuário digite alguma coisa. Quando o usuário aperta a tecla Enter ou Return, o programa prossegue e a função raw_input retorna o que o usuário digitou como uma string:

>>> entrada = raw_input()
O que você está esperando?
>>> print entrada
O que você está esperando?

Antes de chamar raw_input, é uma boa ideia exibir uma mensagem dizendo ao usuário o que ele deve entrar. Esta mensagem é uma como se fosse uma pergunta (prompt). Esta pergunta pode ser enviada como um argumento para raw_input:

>>> nome = raw_input("Qual... é o seu nome? ")
Qual... é o seu nome? Arthur, Rei dos Bretões!
>>> print nome
Arthur, Rei dos Bretões!

Se esperamos que a entrada seja um inteiro, podemos usar a função input:

pergunta = "Qual... é a velocidade de vôo de uma andorinha?\n"
velocidade = input(pergunta)

Se o usuário digita uma string de números, ela é convertida para um inteiro e atribuída a velocidade. Infelizmente, se o usuário digitar um caractere que não seja um número, o programa trava:

>>> velocidade = input(pergunta)
Qual... é a velocidade de vôo de uma andorinha?
De qual você fala, uma andorinha Africana ou uma Europeia?
SyntaxError: invalid syntax

Para evitar esse tipo de erro, geralmente é bom usar raw_input para pegar uma string e, então, usar funções de conversão para converter para outros tipos.

4.13 Glossário

aninhamento (nesting)
Estrutura de programa dentro da outra, como um comando condicional dentro de um bloco de outro comando condicional.
bloco (block)
Grupo de comandos consecutivos com a mesma endentação.
caso base (base case)
Bloco de comando condicional numa função recursiva que não resulta em uma chamada recursiva.
comando composto (compound statement)
Comando que consiste de um cabeçalho e um corpo. O cabeçalho termina com um dois-pontos (:). O corpo é endentado em relação ao cabeçalho.
comando condicional (conditional statement)
Comando que controla o fluxo de execução dependendo de alguma condição.
condição (condition)
A expressão booleana que determina qual bloco será executado num comando condicional.
corpo (body)
O bloco que se segue ao cabeçalho em um comando composto.
expressão booleana (boolean expression)
Uma expressão que é verdadeira ou falsa.
operador de comparação (comparison operator)
Um dos operadores que compara dois valores: ==, !=, >, <, >=, e <=.
operador lógico (logical operator)
Um dos operadores que combina expressões booleanas: and, or, e not.
operador módulo (modulus operator)
Operador denotado por um símbolo de porcentagem (%), que trabalha com inteiros e retorna o resto da divisão de um número por outro.
prompt
Indicação visual que diz ao usuário que o programa está esperando uma entrada de dados.
recursividade (recursion)
O processo de chamar a própria função que está sendo executada.
recursividade infinita (infinite recursion)
Função que chama a si mesma recursivamente sem nunca chegar ao caso base. Após algum tempo, uma recursividade infinita causa um erro de execução.