Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00618

UC00618 · Criar aplicações em Python

Fundamentos, OOP, bibliotecas, projectos
50h · 4.5 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

Introdução

Python é a linguagem de programação mais popular do mundo (TIOBE Index, Stack Overflow surveys). Criada por Guido van Rossum em 1991, é hoje usada por: - Google, Instagram, Spotify, Netflix (web e infrastructure). - NASA, CERN (computing científico). - OpenAI, DeepMind (machine learning). - Milhares de startups (rapid prototyping).

Vantagens: - Sintaxe limpa (lê-se quase como inglês). - Versatilidade (web, data, AI, automation, scripts). - Comunidade massiva (bibliotecas para tudo). - Curva de aprendizagem suave. - Salários competitivos.

Esta UC (50h) cobre fundamentos sólidos até projecto prático real. Foco em escrever código Python idiomático (Pythonic).


1. Por que Python

1.1 História e filosofia

Ler na REPL: import this.

1.2 Características

1.3 Usos

Área Bibliotecas
Web Django, Flask, FastAPI
Data Analysis pandas, numpy
Machine Learning scikit-learn, TensorFlow, PyTorch
Visualization matplotlib, seaborn, plotly
Web Scraping BeautifulSoup, Scrapy
Automation Selenium, PyAutoGUI
DevOps Ansible, Fabric
Game Dev Pygame
Scientific SciPy, SymPy
GUI tkinter, PyQt

1.4 Carreira

Funções procuradas: - Backend Developer (Django, Flask, FastAPI). - Data Engineer / Analyst / Scientist. - ML Engineer. - DevOps Engineer. - Automation Engineer.

Salários médios em Portugal (2026): - Junior: 18-25 k€/ano. - Mid: 30-45 k€/ano. - Sénior: 50-80 k€/ano. - Em remote international: 2-3× isto.


2. Setup

2.1 Instalação

Windows / Mac / Linux: download python.org.

Garantir Python 3.10+ (3.11 ou 3.12 recomendado).

Verificar:

python --version
# Python 3.12.0

Em alguns sistemas, usar python3 se python é versão 2.

2.2 Editor / IDE

VSCode (recomendado gratuito): - Extensão "Python" da Microsoft. - Formatação, linting, debugging incluídos.

PyCharm (gratuito Community, pago Pro): - IDE poderosa. - Mais features que VSCode.

Jupyter Notebook: - Para data science. - Mistura código + texto + visualizações. - pip install jupyter.

2.3 REPL

$ python
Python 3.12.0
>>> print("Olá")
Olá
>>> 2 + 3
5
>>> exit()

REPL = Read-Eval-Print Loop. Útil para experimentar.

Avançado: IPython (pip install ipython).

2.4 Primeiro programa

hello.py:

nome = input("Como te chamas? ")
print(f"Olá, {nome}!")

Correr:

python hello.py

3. Sintaxe básica

3.1 Variáveis

nome = "Joana"       # str
idade = 25           # int
altura = 1.65        # float
estudante = True     # bool
nada = None          # NoneType

# Dynamic typing
x = 5
x = "agora string"  # OK

# Type hints (recomendado em código profissional)
nome: str = "Joana"
idade: int = 25

Convenções (PEP 8): - Variáveis: snake_case. - Constantes: MAIUSCULAS_SNAKE_CASE. - Classes: PascalCase.

3.2 Tipos básicos

int: inteiros (qualquer tamanho).

x = 42
y = 1_000_000  # readability
z = 10 ** 100  # números grandes OK

float: decimais (double precision).

pi = 3.14
e = 2.71828

str: strings (Unicode).

a = "hello"
b = 'hello'
c = """
multi-line
string
"""

bool: True / False.

ativo = True
desligado = False

None: ausência de valor.

resultado = None

3.3 Operadores

Aritméticos:

5 + 3       # 8
5 - 3       # 2
5 * 3       # 15
5 / 3       # 1.6666... (sempre float)
5 // 3      # 1 (divisão inteira)
5 % 3       # 2 (resto)
5 ** 3      # 125 (potência)

Comparação:

5 == 3      # False
5 != 3      # True
5 < 3       # False
5 > 3       # True
5 <= 5      # True
5 >= 5      # True

Lógicos:

True and False    # False
True or False     # True
not True          # False

