Ficha 01 · Sintaxe, estruturas, funções
- Sintaxe
- Controlo
- Funções
- Estruturas
Parte I · Sintaxe e variáveis
Exercício 1 · Variáveis e tipos (10 pts)
a) Identifica o tipo de cada variável:
a = 42
b = 3.14
c = "Python"
d = True
e = None
f = [1, 2, 3]
g = (4, 5)
h = {"x": 1}
i = {1, 2, 3}
b) Que acontece em: a / 2, a // 2, a % 2, a ** 2?
a) Tipos:
- a = 42 → int.
- b = 3.14 → float.
- c = "Python" → str.
- d = True → bool.
- e = None → NoneType.
- f = [1, 2, 3] → list.
- g = (4, 5) → tuple.
- h = {"x": 1} → dict.
- i = {1, 2, 3} → set.
Verificar com type(a).
b) Operações:
- 42 / 2 = 21.0 (divisão sempre retorna float).
- 42 // 2 = 21 (divisão inteira, int).
- 42 % 2 = 0 (resto).
- 42 ** 2 = 1764 (potência).
Adicionalmente:
- 42 // 5 = 8 (truncado).
- 42 % 5 = 2 (resto).
- 2 ** 10 = 1024.
Exercício 2 · F-strings (10 pts)
Reescreve com f-string:
nome = "Ana"
idade = 25
preco = 19.99
mensagem = "Olá " + nome + ", tens " + str(idade) + " anos. O preço é " + str(preco) + " euros."
Reescrito com f-string:
nome = "Ana"
idade = 25
preco = 19.99
mensagem = f"Olá {nome}, tens {idade} anos. O preço é {preco} euros."
Mais polido (formatação de preço):
mensagem = f"Olá {nome}, tens {idade} anos. O preço é {preco:.2f}€."
Outras opções de formatação:
# Decimais
f"{preco:.2f}" # '19.99'
f"{preco:.4f}" # '19.9900'
# Padding
f"{nome:>10}" # ' Ana' (direita, 10 chars)
f"{nome:<10}" # 'Ana ' (esquerda)
f"{nome:^10}" # ' Ana ' (centro)
# Padding com char
f"{nome:*>10}" # '*******Ana'
# Números
f"{1234567:,}" # '1,234,567' (separador milhares)
f"{0.5:%}" # '50.000000%' (percentagem)
f"{0.5:.1%}" # '50.0%'
# Hexadecimal
f"{255:x}" # 'ff'
f"{255:X}" # 'FF'
f"{255:#x}" # '0xff'
# Binário
f"{10:b}" # '1010'
# Em expressões
f"{nome.upper()}" # 'ANA'
f"{idade * 2}" # '50'
# Multi-linha
texto = f"""
Olá {nome},
A tua idade é {idade}.
"""
Vantagens f-strings vs alternativas:
- f-string: f"{nome} tem {idade}" ✅ moderno, legível, rápido.
- format: "{} tem {}".format(nome, idade) (antigo, ainda válido).
- %: "%s tem %d" % (nome, idade) (muito antigo).
- Concatenação: nome + " tem " + str(idade) (verbose, erro-prone).
Sempre preferir f-strings em Python 3.6+.
Parte II · Estruturas de controlo
Exercício 3 · If/Elif/Else (10 pts)
Escreve função que classifica nota numérica: - 0-9: "Reprovado" - 10-13: "Suficiente" - 14-15: "Bom" - 16-17: "Muito Bom" - 18-20: "Excelente" - Outras: "Inválido"
def classificar_nota(nota: int) -> str:
"""Classifica nota de 0-20."""
if nota < 0 or nota > 20:
return "Inválido"
elif nota < 10:
return "Reprovado"
elif nota < 14:
return "Suficiente"
elif nota < 16:
return "Bom"
elif nota < 18:
return "Muito Bom"
else:
return "Excelente"
# Testar
notas = [-1, 0, 9, 10, 13, 14, 15, 16, 17, 18, 20, 21]
for n in notas:
print(f"{n}: {classificar_nota(n)}")
Output:
-1: Inválido
0: Reprovado
9: Reprovado
10: Suficiente
13: Suficiente
14: Bom
15: Bom
16: Muito Bom
17: Muito Bom
18: Excelente
20: Excelente
21: Inválido
Alternativa mais idiomática com match-case (Python 3.10+):
def classificar_nota(nota: int) -> str:
match nota:
case n if n < 0 or n > 20:
return "Inválido"
case n if n < 10:
return "Reprovado"
case n if n < 14:
return "Suficiente"
case n if n < 16:
return "Bom"
case n if n < 18:
return "Muito Bom"
case _:
return "Excelente"
Alternativa com dict (elegante para muitos casos):
def classificar_nota(nota: int) -> str:
if nota < 0 or nota > 20:
return "Inválido"
classificacoes = [
(0, "Reprovado"),
(10, "Suficiente"),
(14, "Bom"),
(16, "Muito Bom"),
(18, "Excelente"),
]
for limite, cat in reversed(classificacoes):
if nota >= limite:
return cat
Type hints + validação:
def classificar_nota(nota: int) -> str:
if not isinstance(nota, int):
raise TypeError("Nota deve ser inteiro")
if not 0 <= nota <= 20:
raise ValueError("Nota deve estar entre 0 e 20")
# ... resto da lógica
Princípio: para esta lógica simples, primeira versão (if/elif/else) é clara e legível.
Exercício 4 · Loops (15 pts)
a) Escreve loop que imprime números pares de 0 a 20. b) Conta vogais numa string. c) FizzBuzz: imprime 1 a 30. Se múltiplo de 3, "Fizz". Se múltiplo de 5, "Buzz". Se ambos, "FizzBuzz".
a) Números pares de 0 a 20:
# Solução 1: for + if
for i in range(21):
if i % 2 == 0:
print(i)
# Solução 2: range com step
for i in range(0, 21, 2):
print(i)
# Solução 3: list comprehension
pares = [i for i in range(21) if i % 2 == 0]
print(pares)
Solução 2 é mais Pythonic (sem condição desnecessária).
b) Contar vogais:
def contar_vogais(texto: str) -> int:
"""Conta vogais (incluindo acentuadas)."""
vogais = "aeiouáéíóúàèìòùâêîôûãõ"
contador = 0
for char in texto.lower():
if char in vogais:
contador += 1
return contador
# Testar
print(contar_vogais("Olá, mundo!")) # 4 (O, á, u, o)
print(contar_vogais("Python")) # 1 (o)
print(contar_vogais("AEIOU")) # 5
Solução mais Pythonic com sum:
def contar_vogais(texto: str) -> int:
vogais = "aeiouáéíóúàèìòùâêîôûãõ"
return sum(1 for char in texto.lower() if char in vogais)
Com Counter:
from collections import Counter
def contar_vogais(texto: str) -> int:
vogais = set("aeiouáéíóúàèìòùâêîôûãõ")
contador = Counter(texto.lower())
return sum(qty for char, qty in contador.items() if char in vogais)
c) FizzBuzz — clássico de entrevista:
for i in range(1, 31):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
Output:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
30
Versão mais elegante:
for i in range(1, 31):
output = ""
if i % 3 == 0:
output += "Fizz"
if i % 5 == 0:
output += "Buzz"
print(output or i)
Truque: output or i retorna i se output é vazio (falsy).
Versão funcional:
def fizzbuzz(n):
return "FizzBuzz" if n % 15 == 0 else \
"Fizz" if n % 3 == 0 else \
"Buzz" if n % 5 == 0 else \
n
for i in range(1, 31):
print(fizzbuzz(i))
Por que FizzBuzz em entrevistas: - Filtra candidatos que não sabem fazer básico. - Avalia: lógica, syntax, estilo. - 5 minutos chega para fazer.
Parte III · Funções
Exercício 5 · Funções (15 pts)
a) Escreve função que calcula IMC (peso/altura²). b) Adapta para receber lista de pessoas e retornar dict com IMC de cada. c) Adiciona validação de inputs.
a) Função básica:
def calcular_imc(peso: float, altura: float) -> float:
"""Calcula IMC."""
return peso / (altura ** 2)
# Testar
print(calcular_imc(70, 1.75)) # 22.857...
b) Para lista de pessoas:
def calcular_imc(peso: float, altura: float) -> float:
return peso / (altura ** 2)
def imcs_pessoas(pessoas: list) -> dict:
"""
Calcula IMC para lista de pessoas.
Args:
pessoas: lista de dicts com 'nome', 'peso', 'altura'.
Returns:
Dict nome → IMC.
"""
return {p["nome"]: calcular_imc(p["peso"], p["altura"]) for p in pessoas}
# Testar
pessoas = [
{"nome": "Ana", "peso": 65, "altura": 1.65},
{"nome": "João", "peso": 80, "altura": 1.80},
{"nome": "Maria", "peso": 55, "altura": 1.60},
]
resultados = imcs_pessoas(pessoas)
for nome, imc in resultados.items():
print(f"{nome}: {imc:.2f}")
# Ana: 23.88
# João: 24.69
# Maria: 21.48
c) Com validação completa:
def calcular_imc(peso: float, altura: float) -> float:
"""
Calcula IMC com validação.
Args:
peso: kg, > 0.
altura: metros, > 0.
Returns:
IMC.
Raises:
TypeError: se inputs não forem números.
ValueError: se inputs não forem positivos.
"""
# Type check
if not isinstance(peso, (int, float)):
raise TypeError(f"Peso deve ser número, recebeu {type(peso).__name__}")
if not isinstance(altura, (int, float)):
raise TypeError(f"Altura deve ser número, recebeu {type(altura).__name__}")
# Value check
if peso <= 0:
raise ValueError(f"Peso deve ser positivo, recebeu {peso}")
if altura <= 0:
raise ValueError(f"Altura deve ser positiva, recebeu {altura}")
# Sanidade (não obrigatório mas útil)
if peso > 500:
raise ValueError(f"Peso de {peso}kg é irrealista")
if altura > 3:
raise ValueError(f"Altura de {altura}m é irrealista")
return peso / (altura ** 2)
def imcs_pessoas(pessoas: list) -> dict:
"""Calcula IMC para lista de pessoas, com tratamento de erros."""
resultados = {}
for p in pessoas:
try:
imc = calcular_imc(p["peso"], p["altura"])
resultados[p["nome"]] = imc
except (TypeError, ValueError) as e:
print(f"Erro com {p.get('nome', '?')}: {e}")
resultados[p["nome"]] = None
except KeyError as e:
print(f"Dados em falta: {e}")
return resultados
# Testar
pessoas = [
{"nome": "Ana", "peso": 65, "altura": 1.65},
{"nome": "Erro1", "peso": -10, "altura": 1.70}, # peso negativo
{"nome": "Erro2", "peso": "muito", "altura": 1.70}, # tipo errado
{"nome": "Sem peso"}, # falta key
]
resultados = imcs_pessoas(pessoas)
for nome, imc in resultados.items():
if imc:
print(f"{nome}: {imc:.2f}")
else:
print(f"{nome}: inválido")
Output:
Erro com Erro1: Peso deve ser positivo, recebeu -10
Erro com Erro2: Peso deve ser número, recebeu str
Dados em falta: 'peso'
Ana: 23.88
Erro1: inválido
Erro2: inválido
Boas práticas demonstradas:
- Type hints (
peso: float). - Docstrings completas (description, args, returns, raises).
- Validação de tipos com
isinstance. - Validação de valores (positivo, sanidade).
- Mensagens de erro descritivas.
- Tratamento de erros no caller.
- Específico (TypeError vs ValueError vs KeyError).
Versão com classificação:
def classificar_imc(imc: float) -> str:
"""Classifica IMC segundo OMS."""
if imc < 18.5:
return "Abaixo do peso"
elif imc < 25:
return "Peso normal"
elif imc < 30:
return "Excesso de peso"
elif imc < 35:
return "Obesidade grau I"
elif imc < 40:
return "Obesidade grau II"
else:
return "Obesidade grau III"
# Combinar
imc = calcular_imc(70, 1.75)
print(f"IMC: {imc:.2f} ({classificar_imc(imc)})")
# IMC: 22.86 (Peso normal)
Parte IV · Estruturas de dados
Exercício 6 · Listas e dicts (15 pts)
Tens lista de produtos:
produtos = [
{"nome": "Caneta", "preco": 1.50, "stock": 50},
{"nome": "Caderno", "preco": 3.00, "stock": 20},
{"nome": "Lápis", "preco": 0.50, "stock": 100},
{"nome": "Borracha", "preco": 1.00, "stock": 30},
]
a) Total em stock (€). b) Produtos com stock < 30. c) Produto mais caro. d) Ordenar por preço descendente. e) Adicionar 10% IVA a todos os preços.
produtos = [
{"nome": "Caneta", "preco": 1.50, "stock": 50},
{"nome": "Caderno", "preco": 3.00, "stock": 20},
{"nome": "Lápis", "preco": 0.50, "stock": 100},
{"nome": "Borracha", "preco": 1.00, "stock": 30},
]
a) Total em stock (€):
# Solução 1: loop tradicional
total = 0
for p in produtos:
total += p["preco"] * p["stock"]
print(f"Total: {total}€")
# Solução 2: sum + generator (idiomática)
total = sum(p["preco"] * p["stock"] for p in produtos)
print(f"Total: {total}€")
# Total: 235.0€
b) Produtos com stock < 30:
# List comprehension
baixo_stock = [p for p in produtos if p["stock"] < 30]
print(baixo_stock)
# [{'nome': 'Caderno', 'preco': 3.0, 'stock': 20}]
# Apenas nomes
nomes_baixo = [p["nome"] for p in produtos if p["stock"] < 30]
print(nomes_baixo)
# ['Caderno']
# Com filter (alternativa funcional)
baixo_stock = list(filter(lambda p: p["stock"] < 30, produtos))
c) Produto mais caro:
# Solução 1: max com key
mais_caro = max(produtos, key=lambda p: p["preco"])
print(mais_caro)
# {'nome': 'Caderno', 'preco': 3.0, 'stock': 20}
# Solução 2: sorted (mais info)
ordenados = sorted(produtos, key=lambda p: p["preco"], reverse=True)
mais_caro = ordenados[0]
# Solução 3: max + iter (apenas nome)
nome_mais_caro = max(produtos, key=lambda p: p["preco"])["nome"]
print(nome_mais_caro)
# Caderno
d) Ordenar por preço descendente:
# Não modifica original
ordenados = sorted(produtos, key=lambda p: p["preco"], reverse=True)
# Modifica original
produtos.sort(key=lambda p: p["preco"], reverse=True)
print(ordenados)
# [
# {'nome': 'Caderno', 'preco': 3.0, 'stock': 20},
# {'nome': 'Caneta', 'preco': 1.5, 'stock': 50},
# {'nome': 'Borracha', 'preco': 1.0, 'stock': 30},
# {'nome': 'Lápis', 'preco': 0.5, 'stock': 100}
# ]
Ordenar por múltiplos critérios (preço desc, depois stock asc):
ordenados = sorted(produtos, key=lambda p: (-p["preco"], p["stock"]))
e) Adicionar 10% IVA:
Modificar in-place:
for p in produtos:
p["preco"] = round(p["preco"] * 1.10, 2)
print(produtos)
# [{'nome': 'Caneta', 'preco': 1.65, 'stock': 50}, ...]
Criar nova lista (não mexer original):
com_iva = [
{**p, "preco": round(p["preco"] * 1.10, 2)}
for p in produtos
]
Operador **: spread/unpack dict. {**p, "preco": novo} copia todos os pares de p mas substitui preco.
Solução completa com funções:
def total_em_stock(produtos: list) -> float:
"""Calcula valor total em stock."""
return sum(p["preco"] * p["stock"] for p in produtos)
def filtrar_baixo_stock(produtos: list, limite: int = 30) -> list:
"""Retorna produtos com stock abaixo do limite."""
return [p for p in produtos if p["stock"] < limite]
def produto_mais_caro(produtos: list) -> dict:
"""Retorna produto com maior preço."""
return max(produtos, key=lambda p: p["preco"])
def ordenar_por_preco(produtos: list, reverse: bool = True) -> list:
"""Ordena produtos por preço."""
return sorted(produtos, key=lambda p: p["preco"], reverse=reverse)
def aplicar_iva(produtos: list, taxa: float = 0.23) -> list:
"""Aplica IVA aos preços (sem modificar original)."""
return [
{**p, "preco_com_iva": round(p["preco"] * (1 + taxa), 2)}
for p in produtos
]
# Usar
print(f"Total em stock: {total_em_stock(produtos):.2f}€")
print(f"Baixo stock: {filtrar_baixo_stock(produtos)}")
print(f"Mais caro: {produto_mais_caro(produtos)['nome']}")
print(f"Ordenados: {ordenar_por_preco(produtos)}")
print(f"Com IVA: {aplicar_iva(produtos)}")
Princípios aplicados: - List comprehensions (idiomáticas). - Sum + generator (memory efficient). - Max/min com key (elegante). - Sorted vs sort (consciência de side effects). - Funções pequenas e reutilizáveis. - Type hints + docstrings.
Parte V · Aplicação
Exercício 7 · Refactor (10 pts)
Refactoriza este código para ser mais Pythonic:
def processar(numeros):
pares = []
for i in range(len(numeros)):
if numeros[i] % 2 == 0:
pares.append(numeros[i] * 2)
soma = 0
for j in range(len(pares)):
soma = soma + pares[j]
if soma > 100:
return "alto"
else:
if soma > 50:
return "medio"
else:
return "baixo"
Refactor Pythonic:
def processar(numeros: list[int]) -> str:
"""Classifica soma do dobro dos pares."""
pares_dobrados = [n * 2 for n in numeros if n % 2 == 0]
soma = sum(pares_dobrados)
if soma > 100:
return "alto"
elif soma > 50:
return "medio"
return "baixo"
Comparação:
| Original | Refactored |
|---|---|
| 16 linhas | 6 linhas |
range(len(...)) (não-Pythonic) |
List comprehension |
| Loop manual para sum | sum() built-in |
| Nested if/else | elif chain |
| Sem type hints | Type hints |
| Sem docstring | Docstring |
Princípios Pythonic aplicados:
- Iterar directamente (não com índice):
- ❌
for i in range(len(lista)): lista[i] -
✅
for item in lista -
List comprehension em vez de append loop:
- ❌
result = []; for x in lst: if cond: result.append(...) -
✅
result = [... for x in lst if cond] -
Built-ins (sum, max, etc.) em vez de loop manual.
-
elif chain em vez de nested else.
-
Return early (sem else final).
-
Type hints para clareza.
-
Docstring mesmo em funções simples.
Versão ainda mais funcional:
def processar(numeros: list[int]) -> str:
soma = sum(n * 2 for n in numeros if n % 2 == 0)
return (
"alto" if soma > 100
else "medio" if soma > 50
else "baixo"
)
Generator expression (( ) em vez de [ ]):
- Não cria lista intermediária na memória.
- Mais eficiente para grandes inputs.
- Sintaxe quase idêntica.
Em vez de:
soma = sum([n * 2 for n in numeros if n % 2 == 0]) # lista temporária
Fazer:
soma = sum(n * 2 for n in numeros if n % 2 == 0) # generator (sem [])
Princípios Pythonic:
- "There should be one obvious way to do it" — escolher a forma standard.
- "Beautiful is better than ugly".
- "Readability counts".
- "Simple is better than complex".
Quando NÃO refactor para list comprehension:
❌ Lógica complexa:
# Não fazer
result = [process(x) if cond1(x) else (other(x) if cond2(x) else None) for x in lst if filter_cond(x)]
# Fazer
result = []
for x in lst:
if not filter_cond(x):
continue
if cond1(x):
result.append(process(x))
elif cond2(x):
result.append(other(x))
else:
result.append(None)
Regra: list comprehension legível em uma linha. Se não cabe, usar loop tradicional.
Exercício 8 · Programa completo (15 pts)
Escreve programa que: 1. Pede ao utilizador 5 números. 2. Calcula min, max, média. 3. Identifica números primos. 4. Imprime resultado formatado.
def é_primo(n: int) -> bool:
"""Verifica se n é primo."""
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
# Verificar apenas até sqrt(n)
for i in range(3, int(n ** 0.5) + 1, 2):
if n % i == 0:
return False
return True
def pedir_numeros(quantidade: int = 5) -> list[int]:
"""Pede ao utilizador quantidade números."""
numeros = []
print(f"Insere {quantidade} números:")
while len(numeros) < quantidade:
try:
entrada = input(f"Número {len(numeros) + 1}: ")
numero = int(entrada)
numeros.append(numero)
except ValueError:
print("⚠️ Não é número inteiro. Tenta novamente.")
return numeros
def analisar(numeros: list[int]) -> dict:
"""Calcula estatísticas e identifica primos."""
return {
"numeros": numeros,
"min": min(numeros),
"max": max(numeros),
"media": sum(numeros) / len(numeros),
"primos": [n for n in numeros if é_primo(n)],
"soma": sum(numeros),
}
def imprimir_relatorio(analise: dict) -> None:
"""Imprime relatório formatado."""
print("\n" + "=" * 40)
print("📊 RELATÓRIO DE ANÁLISE")
print("=" * 40)
print(f"\nNúmeros: {analise['numeros']}")
print(f"\n📉 Mínimo: {analise['min']}")
print(f"📈 Máximo: {analise['max']}")
print(f"➕ Soma: {analise['soma']}")
print(f"📊 Média: {analise['media']:.2f}")
if analise['primos']:
print(f"\n🔢 Primos: {analise['primos']}")
print(f" (Encontrados {len(analise['primos'])} primos)")
else:
print(f"\n🔢 Primos: (nenhum)")
print("=" * 40)
def main():
"""Programa principal."""
print("🔢 ANALISADOR DE NÚMEROS")
print("-" * 40)
try:
numeros = pedir_numeros(5)
analise = analisar(numeros)
imprimir_relatorio(analise)
except KeyboardInterrupt:
print("\n\n👋 Programa cancelado.")
except Exception as e:
print(f"\n❌ Erro: {e}")
if __name__ == "__main__":
main()
Exemplo de execução:
🔢 ANALISADOR DE NÚMEROS
----------------------------------------
Insere 5 números:
Número 1: 7
Número 2: 13
Número 3: 4
Número 4: abc
⚠️ Não é número inteiro. Tenta novamente.
Número 4: 11
Número 5: 20
========================================
📊 RELATÓRIO DE ANÁLISE
========================================
Números: [7, 13, 4, 11, 20]
📉 Mínimo: 4
📈 Máximo: 20
➕ Soma: 55
📊 Média: 11.00
🔢 Primos: [7, 13, 11]
(Encontrados 3 primos)
========================================
Princípios aplicados:
- Funções pequenas com responsabilidade única:
é_primo(): testa primalidade.pedir_numeros(): interage com utilizador.analisar(): calcula.imprimir_relatorio(): apresenta.-
main(): orquestra. -
Type hints em todas as funções.
-
Docstrings descritivas.
-
Tratamento de erros:
try/exceptem input.KeyboardInterruptpara Ctrl+C.-
Genérico para safety.
-
Algoritmo eficiente para primo:
- Casos especiais primeiro (< 2, == 2).
- Apenas até
sqrt(n). -
Apenas números ímpares.
-
if __name__ == "__main__":— permite importar sem executar. -
Output bonito com emojis e formatação.
Extensões possíveis:
- Aceitar quantidade variável de números.
- Guardar histórico em ficheiro JSON.
- Mais estatísticas: mediana, desvio padrão.
- Categorias adicionais: pares/ímpares, múltiplos de X.
- Visualização: gráfico com matplotlib.
- CLI args:
argparsepara opções. - Testes: pytest para
é_primo()eanalisar().
Versão melhorada com argparse:
import argparse
def main():
parser = argparse.ArgumentParser(description="Analisador de números")
parser.add_argument("--quantidade", "-q", type=int, default=5,
help="Quantos números a analisar")
parser.add_argument("--ficheiro", "-f", help="Ler de ficheiro")
args = parser.parse_args()
if args.ficheiro:
with open(args.ficheiro) as f:
numeros = [int(line.strip()) for line in f]
else:
numeros = pedir_numeros(args.quantidade)
analise = analisar(numeros)
imprimir_relatorio(analise)
Correr:
python analisador.py --quantidade 10
python analisador.py --ficheiro numeros.txt