Estruturas elementares em Python

Terça, 31 de março de 2020

Instruções: Enquanto você lê, você deve acompanhar os exemplos utilizando um console Python. Experimente escrever outras instruções análogas e vá além do que está escrito. Após a leitura do conteúdo abaixo, você deve ler os capítulos 1, 2 e o capítulo 3 até 3.1.2 do tutorial Python.


Estruturas elementares em Python

Suponha que precisamos somar dois números de vários algarismos, digamos, 124682 e 2468. Esses números são pequenos, então você pode facilmente escrever os dois números no papel, alinhados pelo último dígito, e executar um algoritmo de soma tradicional.

  124682
+   2468
--------
  127150

Melhor ainda, para esse problema pode ser razoável tomar uma calculadora de mesa. Uma calculadora nada mais é do que um computador que realiza operações aritméticas e em que escrevemos as instruções diretamente no teclado. Se quisermos utilizar um computador moderno, então precisamos decidir duas coisas:

  1. Em que linguagem de programação escreveremos nossas instruções?
  2. Qual o mecanismo utilizaremos para executar essas instruções?

A resposta da primeira pergunta para essa disciplina é Python 3. Como Python é uma linguagem interpretada, a resposta da segunda pergunta é invocando um interpretador. Existem duas maneiras de invocar o interpretador do Python: interativa e não interativa.

Interpretador de comandos interativo

Usamos o interpretador de comandos interativos quando queremos realizar operações simples e curtas apenas uma vez. Muitas vezes também utilizamos esse interpretadora para experimentar e explorar a linguagem, algum algoritmos ou alguma biblioteca de funções existente. Para isso, primeiro invocamos o interpretador de comandos python3 sem argumentos a partir do terminal do computador.

python3

Iremos ver o console do Python 3 em que podemos digitar comandos, como

Python 3.7.3 (default, Oct  7 2019, 12:56:13)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Podemos digitar expressões aritméticas usuais (soma, subtração, divisão e multiplicação) nesse console. Experimente várias delas.

>>> 124682 + 2468
127150

Ao contrário do computador moderno, a nossa calculadora é bem limitada. Você deve adivinhar que uma das principais limitações da calculadora é que a memória dela é muito limitada — só armazena os operandos e, normalmente, um resultado anterior. Se você tiver realizando operações com várias números, você precisa anotar cada resultado intermediário no papel. Em Python, podemos criar uma variável. Por exemplo, o IMC é dado pela seguinte fórmula

$$ \mbox{IMC} = \frac{{\mbox{massa}}}{({\mbox{altura}}\cdot {\mbox{altura}})} $$

Então podemos calcular nosso índice como o seguinte

>>> peso = 73
>>> altura = 1.75
>>> imc = peso / (altura * altura)
>>> imc
23.836734693877553

Devemos ler "peso recebe 73", "altura recebe 1.75", etc. O que as três primeiras linhas fazem é criar variáveis que guardam os valores do lado direto. Lembrem-se, uma variável corresponde a uma caixa na memória que guarda um determinado valor. Cada uma dessas três linhas faz o seguinte:

  • reserva um espaço na memória suficiente para um valor
  • guardo o valor do lado direito de = nesse espaço
  • associa o nome do lado esquerdo de = a esse valor

Observe que apenas após a último linha obtemos algum resultado: atribuição não é uma expressão em Python e apenas expressões têm o seu valor impresso no console interativo.

Tipos de variáveis

Você deve se lembrar de que todo valor armazenado na memória do computador tem um tipo associado. Para descobrir o tipo associado a cada uma das variáveis, usamos o seguinte:

>>> type(peso)
<class 'int'>
>>> type(altura)
<class 'float'>
>>> type(imc)
<class 'float'>

Vemos que peso tem tipo int. Um tipo int corresponde a um número inteiro (positivo, negativo ou zero). Em Python 3 (mas não em Python 2), um número inteiro pode ter quantos dígitos forem necessários. Experimente, por exemplo, calcular a décima potência de -99999999 em uma calculadora comum. Certamente ela terminará com um erro. Em Python, podemos fazer o seguinte