# Short-circuit evaluation
x = 0
x and 1/x   # 0 (não dá ZeroDivisionError)

Atribuição composta:

x = 5
x += 1      # x = 6
x -= 2      # x = 4
x *= 3      # x = 12
x //= 4     # x = 3

3.4 Strings

Criar:

nome = "Python"
descricao = 'linguagem fixe'
longo = """
linha 1
linha 2
"""

Métodos comuns:

"olá".upper()           # 'OLÁ'
"OLÁ".lower()           # 'olá'
"hello".capitalize()    # 'Hello'
"hello world".title()   # 'Hello World'

"  espaço  ".strip()    # 'espaço'
"abc".replace("b","X")  # 'aXc'

"a,b,c".split(",")      # ['a', 'b', 'c']
",".join(['a','b'])     # 'a,b'

"abc".startswith("a")   # True
"abc".endswith("c")     # True
"abc".find("b")         # 1
"abc".count("a")        # 1

Indexing e slicing:

s = "Python"
s[0]        # 'P'
s[-1]       # 'n' (último)
s[0:3]      # 'Pyt'
s[3:]       # 'hon'
s[:3]       # 'Pyt'
s[::2]      # 'Pto' (step)
s[::-1]     # 'nohtyP' (reverso)

F-strings (recomendado, Python 3.6+):

nome = "Ana"
idade = 25
print(f"{nome} tem {idade} anos")
# Ana tem 25 anos

# Com formatação
preco = 19.99
print(f"Preço: {preco:.2f}€")  # Preço: 19.99€

# Com expressões
print(f"{nome.upper()} tem {idade * 2} anos no espelho")

3.5 Input/Output

# Output
print("Olá")
print("a", "b", "c")           # 'a b c'
print("a", "b", sep="-")       # 'a-b'
print("a", end="")             # sem newline

# Input (sempre string)
nome = input("Nome: ")
idade = int(input("Idade: "))  # converter para int
altura = float(input("Altura: "))

4. Estruturas de controlo

4.1 If / Elif / Else

idade = 18

if idade < 13:
    print("criança")
elif idade < 18:
    print("adolescente")
elif idade < 65:
    print("adulto")
else:
    print("sénior")

Importante: indentação obrigatória (4 espaços standard).

Operador ternário:

status = "adulto" if idade >= 18 else "menor"

Match-case (Python 3.10+):

match comando:
    case "start":
        iniciar()
    case "stop":
        parar()
    case _:
        print("desconhecido")

4.2 For

Iterar sequência:

for fruta in ["maçã", "banana", "uva"]:
    print(fruta)

Range:

for i in range(5):           # 0, 1, 2, 3, 4
    print(i)

for i in range(2, 10):       # 2 a 9
    print(i)

for i in range(0, 20, 2):    # 0, 2, 4, ...
    print(i)

Enumerate (índice + valor):

for i, fruta in enumerate(["a", "b", "c"]):
    print(f"{i}: {fruta}")

Zip (múltiplas listas):

nomes = ["Ana", "João"]
idades = [25, 30]
for nome, idade in zip(nomes, idades):
    print(f"{nome}: {idade}")

Iterar dicionário:

pessoa = {"nome": "Ana", "idade": 25}
for chave in pessoa:
    print(chave)
for chave, valor in pessoa.items():
    print(f"{chave}: {valor}")

4.3 While

contador = 0
while contador < 5:
    print(contador)
    contador += 1

Break / Continue:

while True:
    resp = input("Continuar? ")
    if resp == "n":
        break       # sai do loop
    if resp == "":
        continue    # próxima iteração
    print(resp)

Else em loops (executa se loop terminar sem break):

for i in range(10):
    if i == 100:
        break
else:
    print("Não encontrou 100")

4.4 Try / Except

try:
    numero = int(input("Número: "))
    resultado = 10 / numero
    print(resultado)
except ValueError:
    print("Não é número válido!")
except ZeroDivisionError:
    print("Não posso dividir por zero!")
except Exception as e:
    print(f"Erro inesperado: {e}")
else:
    print("Sem erros!")  # corre se try não tiver excepção
finally:
    print("Sempre corre, com ou sem erro")

Custom exceptions:

class IdadeInvalidaError(Exception):
    pass

def validar_idade(idade):
    if idade < 0:
        raise IdadeInvalidaError("Idade não pode ser negativa")
    return idade

5. Funções

5.1 Definir e chamar

def saudar(nome):
    return f"Olá, {nome}!"

mensagem = saudar("Ana")
print(mensagem)

5.2 Parâmetros

Default values:

def saudar(nome, saudacao="Olá"):
    return f"{saudacao}, {nome}!"

saudar("Ana")          # Olá, Ana!
saudar("Ana", "Hi")    # Hi, Ana!

Keyword arguments:

def criar_user(nome, idade, email):
    return {"nome": nome, "idade": idade, "email": email}

# Posicional
criar_user("Ana", 25, "ana@x.pt")

# Keyword (mais claro)
criar_user(nome="Ana", idade=25, email="ana@x.pt")

# Misto (posicional primeiro)
criar_user("Ana", idade=25, email="ana@x.pt")

Args e Kwargs variáveis:

def somar_todos(*numeros):
    return sum(numeros)

somar_todos(1, 2, 3, 4)    # 10

def info_pessoa(**dados):
    for k, v in dados.items():
        print(f"{k}: {v}")

info_pessoa(nome="Ana", idade=25, cidade="Lisboa")

5.3 Type hints

def soma(a: int, b: int) -> int:
    return a + b

def saudar(nome: str, formal: bool = False) -> str:
    if formal:
        return f"Bom dia, Senhor(a) {nome}"
    return f"Olá, {nome}"

# Em listas/dicts
from typing import List, Dict, Optional

def processar(items: List[int]) -> Dict[str, int]:
    return {"total": sum(items), "count": len(items)}

def buscar(id: int) -> Optional[str]:
    # retorna str ou None
    pass

5.4 Lambda

quadrado = lambda x: x ** 2
quadrado(5)  # 25

# Com map / filter / sorted
numeros = [3, 1, 4, 1, 5, 9, 2, 6]

quadrados = list(map(lambda x: x**2, numeros))
pares = list(filter(lambda x: x % 2 == 0, numeros))
ordenados = sorted(numeros, reverse=True)

# Com key personalizado
palavras = ["maçã", "banana", "uva"]
por_tamanho = sorted(palavras, key=lambda p: len(p))
# ['uva', 'maçã', 'banana']

5.5 Docstrings

def calcular_imc(peso: float, altura: float) -> float:
    """
    Calcula o Índice de Massa Corporal.

    Args:
        peso: peso em quilogramas.
        altura: altura em metros.

    Returns:
        IMC como float.

    Raises:
        ValueError: se altura for zero.

    Examples:
        >>> calcular_imc(70, 1.75)
        22.857142857142858
    """
    if altura == 0:
        raise ValueError("Altura não pode ser zero")
    return peso / (altura ** 2)

Aceder: help(calcular_imc).

5.6 Decorators

import time

def timing(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} demorou {end-start:.4f}s")
        return result
    return wrapper

@timing
def calcular_pesado(n):
    return sum(range(n))

calcular_pesado(1000000)
# calcular_pesado demorou 0.0234s

6. Estruturas de dados

6.1 Listas

# Criar
frutas = ["maçã", "banana", "uva"]
vazia = []
nums = list(range(5))  # [0, 1, 2, 3, 4]

# Acesso e slicing (igual strings)
frutas[0]      # "maçã"
frutas[-1]     # "uva"
frutas[1:]     # ["banana", "uva"]

# Modificar
frutas[0] = "kiwi"
frutas.append("laranja")
frutas.insert(0, "manga")
frutas.extend(["pêra", "ameixa"])

# Remover
frutas.remove("kiwi")     # primeiro com este valor
del frutas[0]             # por índice
ultima = frutas.pop()     # remove e retorna último
primeira = frutas.pop(0)  # remove e retorna primeiro

# Ordenar
frutas.sort()
frutas.sort(reverse=True)
frutas.sort(key=len)      # por comprimento

# Reverso
frutas.reverse()

# Procurar
frutas.index("banana")    # índice
frutas.count("uva")       # quantas vezes
"banana" in frutas        # True/False

# Comprimento
len(frutas)

# Cópia
copia = frutas.copy()
# ou: copia = frutas[:]
# ou: copia = list(frutas)

6.2 List comprehensions