>>> -99999999 ** 10
-99999990000000449999988000000209999997480000020999999880000000449999999000000001

Você acabou de aprender que ** corresponde ao operador de exponenciação de Python. Mas tem algo estranho: a potência par de um número não pode ser negativa, mas o resultado obtido foi negativo! Na verdade, o que acabamos de calcular foi -(99999999 ** 10). O operador ** tem precedência ou prioridade sobre o operador de negação -. Deveríamos escrever, portanto,

>>> (-99999999) ** 10
99999990000000449999988000000209999997480000020999999880000000449999999000000001

Já as variáveis altura e imc têm tipo float. Um float é um tipo numérico de ponto flutuante, que é utilizada para guardar uma aproximação de um número real. Observe que falamos ponto, e não vírgula: ao contrário do português do Brasil, na maioria das linguagens de programação utilizamos um ponto . para indicar a parte fracionária de um número.

O fato de que guardamos uma aproximação ao usarmos um número fracionário é importante. Por exemplo, é evidente que a soma 0.1 + 0.2 deve valer exatamente 0.3, mas o interpretador Python parece discordar:

>>> 0.1 + 0.2
0.30000000000000004

Ao contrário dos números inteiros, em que sempre guardamos uma representação exato no número, não é possível guardar uma representação exata de cada número real. Pense, por exemplo, em como representar o número $\pi$ e cada um dos outros números irracionais: não podemos enumerar todos os números irracionais! Para a maioria das nossas aplicações que veremos, usar uma aproximação é mais do que suficiente, mas devemos tomar cuidado sempre que:

  1. compararmos números de ponto flutuante
  2. armazenarmos números astronomicamente grandes ou pequenos

As variáveis de ponto flutuante são armazenadas guardando três números inteiros: sinal, mantissa e expoente e representam um número $$(-1)^{sinal} \cdot mantissa \cdot 2^{expoente}$$

Por exemplo, $$0.5 = (-1)^0 \cdot 1 \cdot 2^{-1}$$

Para a programadora, normalmente é indiferente essa representação, desde que ele se atente ao fato de que cada número de ponto flutuante é uma aproximação de algum número representado!

É claro que o tipo da variável peso é inteiro, pois o valor atribuído é 73. Podemos forçar a utilização de float adicionando um ponto

>>> peso_float = 73.
>>> type(peso_float)
<class 'float'>

Mas por que imc tem tipo float? O motivo disso é que a expressão do lado direito é avaliada para um número float: sempre que dividimos dois números, obtemos um float em Python 3, mesmo que a divisão seja exata! Se quisermos obter apenas o quociente inteiro de uma divisão, usamos o operador //

>>> 5 / 2
2.5
>>> 6 / 2
3.0
>>> 6 // 2
3

Erros

Quando programamos pode acontecer uma série de erros. Um dos erros mais comuns é o erro de sintaxe e pode acontecer mesmo com operações bem simples como as que aprendermos. Por exemplo, se nos esquecermos de um operador

>>> imc = peso / (altura  altura)
  File "<stdin>", line 1
    imc = peso / (altura  altura)
                               ^
SyntaxError: invalid syntax

O símbolo ^ está apontando para o elemento do programa (token) que não era esperado naquela posição. De fato, ali deveríamos ter um símbolo de multiplicação *.

Outro erro de programação bastante comum é utilizar uma variável não definida, algumas vezes porque escrevemos o nome da variável com a grafia incorreta

>>> imc = peso / (altura * autura)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'autura' is not defined

Outros erros são chamados erros de execução e acontecem apenas no momento da execução. Por exemplo, se tivéssemos digitado a altura erradamente:

>>> peso = 73
>>> altura = 0
>>> imc = peso / (altura * altura)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Os erros acima são chamados de exceções. Quando uma exceção ocorre, o interpretador para a execução do seu programa com uma mensagem de erro e se recursa a continuar. Algumas vezes, é preciso tratar exceções, mas por agora não falaremos disso. Nos nossos primeiros programas, as exceções indicam simplesmente que o programa está incorreto é deve ser corrigido.

Finalmente, podemos ter erros de lógica, que são erros que não são detectados pelo interpretadores. Ao contrário dos erros anteriores, pode ser difícil encontrar esse erro e, algumas vezes, um erro de lógica pode passar desapercebido por muito tempo. Já vimos esse problema com a precedência acima. Vamos ver outro caso de descuido com a ordem das operações:

>>> peso = 73
>>> altura = 1.75
>>> imc = peso / altura*altura
>>> imc
73.0

Alguém que tomasse esse resultado como correto iria com certeza estar bastante preocupado com sua saúde!

Um parênteses: módulos

Uma razão comum para se usar uma sessão interativa do interpretador é experimentar com novos módulos e funções. Não vamos ver como criar módulos agora, mas podemos desde já como utilizá-los. Suponha que queremos calcular a raiz quadrada de um número. Não existe operador em Python dedicado para isso, mas podemos importar esse operador de um módulo math. Por exemplo, para calcular o tamanho da diagonal de um retângulo, fazemos o seguinte:

>>> import math
>>> cateto1 = 10
>>> cateto2 = 5
>>> hipotenusa = math.sqrt(cateto1**2 + cateto2**2)
>>> hipotenusa
11.180339887498949

Dica: nas sessões interativas, as teclas para cima e para baixo navegam no histórico de comandos digitados. Além disso, você pode usar o tab para completar uma palavra, depois de escrever o prefixo. Por exemplo, para ver quais nomes ou operadores estão disponíveis no módulo importado, digite math. e aperte tab duas vezes. Para ver o que cada operação faz, use help: help(math.cos) irá mostrar a documentação dessa operação. Talvez seja necessário digitar q para sair da documentação.


Escrevendo um programa

Embora o modo interativo do interpretador Python seja útil, ele só serve em situações limitadas, quando queremos executar as instruções uma única vez. Na maior parte das vezes, quando queremos executar instruções Python, primeiro escrevemos um programa em Python. Os programas em Python também são chamados de script, em parte porque Python é utilizado muitas vezes com ferramenta de automação de tarefas cotidianas, em parte para distinguir de outros programas já compilados em linguagem de máquina.

Vamos construir um programa que

  1. pede para o usuário digitar um nome
  2. imprime uma mensagem de bom dia para o usuário

Criamos o seguinte programa, i.e., criarmos um arquivo de texto chamado bomdia.py e digitamos

usuario = input("Digite seu nome: ")
print("Bom dia", usuario)

Após salvar o arquivo, podemos executar o programa invocando o interpretador Python em um terminal de execução. Esse programa irá imprimir uma mensagem pedindo o nome. Ao escrevermos algum texto, digamos, Maria, e apertarmos Enter, obteremos o seguinte terminal

user@host:$ python3 bomdia.py
Digite seu nome: Maria
Bom dia Maria

No programa vemos dois comandos que fazem parte da Linguagem Python. A função input lê um texto digitado pelo usuário no teclado até o momento em que ele digita a tecla Enter. O que essa função devolve é uma variável que guarda a sequência de caracteres digitada pelo usuário. Essa variável, que é do tipo string, ou str, é associada ao identificador usuario, que é o nome do usuário.

A função print recebe uma sequência de argumentos, cada um pode ter um valor e um tipo diferente. O que ela faz é mostrar (ou imprimir) na tela os valores na forma de texto, isso é, de uma string. Se houver vários argumentos, a função print irá mostra um espaço entre eles.

Strings

No código acima, temos "Bom dia" que é uma string literal. As strings literais representam valores do tipo str que não mudam e correspondem à sequência de caracteres entre as aspas. Se quisermos inserir um caractere de aspas dentro da string, precisamos escapar do significado especial que esse caractere tem utilizando uma contra-barra, ou backslash antes.