# Em vez de:
quadrados = []
for x in range(10):
    quadrados.append(x ** 2)

# Forma idiomática:
quadrados = [x ** 2 for x in range(10)]

# Com condição
pares = [x for x in range(20) if x % 2 == 0]

# Aninhada
matriz = [[i*j for j in range(5)] for i in range(5)]

# Transformar dict em list
nomes = [pessoa["nome"] for pessoa in lista_pessoas]

# Achatar lista de listas
flatten = [item for sublist in lista_listas for item in sublist]

Boa prática: usar quando é simples. Para lógica complexa, usar for tradicional.

6.3 Tuplos

# Imutáveis (não podem ser alterados após criação)
ponto = (3, 4)
cores = ("vermelho", "verde", "azul")
um_elemento = (42,)  # vírgula obrigatória para 1 elem

# Acesso
ponto[0]   # 3

# Unpacking
x, y = ponto
r, g, b = cores

# Multiplos returns
def coordenadas():
    return 10, 20

x, y = coordenadas()

Usar tuplos quando: - Valor não muda (imutabilidade). - Heterogéneo (vários tipos). - Como chave de dict.

Usar listas quando: - Vai modificar. - Homogéneo (mesmo tipo). - Sequência de coisas similares.

6.4 Dicionários

# Criar
pessoa = {
    "nome": "Ana",
    "idade": 25,
    "cidade": "Lisboa"
}
vazio = {}
de_zip = dict(zip(["a", "b"], [1, 2]))

# Acesso
pessoa["nome"]                # "Ana"
pessoa.get("email")           # None (não levanta erro)
pessoa.get("email", "N/A")    # default

# Modificar / adicionar
pessoa["idade"] = 26
pessoa["email"] = "ana@x.pt"

# Remover
del pessoa["cidade"]
removido = pessoa.pop("idade")

# Iterar
for chave in pessoa:           # apenas chaves
    print(chave)

for valor in pessoa.values():
    print(valor)

for chave, valor in pessoa.items():
    print(f"{chave}: {valor}")

# Verificar
"nome" in pessoa     # True

# Tamanho
len(pessoa)

# Dict comprehension
quadrados = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

6.5 Sets

# Conjuntos (sem duplicados, sem ordem)
fruits = {"apple", "banana", "orange"}
duplicados = {1, 2, 2, 3, 3, 3}  # {1, 2, 3}
vazio = set()   # NÃO {} (é dict)

# Operações
fruits.add("kiwi")
fruits.remove("banana")
fruits.discard("nada")  # não erro se não existe

# Operações de conjuntos
a = {1, 2, 3}
b = {2, 3, 4}
a | b   # união: {1, 2, 3, 4}
a & b   # intersecção: {2, 3}
a - b   # diferença: {1}
a ^ b   # diferença simétrica: {1, 4}

# Verificar
2 in a    # True

Usar sets quando: - Procura rápida (in é O(1)). - Eliminar duplicados. - Operações matemáticas de conjuntos.


7. OOP — Programação Orientada a Objectos

7.1 Classes

class Pessoa:
    # Atributo de classe (partilhado)
    especie = "Homo sapiens"

    # Construtor
    def __init__(self, nome, idade):
        self.nome = nome    # atributos de instância
        self.idade = idade

    # Método
    def saudar(self):
        return f"Olá, sou {self.nome}"

    # Method magic / dunder
    def __repr__(self):
        return f"Pessoa(nome={self.nome!r}, idade={self.idade})"

    def __str__(self):
        return f"{self.nome} ({self.idade} anos)"

    def __eq__(self, other):
        return self.nome == other.nome and self.idade == other.idade

# Usar
ana = Pessoa("Ana", 25)
joao = Pessoa("João", 30)

print(ana.saudar())     # Olá, sou Ana
print(ana)              # Ana (25 anos)
print(repr(ana))        # Pessoa(nome='Ana', idade=25)

7.2 Herança

class Animal:
    def __init__(self, nome):
        self.nome = nome

    def fala(self):
        raise NotImplementedError("Subclasses devem implementar")

    def info(self):
        return f"Sou um animal chamado {self.nome}"

class Cao(Animal):
    def fala(self):
        return "Au au!"

    def trazer_bola(self):
        return f"{self.nome} traz a bola"

class Gato(Animal):
    def fala(self):
        return "Miau"

class Cachorro(Cao):  # Herança múltipla a nível
    def __init__(self, nome, idade):
        super().__init__(nome)
        self.idade = idade

    def fala(self):
        return "Auf!"  # cachorro fala diferente

    def info(self):
        return super().info() + f" e tenho {self.idade} meses"

rex = Cao("Rex")
print(rex.fala())   # Au au!
print(rex.info())   # Sou um animal chamado Rex

felix = Gato("Felix")
print(felix.fala()) # Miau

 = Cachorro("Zé", 3)
print(.fala())    # Auf!
print(.info())    # Sou um animal chamado Zé e tenho 3 meses

7.3 Encapsulamento

class ContaBancaria:
    def __init__(self, titular, saldo_inicial=0):
        self.titular = titular
        self._saldo = saldo_inicial   # _ = convenção "privado"
        self.__pin = "1234"           # __ = name mangling

    def depositar(self, valor):
        if valor <= 0:
            raise ValueError("Valor deve ser positivo")
        self._saldo += valor

    def levantar(self, valor):
        if valor <= 0:
            raise ValueError("Valor deve ser positivo")
        if valor > self._saldo:
            raise ValueError("Saldo insuficiente")
        self._saldo -= valor

    @property
    def saldo(self):
        return self._saldo

    @saldo.setter
    def saldo(self, valor):
        raise AttributeError("Não pode modificar saldo directamente")

conta = ContaBancaria("Ana", 1000)
print(conta.saldo)  # 1000

conta.depositar(500)
print(conta.saldo)  # 1500

conta.levantar(200)
print(conta.saldo)  # 1300

# conta.saldo = 1000000  # AttributeError!

7.4 Polimorfismo

def fazer_falar(animal):
    print(animal.fala())

animais = [Cao("Rex"), Gato("Felix"), Cao("Bobi")]

for animal in animais:
    fazer_falar(animal)
# Au au!
# Miau
# Au au!

Mesma interface, comportamentos diferentes.

7.5 Classmethod e Staticmethod

class Pessoa:
    contador = 0

    def __init__(self, nome):
        self.nome = nome
        Pessoa.contador += 1

    @classmethod
    def total(cls):
        return cls.contador

    @staticmethod
    def é_maior_idade(idade):
        return idade >= 18

# Usar
ana = Pessoa("Ana")
joao = Pessoa("João")

print(Pessoa.total())                # 2
print(Pessoa.é_maior_idade(20))      # True

7.6 Dataclasses (Python 3.7+)

from dataclasses import dataclass

@dataclass
class Ponto:
    x: float
    y: float

    def distancia(self, outro):
        return ((self.x - outro.x)**2 + (self.y - outro.y)**2)**0.5

p1 = Ponto(0, 0)
p2 = Ponto(3, 4)

print(p1)               # Ponto(x=0, y=0)
print(p1.distancia(p2)) # 5.0
print(p1 == Ponto(0,0)) # True (equality automático)

Reduz boilerplate.


8. Bibliotecas essenciais

8.1 Standard library

math:

import math
math.sqrt(16)           # 4.0
math.pi                 # 3.14159...
math.ceil(4.2)          # 5
math.floor(4.8)         # 4
math.factorial(5)       # 120

random:

import random
random.random()              # 0.0 a 1.0
random.randint(1, 10)        # 1 a 10 inclusive
random.choice(["a","b"])     # escolhe um
random.shuffle(lista)        # baralha (in-place)
random.sample(lista, 3)      # 3 únicos

datetime:

from datetime import datetime, timedelta, date

agora = datetime.now()
hoje = date.today()
amanha = agora + timedelta(days=1)

formato = agora.strftime("%Y-%m-%d %H:%M")
parsed = datetime.strptime("2026-05-22", "%Y-%m-%d")

os / pathlib:

import os
from pathlib import Path

# Moderno: pathlib
p = Path("dados/file.txt")
p.exists()
p.read_text()
p.write_text("conteúdo")
p.suffix      # .txt
p.parent      # Path("dados")

for f in Path(".").glob("*.py"):
    print(f)

json:

import json

# Dict → JSON string
data = {"nome": "Ana", "idade": 25}
json_str = json.dumps(data, indent=2)

# JSON string → dict
data = json.loads('{"x": 1, "y": 2}')