Por exemplo, considere um programa pensamento.py com o seguinte conteúdo

frase = "Descartes disse:\n\"penso, logo existo\""
print(frase)

O primeiro comando de pensamento.py cria uma string com aspas. Poderíamos também utilizar aspas simples,

frase = 'Descartes disse:\n"penso, logo existo"'
print(frase)

já que o caractere " não têm significado especial dentro da string literal. Se executarmos esse programa, iremos obter

Descartes disse:
"penso, logo existo"

Repare que o \n não foi impresso, mas sim foi mostrada uma quebra de linha: de fato, \n é um caractere de controle que instrui o terminal a saltar uma linha. Se quiséssemos a própria barra, deveríamos escapar do significado especial da barra, usando

frase = 'Descartes disse:\\n"penso, logo existo"'
print(frase)

que imprimiria

Descartes disse:\n"penso, logo existo"

Operações de string

Assim como podemos fazer operações com números inteiros e números de ponto flutuante, também podemos fazer várias operações com string. Uma operação comum é criar uma substring ou acessar um determinado caractere. Veja o exemplo:

frase = "O essencial é invisível aos olhos"
print("O primeiro caractere é: ", frase[0])
print("O segundo é um espaço: ", frase[1])
print("A segunda palavra é: ", frase[2:11])
print("Um sufixo é:", frase[16:])
print("A última palavra é:", frase[-5:])

Executando o código acima, obtemos

O primeiro caractere é:  O
O segundo é um espaço:
A segunda palavra é:  essencial
Um sufixo é: visível aos olhos
A última palavra é: olhos

Execute o exemplo, releia o código e reflita. Procure entender porque essa saída é executada e procure a documentação se necessário.

Uma outra operação comum com strings é a concatenação. Por exemplo, poderíamos reescrever o exemplo acima da seguinte maneira; dessa vez, usando a pontuação adequadamente.

usuario = input("Digite seu nome: ")
mensagem = "Bom dia, " + usuario + "."
print(mensagem)

Reutilizamos o símbolo de +. Mas perceba que nesse caso ambos operandos são strings, assim o significado de + é concatenar as duas strings, em ordem. Qual a saída será obtida?

Convertendo tipos

Vamos agora usar o que aprendemos para criar um programa que calcula a hipotenusa de um triângulo retângulo. Escrevemos o seguinte

import math

cateto1 = input()
cateto2 = input()

hipotenusa = math.sqrt(cateto1*cateto1 + cateto2*cateto2)

print("A hipotenusa é " + hipotenusa)

Como você já deve imaginar, esse programa não funciona. Experimente. O motivo é fácil de entender: ninguém garante que o usuário irá escrever dois números válidos! E de fato, input apenas lê uma sequência de caracteres e não tenta interpretá-la de nenhuma maneira. O tipo de ambos cateto1 e cateto2 no programa acima é str. Ou seja, são strings: não podemos multiplicar duas strings!

Para que nosso programa funcione, precisamos que o valor das variáveis que representam os catetos sejam números de ponto flutuante! Mas como transformar uma string, que contém a sequência de dígitos decimais e, possivelmente um ponto, no número fracionário que ele representa em ponto flutuante? Para isso, usamos uma função de conversão para float.

import math

cateto1 = float(input())
cateto2 = float(input())

hipotenusa = math.sqrt(cateto1*cateto1 + cateto2*cateto2)

print("A hipotenusa é " + hipotenusa)

Agora sim, a hipotenusa será calculada corretamente! Mas o programa ainda está incorreto. O erro é que tentamos fazer uma concatenação com um operando que não é uma string! Para converter um valor em uma string, usamos a função str. Corrija esse programa usando conversão para string e depois fazendo a concatenação adequada!

Formatando a saída