# Ficheiro
with open("data.json") as f:
    data = json.load(f)

with open("out.json", "w") as f:
    json.dump(data, f, indent=2)

collections:

from collections import Counter, defaultdict, namedtuple

# Counter
votes = ["a", "b", "a", "c", "a", "b"]
Counter(votes)  # Counter({'a': 3, 'b': 2, 'c': 1})

# defaultdict
dd = defaultdict(list)
dd["chave"].append(1)  # não erro se não existe

# namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x  # 3

8.2 Ficheiros

# Ler
with open("arquivo.txt", "r", encoding="utf-8") as f:
    conteudo = f.read()              # tudo
    linhas = f.readlines()           # lista de linhas
    # ou iterar
    for linha in f:
        print(linha.strip())

# Escrever
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Olá\n")
    f.writelines(["linha 1\n", "linha 2\n"])

# Anexar
with open("log.txt", "a") as f:
    f.write("nova entrada\n")

# CSV
import csv

with open("dados.csv") as f:
    reader = csv.DictReader(f)
    for linha in reader:
        print(linha["nome"], linha["idade"])

with open("saida.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["nome", "idade"])
    writer.writeheader()
    writer.writerow({"nome": "Ana", "idade": 25})

8.3 pip + venv

# Criar venv
python -m venv venv

# Activar
source venv/bin/activate     # Linux/Mac
venv\Scripts\activate        # Windows

# Instalar pacotes
pip install requests pandas numpy

# Listar
pip list

# Guardar
pip freeze > requirements.txt

# Instalar de requirements
pip install -r requirements.txt

# Desactivar
deactivate

Sempre usar venv por projecto (evita conflitos).

8.4 requests (HTTP)

import requests

# GET
r = requests.get("https://api.github.com/users/joaosilva")
data = r.json()
print(data["name"])

# POST
r = requests.post(
    "https://api.example.com/users",
    json={"nome": "Ana", "email": "ana@x.pt"},
    headers={"Authorization": "Bearer token"}
)

# Verificar status
if r.status_code == 200:
    print("OK")
else:
    print(f"Erro: {r.status_code}")

8.5 pandas (data analysis)

import pandas as pd

# Ler CSV
df = pd.read_csv("dados.csv")

# Explorar
df.head()           # primeiras 5 linhas
df.info()           # info estrutural
df.describe()       # estatísticas
df.shape            # (linhas, colunas)
df.columns          # nome colunas

# Seleccionar
df["nome"]          # 1 coluna
df[["nome", "idade"]]  # várias

# Filtrar
df[df["idade"] > 25]
df[(df["idade"] > 25) & (df["cidade"] == "Lisboa")]

# Agrupar
df.groupby("cidade")["idade"].mean()

# Modificar
df["idade_dobro"] = df["idade"] * 2
df.dropna()  # remover NaN
df.fillna(0)  # substituir NaN

# Guardar
df.to_csv("output.csv", index=False)
df.to_excel("output.xlsx")

8.6 matplotlib (visualização)

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]

# Linha
plt.plot(x, y)
plt.title("Meu Gráfico")
plt.xlabel("X")
plt.ylabel("Y")
plt.show()

# Bar chart
categorias = ["A", "B", "C"]
valores = [10, 20, 15]
plt.bar(categorias, valores)
plt.show()

# Scatter
plt.scatter([1,2,3], [4,5,6])
plt.show()

# Guardar
plt.savefig("grafico.png")

9. Boas práticas (PEP 8)

9.1 Estilo

# Bom
def calcular(x, y):
    """Soma dois números."""
    return x + y


def subtrair(x, y):
    """Subtrai dois números."""
    return x - y

9.2 Naming conventions

# Variáveis e funções: snake_case
nome_completo = "Ana Silva"
def calcular_imc(peso, altura):
    pass

# Classes: PascalCase
class ContaBancaria:
    pass

# Constantes: UPPER_SNAKE_CASE
PI = 3.14159
MAX_USERS = 100

# Privado: prefixo _
self._saldo = 0

# Muito privado (name mangling): __
self.__pin = "1234"

9.3 Docstrings