Em Python 3, existe uma maneira bem conveniente de converter valores em strings, que são as strings de modelo para formatação. A ideia é escrever um texto e deixar marcações, placeholders, para substituir com o valor formatado. Por exemplo, se quiséssemos sempre escrever o valor da hipotenusa com duas casas decimais, poderíamos fazer o seguinte

import math

cateto1 = float(input())
cateto2 = float(input())

hipotenusa = math.sqrt(cateto1*cateto1 + cateto2*cateto2)

print("Os catetos são {} e {}!".format(cateto1, cateto2))
print("A hipotenusa é {:0.2f}.".format(hipotenusa))

Procure na documentação as várias formas de usar format. Nas versões mais novas de Python, há também uma maneira mais compacta de escrever esse código, usando as f-strings:

import math

cateto1 = float(input())
cateto2 = float(input())

hipotenusa = math.sqrt(cateto1*cateto1 + cateto2*cateto2)

print(f"Os catetos são {cateto1} e {cateto2}!")
print(f"A hipotenusa é {hipotenusa:0.2f}.")

Repare que as f-strings começam com um símbolo f.

Comandos condicionais

Vamos resolver o seguinte exercício:

Escreva um programa que lê um número inteiro do teclado e imprime "sim" se o número for par e maior do que 10, ou for ímpar e menor do que 50. Caso contrário o programa deve imprimir "não".

Primeiro, vamos tentar escrever um algoritmo em português

numero <-- leia um número do teclado
se número for par
    se numero > 10
        imprima sim
    senão
        imprima não
do contrário, se for ímpar
    se numero < 50
        imprima sim
    senão
        imprima não

Parece razoável supor que esse problema é bem simples, então fazendo algumas simulações rápidas nos convencemos de que o programa está correto. Vamos reescrever o algoritmo, agora em Python

numero = int(input())
if numero % 2 == 0:
    if numero > 10:
        print("sim")
    else:
        print("não")
else:
    if numero < 50:
        print("sim")
    else:
        print("não")

Há muitas novidades aqui, vamos parte a parte. Primeiro, o recuo em que os comandos são escritos é fundamental para identificar a estrutura do algoritmo. Temos duas palavras-chaves novas: if e else. O comando if significa se e é seguido por uma condição. Se essa condição for verdadeira, e somente nesse caso, o corpo de comandos que segue os dois pontos e que está recuado é executado. O comando else é opcional; o corpo desse comando é executando sempre que o a condição do if falhar.

Valores booleanos

Primeiro precisamos entender o que é uma condição: uma condição é uma expressão cujo resultado é um valor do tipo bool. Um tipo bool, por sua vez, é um valor de verdade que pode ser ou True ou False e nada mais.

Obtemos valores booleanos normalmente fazendo perguntas! Em uma linguagem de programação, essas perguntas estão na forma de comparações e outras funções. Por exemplo, se quisermos verificar se o usuário digitou apenas números decimais, podemos usar

numero_string = input()
if numero_string.isdigit():
    numero = int(numero_string)
    print(f"O texto digitado {numero_string} contém apenas dígitos decimais.")
else:
    print(f"O texto digitado {numero_string} contém apenas dígitos decimais.")

Poderíamos reescrever o programa anterior da seguinte maneira:

numero = int(input())

par = numero % 2 == 0
maior_10 = numero > 10
menor_50 = numero < 50

if par:
    if maior_10:
        print("sim")
    else:
        print("não")
else:
    if menor_50:
        print("sim")
    else:
        print("não")

O tipo dessas três novas variáveis é bool. Confira usando um console Python e digitando

var = True
type(var)
par = numero % 2 == 0
type(var)

Operadores booleanos

Às vezes, queremos fazer duas perguntas ao mesmo tempo, outras vezes precisamos satisfazer apenas uma de várias condições. Pensando nisso, vamos reescrever o programa

numero = int(input())

par = numero % 2 == 0
maior_10 = numero > 10
menor_50 = numero < 50

if par and maior_10:
    print("sim")
elif not par and menor_50:
    print("sim")