def funcao(arg1: int, arg2: str) -> bool:
    """
    Breve descrição em uma linha.

    Descrição mais detalhada se necessário, podendo
    estender por várias linhas.

    Args:
        arg1: descrição do primeiro arg.
        arg2: descrição do segundo arg.

    Returns:
        Descrição do retorno.

    Raises:
        ValueError: se algo correr mal.
    """
    pass

9.4 Ferramentas

Black — formatador automático:

pip install black
black meu_codigo.py

flake8 — linter:

pip install flake8
flake8 meu_codigo.py

mypy — type checker:

pip install mypy
mypy meu_codigo.py

pytest — testes:

pip install pytest
pytest test_arquivo.py

Usar pre-commit hooks para automatizar.


10. Projecto exemplo

10.1 Estrutura

meu-projecto/
├── venv/                (não commitar)
├── src/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   └── utils.py
├── tests/
│   ├── __init__.py
│   ├── test_main.py
│   └── test_utils.py
├── docs/
│   └── README.md
├── .gitignore
├── README.md
├── requirements.txt
└── pyproject.toml

10.2 Exemplo: CLI Todo App

src/main.py:

import json
from pathlib import Path
from datetime import datetime

DB_PATH = Path("todos.json")


def carregar_todos():
    """Carrega todos do ficheiro JSON."""
    if DB_PATH.exists():
        return json.loads(DB_PATH.read_text())
    return []


def guardar_todos(todos):
    """Guarda todos no ficheiro JSON."""
    DB_PATH.write_text(json.dumps(todos, indent=2, default=str))


def adicionar(texto):
    """Adiciona novo todo."""
    todos = carregar_todos()
    todos.append({
        "id": len(todos) + 1,
        "texto": texto,
        "feito": False,
        "criado": datetime.now().isoformat()
    })
    guardar_todos(todos)
    print(f"✓ Adicionado: {texto}")


def listar():
    """Lista todos os todos."""
    todos = carregar_todos()
    if not todos:
        print("Sem todos.")
        return

    for t in todos:
        check = "✓" if t["feito"] else " "
        print(f"[{check}] {t['id']}. {t['texto']}")


def marcar_feito(id):
    """Marca todo como feito."""
    todos = carregar_todos()
    for t in todos:
        if t["id"] == id:
            t["feito"] = True
            guardar_todos(todos)
            print(f"✓ {t['texto']} marcado como feito")
            return
    print(f"Todo {id} não encontrado")


def main():
    """Loop principal."""
    while True:
        print("\n--- TODO ---")
        print("1. Adicionar")
        print("2. Listar")
        print("3. Marcar feito")
        print("0. Sair")

        escolha = input("Opção: ")

        if escolha == "1":
            texto = input("O quê? ")
            adicionar(texto)
        elif escolha == "2":
            listar()
        elif escolha == "3":
            id = int(input("ID: "))
            marcar_feito(id)
        elif escolha == "0":
            break


if __name__ == "__main__":
    main()

Correr: python src/main.py.

10.3 Testes

tests/test_main.py:

import pytest
from src.main import carregar_todos, guardar_todos


def test_carregar_vazio(tmp_path, monkeypatch):
    monkeypatch.setattr("src.main.DB_PATH", tmp_path / "todos.json")
    assert carregar_todos() == []


def test_guardar_e_carregar(tmp_path, monkeypatch):
    monkeypatch.setattr("src.main.DB_PATH", tmp_path / "todos.json")
    todos = [{"id": 1, "texto": "teste", "feito": False}]
    guardar_todos(todos)
    assert carregar_todos() == todos

Correr: pytest.


Apêndice A · PEP 20 — Zen of Python

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Correr: import this.


Apêndice B · Recursos

Livros: - "Automate the Boring Stuff with Python" — Al Sweigart (gratuito online). - "Python Crash Course" — Eric Matthes. - "Fluent Python" — Luciano Ramalho (avançado). - "Effective Python" — Brett Slatkin.

Cursos online: - freeCodeCamp: Python for Everybody (gratuito). - Coursera: Python for Everybody (Michigan). - Real Python: realpython.com (artigos + cursos).

Prática: - HackerRank: hackerrank.com/domains/python. - LeetCode: leetcode.com (interview prep). - Codewars: codewars.com. - Exercism: exercism.org (mentoria).

Documentação: - Oficial: docs.python.org. - Python Package Index: pypi.org.


Apêndice C · Glossário