else:
    print("não")

Observamos algumas novas palavras-chaves: a primeira é and (a conjunção e escrita em inglês) que significa que queremos que ambas condições sejam verdadeiras, a da esquerda e a da direita; a segunda é not (não em inglês), que nega o valor e verdade de uma expressão; a última é elif, que significa, do contrário, verifique a condição e execute. O elif nada mais é do que uma forma mais compacta de escrever um código equivalente:

numero = int(input())

par = numero % 2 == 0
maior_10 = numero > 10
menor_50 = numero < 50

if par and maior_10:
    print("sim")
else:
  if par and menor_50:
      print("sim")
  else:
      print("não")

Mas, como essa sequência de if seguido de else é bastante comum, fica mais fácil escrever todos os corpos de comando no mesmo recuo. Podemos usar quantos elif após um if quanto forem necessários.

No nosso exemplo, o corpo de if e elif são iguais. Assim, podemos simplificar ainda mais o programa acima.

numero = int(input())

par = numero % 2 == 0
maior_10 = numero > 10
menor_50 = numero < 50

if par and maior_10 or not par and menor_50:
    print("sim")
else:
    print("não")

A palavra-chave or é um operador que devolve verdadeiro bastando que pelo menos um dos seus operando seja True.

Note que nem sempre é fácil entender qual a ordem em que as operações serão executadas. Para isso é necessário conhecer a precedência dos operadores (e um bocado de experiência). No caso do if acima, not é executado primeiro e or é executado por último. Em expressões complicadas, como a acima, é sempre mais claro (e mais prático), usar parênteses:

numero = int(input())

par = numero % 2 == 0
maior_10 = numero > 10
menor_50 = numero < 50

if (par and maior_10) or (not par and menor_50):
    print("sim")
else:
    print("não")

O nome booleano, que em Python corresponde a bool, é uma homenagem a um matemático George Bool, a quem é frequentemente atribuída a criação da álgebra booleana. Por ora, basta conhecermos bem as chamadas tabelas-verdades das operações:

A not A
True False
False True
A B A and B
True True True
True False False
False True False
False False False
A B A or B
True True True
True False True
False True True
False False False

Relembrar algumas equivalências bem conhecidas também é importante:

  • not (a == b) é equivalente a a != b
  • not (a != b) é equivalente a a == b
  • not (a > b) é equivalente a a <= b
  • not (a < b) é equivalente a a >= b
  • not (a >= b) é equivalente a a < b
  • not (a <= b) é equivalente a a > b

E refletir sobre algumas formas que às vezes passam desapercebido:

  • not (a and b) é equivalente a not a or not b
  • not (a or b) é equivalente a not a and not b

Repetindo

O corpo de comandos de if e de else pode ser executado uma ou nenhuma vez dependendo da condição. Muitas vezes, queremos repetir um conjunto de comandos enquanto determinada condição é satisfeita.

Iremos ver comandos repetitivos com calma depois. Por enquanto, resolvamos um exercício:

Escreva um programa que leia ma sequência de pares de números inteiros e pare apenas quando a soma de ambos for 42.

print("Escreva dois números")
a = int(input())
b = int(input())
soma = a + b

while soma != 42:
    print("Escreva dois números")
    a = int(input())
    b = int(input())
    soma = a + b

print(f"Parabéns, a soma de {a} e {b} é um número fundamental")

Há uma novidade: a palavra-chave while é como if, mas é um comando repetitivo, isso significa que o corpo de comandos do while deve mudar o valor das variáveis a mudar a expressão de condição.

Há um problema desse código: repetimos a mesma coisa duas vezes. Como sempre precisamos executar uma vez, podemos substituir a expressão da condição por uma variável, assim:

procurando = True

while procurando:
    print("Escreva dois números")
    a = int(input())
    b = int(input())
    soma = a + b

    if soma == 42:
      procurando = False

print(f"Parabéns, a soma de {a} e {b} é um número fundamental")