40. Escrevendo Código Limpo e Legível
Ao longo deste livro, você aprendeu a sintaxe do Python, estruturas de dados, fluxo de controle, funções, classes e muitos outros conceitos de programação. Agora você consegue escrever programas que funcionam. Mas existe uma diferença crucial entre código que funciona e código que é manutenível — código que você e outras pessoas conseguem entender, modificar e depurar meses ou anos depois.
Este capítulo foca em escrever código limpo e legível. Você vai aprender as convenções e práticas que tornam código Python profissional e manutenível. Isso não são apenas regras arbitrárias — são diretrizes testadas na prática que facilitam a colaboração, reduzem bugs e ajudam você a entender o seu próprio código quando voltar a ele mais tarde.
40.1) Por que Estilo Importa: Ler vs. Escrever Código
40.1.1) Código é Lido com Mais Frequência do que Escrito
Quando você escreve código, você passa minutos ou horas criando-o. Mas esse código será lido muitas vezes: quando você depura, quando adiciona funcionalidades, quando outros desenvolvedores trabalham com ele e quando você volta a ele meses depois tentando lembrar o que ele faz.
Considere este código que funciona, mas tem um estilo ruim:
# AVISO: Estilo ruim - apenas para demonstração
def c(l):
t=0
for i in l:
t=t+i
return t/len(l)
data=[85,92,78,90,88]
result=c(data)
print(result) # Output: 86.6Esse código funciona perfeitamente. Ele calcula a média de uma lista de números. Mas entender o que ele faz exige uma análise cuidadosa. Agora compare com esta versão:
def calculate_average(numbers):
"""Calculate the arithmetic mean of a list of numbers."""
total = 0
for number in numbers:
total = total + number
return total / len(numbers)
test_scores = [85, 92, 78, 90, 88]
average_score = calculate_average(test_scores)
print(average_score) # Output: 86.6O que torna a segunda versão melhor?
- O nome da função (
calculate_average) deixa o propósito claro - Os nomes das variáveis (
numbers,total,test_scores) são descritivos - A docstring explica o que a função faz
- O espaçamento adequado deixa a estrutura clara
- Qualquer pessoa consegue entender esse código sem estudá-lo
As duas versões produzem resultados idênticos, mas a segunda versão é imediatamente compreensível.
O insight principal: você escreve código uma vez, mas você o lê dezenas ou centenas de vezes. Investir alguns segundos a mais em nomes e formatação claros economiza horas de confusão depois.
40.1.2) Legibilidade Reduz Bugs
Código claro é mais fácil de depurar porque você consegue entender rapidamente o que cada parte faz. Quando os nomes de variáveis são descritivos e a estrutura é limpa, você consegue identificar erros de lógica com mais facilidade.
# Difícil de depurar - o que essas variáveis representam?
# AVISO: Estilo ruim - apenas para demonstração
def process(x, y):
if x > y:
return x * (1 - y)
return x
result = process(100, 0.1)# Fácil de depurar - fica claro o que está acontecendo
def apply_discount(price, discount_rate):
"""Calculate price after applying discount rate (0.0 to 1.0)."""
discount_amount = price * discount_rate
final_price = price - discount_amount
return final_price
original_price = 100
discount = 0.1 # desconto de 10%
final_price = apply_discount(original_price, discount)
print(f"Final price: ${final_price}")
# Output: Final price: $90.0Na segunda versão, você consegue ver imediatamente a lógica: "Estamos calculando um valor de desconto e depois subtraindo esse valor do preço." Na primeira versão, você precisa acompanhar mentalmente o que x e y representam e descobrir o que x * (1 - y) significa.
40.1.3) Consistência Permite Colaboração
Quando todo mundo em um time segue as mesmas convenções de estilo, o código fica previsível. Você não gasta energia mental decifrando estilos de formatação diferentes — você pode focar em entender a lógica.
O Python tem um guia de estilo oficial chamado PEP 8 (Python Enhancement Proposal 8). A PEP 8 define convenções para:
- Como nomear variáveis, funções e classes
- Como formatar código (espaçamento, comprimento de linha, indentação)
- Quando usar comentários e docstrings
- Como organizar imports
Seguir a PEP 8 significa que seu código vai parecer familiar para outros programadores Python, deixando a colaboração mais tranquila. Vamos cobrir as diretrizes essenciais da PEP 8 nas próximas seções.
40.2) Convenções de Nomenclatura: Variáveis, Funções e Classes (PEP 8)
40.2.1) Princípios Gerais de Nomenclatura
Bons nomes são descritivos e não ambíguos. Eles devem dizer o que algo representa ou faz sem exigir que você leia a implementação.
Princípios-chave:
- Use palavras completas, não abreviações (exceto as muito comuns como
id,url,html) - Seja específico:
user_counté melhor quecount,calculate_total_priceé melhor quecalculate - Evite nomes de uma letra, exceto em loops muito curtos ou fórmulas matemáticas
- Não inclua informação de tipo nos nomes (Python é dinamicamente tipado)
# Nomes ruins - não fica claro o que representam
# AVISO: Estilo ruim - apenas para demonstração
# O que é 'n'? Um número? Um nome? Um nó?
# O que é 'd'? Uma data? Uma distância? Uma duração?
# O que é 'l'? Parece o número 1!
n = "Alice"
d = 25
l = [1, 2, 3]
calc = lambda x: x * 2
# Bons nomes - claros e descritivos
student_name = "Alice"
age_in_years = 25
test_scores = [1, 2, 3]
double_value = lambda x: x * 2Exceção: variáveis curtas em loops
# Aceitável: bem curto, contexto claro
for i in range(10):
print(i)
for x, y in coordinates:
distance = (x**2 + y**2) ** 0.5
# Mas prefira nomes descritivos para clareza
for student_index in range(len(students)):
print(students[student_index])
for point_x, point_y in coordinates:
distance = (point_x**2 + point_y**2) ** 0.540.2.2) Nomes de Variáveis e Funções: snake_case
Em Python, variáveis e funções usam snake_case: tudo em minúsculas com palavras separadas por underscores.
# Variáveis
user_name = "Bob"
total_price = 99.99
is_valid = True
max_retry_count = 3
# Funções
def calculate_tax(amount, rate):
"""Calculate tax on a given amount."""
return amount * rate
def send_email_notification(recipient, message):
"""Send an email to the specified recipient."""
print(f"Sending to {recipient}: {message}")
# Usando as funções
tax_amount = calculate_tax(100, 0.08)
send_email_notification("user@example.com", "Welcome!")Por que snake_case? É altamente legível. Os underscores criam limites claros entre palavras, deixando os nomes fáceis de “varrer” com os olhos. Compare calculatetotalprice (difícil de ler) com calculate_total_price (imediatamente claro).
40.2.3) Nomes de Constantes: UPPER_SNAKE_CASE
Constantes — valores que não deveriam mudar durante a execução do programa — usam UPPER_SNAKE_CASE: tudo em maiúsculas com underscores.
# Constantes no nível do módulo
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
PI = 3.14159
DATABASE_URL = "postgresql://localhost/mydb"
def validate_password_length(password):
"""Check if password meets minimum length requirement."""
MIN_PASSWORD_LENGTH = 8 # Constante dentro da função
return len(password) >= MIN_PASSWORD_LENGTH
# Usando constantes
if login_attempts > MAX_LOGIN_ATTEMPTS:
print("Account locked")Importante: Python não tem sintaxe embutida para constantes. Diferente de algumas linguagens (como const em JavaScript ou final em Java), Python não tem um jeito de declarar que uma variável não pode ser alterada.
Em vez disso, programadores Python usam uma convenção de nomenclatura para sinalizar intenção:
UPPER_SNAKE_CASEsignifica: "Eu pretendo que isso seja uma constante — não modifique"- Isso é uma ferramenta de comunicação entre programadores, não um recurso da linguagem
# Python não tem sintaxe de constante - isto é apenas uma variável normal
MAX_LOGIN_ATTEMPTS = 3
# Python não vai impedir você de modificá-la
MAX_LOGIN_ATTEMPTS = 5 # ❌ Tecnicamente funciona, mas viola a convenção
# A convenção de nomenclatura é um sinal de INTENÇÃO:
# "Eu nomeei isto em MAIÚSCULAS para mostrar que não quero que seja alterado"Boa prática: se um valor realmente precisa mudar durante a execução do programa, não o nomeie como uma constante:
# Este valor vai mudar - use minúsculas
max_login_attempts = 3
max_login_attempts = 5 # ✅ OK - o nome indica que pode mudar
# Este valor nunca deveria mudar - use MAIÚSCULAS
MAX_LOGIN_ATTEMPTS = 3
# Não reatribua depois no códigoA convenção ajuda programadores a entenderem sua intenção e evitarem bugs. Quando você vê MAX_LOGIN_ATTEMPTS, você sabe que não deve modificar isso.
40.2.4) Nomes de Classes: PascalCase
Nomes de classes usam PascalCase (também chamado CapWords): cada palavra começa com letra maiúscula, sem underscores.
# Definições de classes
class Student:
"""Represent a student with name and grades."""
def __init__(self, name):
self.name = name
self.grades = []
class ShoppingCart:
"""Manage items in a shopping cart."""
def __init__(self):
self.items = []
def add_item(self, item):
"""Add an item to the cart."""
self.items.append(item)
class DatabaseConnection:
"""Handle database connection and queries."""
def __init__(self, url):
self.url = url
# Criando instâncias (observação: instâncias usam nomes de variável em snake_case)
student = Student("Alice")
shopping_cart = ShoppingCart()
db_connection = DatabaseConnection("localhost")Por que PascalCase para classes? Isso distingue visualmente classes de funções e variáveis. Quando você vê Student(), você sabe imediatamente que está criando uma instância de uma classe. Quando você vê calculate_average(), você sabe que está chamando uma função.
40.2.5) Nomes Privados e Internos: Underscore no Início
Nomes que começam com um único underscore (_name) indicam uso interno — eles são feitos para uso dentro do módulo ou da classe, não por código externo.
Python não tem sintaxe para marcar métodos ou atributos como "privados" (diferente de private em Java ou C++). Em vez disso, Python usa uma convenção de nomenclatura com um underscore no início (_name) para comunicar intenção.
O que _name significa:
- "Isso é para uso interno apenas"
- "Eu fiz isso para usar dentro desta classe/módulo, não para código externo"
- "Isso pode mudar a qualquer momento em versões futuras — não dependa disso"
class BankAccount:
"""Represent a bank account with balance tracking."""
def __init__(self, account_number, initial_balance):
self.account_number = account_number
self._balance = initial_balance # Atributo interno
def deposit(self, amount):
"""Add money to the account."""
if self._validate_amount(amount): # Método interno
self._balance += amount
def _validate_amount(self, amount):
"""Internal helper to validate transaction amounts."""
return amount > 0
def get_balance(self):
"""Return the current balance."""
return self._balance
# Usando a classe
account = BankAccount("12345", 1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# Tecnicamente funciona, mas viola a convenção
print(account._balance)
# Output: 1500 (works, but you shouldn't do this!)
# Tecnicamente funciona, mas viola a convenção
result = account._validate_amount(100)
# Output: True (works, but you shouldn't do this!)Ponto-chave: Python não consegue impedir você de acessar _balance ou chamar _validate_amount(). O underscore é um sinal entre programadores, não um recurso de segurança.
Por que Essa Convenção Existe
Como Python não consegue impor privacidade, o underscore é como autores de classes comunicam sua intenção:
O que o underscore sinaliza:
- "Isto é implementação interna — pode mudar em versões futuras"
- "Use os métodos públicos — eles têm garantia de permanecer estáveis"
- "Se você depender de detalhes internos, seu código pode quebrar quando eu atualizar a biblioteca"
A convenção cria um contrato: autores de classes podem mudar livremente a implementação interna (qualquer coisa com _), mas precisam manter a interface pública estável. Isso permite que bibliotecas evoluam sem quebrar o código de quem usa.
40.2.6) Nomes Especiais: Underscores Duplos
Nomes com underscores duplos no começo e no fim (__name__) são métodos especiais ou métodos mágicos definidos pelo Python. Não crie seus próprios nomes com esse padrão — ele é reservado para uso do Python.
class Point:
"""Represent a 2D point."""
def __init__(self, x, y): # Método especial: inicialização
self.x = x
self.y = y
def __str__(self): # Método especial: representação em string
return f"Point({self.x}, {self.y})"
def __add__(self, other): # Método especial: operador de adição
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1) # Output: Point(1, 2)
print(p1 + p2) # Output: Point(4, 6)Como aprendemos no Capítulo 31, esses métodos especiais permitem sobrecarga de operadores e integração com as funções embutidas do Python.
40.2.7) Tabela-Resumo de Nomenclatura
| Tipo | Convenção | Exemplo |
|---|---|---|
| Variáveis | snake_case | user_name, total_count |
| Funções | snake_case | calculate_tax(), send_email() |
| Constantes | UPPER_SNAKE_CASE | MAX_SIZE, DEFAULT_TIMEOUT |
| Classes | PascalCase | Student, ShoppingCart |
| Interno/Privado | _leading_underscore | _balance, _validate() |
| Especial/Mágico | double_underscore | __init__, __str__ |
40.3) Layout de Código: Indentação, Espaçamento e Linhas em Branco
40.3.1) Indentação: Quatro Espaços
Python usa indentação para definir blocos de código. Sempre use 4 espaços por nível de indentação — nunca tabs, e nunca misture tabs e espaços.
def calculate_grade(score):
"""Determine letter grade from numeric score."""
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
else:
return "F"
# Indentação aninhada: 4 espaços por nível
def process_students(students):
"""Process a list of student records."""
for student in students:
if student["active"]:
grade = calculate_grade(student["score"])
print(f"{student['name']}: {grade}")
students = [
{"name": "Alice", "score": 92, "active": True},
{"name": "Bob", "score": 78, "active": True}
]
process_students(students)
# Output:
# Alice: A
# Bob: CPor que 4 espaços? É o padrão da comunidade Python. A maior parte do código Python que você encontrar usa 4 espaços, então seguir essa convenção torna seu código consistente com o ecossistema.
Configurando seu editor: editores modernos podem ser configurados para inserir 4 espaços quando você aperta Tab. Isso dá a conveniência da tecla Tab enquanto mantém o padrão de 4 espaços.
40.3.2) Comprimento Máximo de Linha: 79 Caracteres
A PEP 8 recomenda limitar linhas a 79 caracteres (com até 99 caracteres para docstrings e comentários). Isso pode parecer restritivo, mas tem benefícios práticos:
- O código continua legível em telas menores
- Você pode ver dois arquivos lado a lado
- Isso incentiva quebrar expressões complexas em partes mais simples
Observação: muitos projetos modernos usam limites um pouco maiores (88, 100 ou 120 caracteres). O ponto principal é a consistência dentro do seu projeto. Escolha um limite e mantenha-o.
# Muito longo - difícil de ler
# AVISO: Estilo ruim - apenas para demonstração
def calculate_monthly_payment(principal, annual_rate, years):
return principal * (annual_rate / 12) * (1 + annual_rate / 12) ** (years * 12) / ((1 + annual_rate / 12) ** (years * 12) - 1)
# Melhor - quebrado em linhas legíveis
def calculate_monthly_payment(principal, annual_rate, years):
"""Calculate monthly loan payment using amortization formula."""
monthly_rate = annual_rate / 12
num_payments = years * 12
numerator = principal * monthly_rate * (1 + monthly_rate) ** num_payments
denominator = (1 + monthly_rate) ** num_payments - 1
return numerator / denominator
payment = calculate_monthly_payment(200000, 0.045, 30)
print(f"Monthly payment: ${payment:.2f}") # Output: Monthly payment: $1013.37Quebrando linhas longas: quando você precisar quebrar uma linha, use continuação implícita dentro de parênteses, colchetes ou chaves:
# Chamada de função longa
result = some_function(
first_argument,
second_argument,
third_argument,
fourth_argument
)
# Lista longa
colors = [
"red", "green", "blue",
"yellow", "orange", "purple",
"pink", "brown", "gray"
]
# String longa
message = (
"This is a very long message that needs to be broken "
"across multiple lines for readability. Python automatically "
"concatenates adjacent string literals."
)
print(message)
# Output: This is a very long message that needs to be broken across multiple lines for readability. Python automatically concatenates adjacent string literals.40.3.3) Espaçamento ao Redor de Operadores e Depois de Vírgulas
Use espaços ao redor de operadores e depois de vírgulas para melhorar a legibilidade:
# Espaçamento ruim - apertado e difícil de ler
# AVISO: Estilo ruim - apenas para demonstração
x=5
y=x*2+3
result=calculate_tax(100,0.08)
data=[1,2,3,4,5]
# Bom espaçamento - claro e legível
x = 5
y = x * 2 + 3
result = calculate_tax(100, 0.08)
data = [1, 2, 3, 4, 5]
# Espaçamento em expressões
total = (price * quantity) + shipping_cost
is_valid = (age >= 18) and (has_license == True)
# Espaçamento em definições de função
def calculate_discount(price, discount_rate, minimum_purchase=0):
"""Calculate discounted price if minimum purchase is met."""
if price >= minimum_purchase:
return price * (1 - discount_rate)
return priceExceção: não use espaços ao redor de = em argumentos nomeados (keyword arguments) ou em valores padrão de parâmetros:
# Espaçamento correto para keyword arguments
result = calculate_discount(price=100, discount_rate=0.1, minimum_purchase=50)
# Espaçamento correto para parâmetros padrão
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"40.3.4) Linhas em Branco para Separação Lógica
Use linhas em branco para separar seções lógicas do código:
Duas linhas em branco entre funções e classes de nível superior:
def first_function():
"""First function."""
pass
def second_function():
"""Second function."""
pass
class MyClass:
"""A class definition."""
passUma linha em branco entre métodos dentro de uma classe:
class Student:
"""Represent a student with grades."""
def __init__(self, name):
self.name = name
self.grades = []
def add_grade(self, grade):
"""Add a grade to the student's record."""
self.grades.append(grade)
def get_average(self):
"""Calculate the student's grade average."""
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)Linhas em branco dentro de funções para separar etapas lógicas:
def process_order(order_items, customer):
"""Process a customer order and calculate total."""
# Calcular subtotal
subtotal = 0
for item in order_items:
subtotal += item["price"] * item["quantity"]
# Aplicar desconto do cliente
discount = 0
if customer["is_premium"]:
discount = subtotal * 0.1
# Calcular imposto
tax = (subtotal - discount) * 0.08
# Calcular total final
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}Essas linhas em branco funcionam como “parágrafos” visuais, deixando a estrutura do código imediatamente evidente.
40.3.5) Evitando Espaços em Branco no Fim da Linha
Não deixe espaços no final das linhas — eles são invisíveis, mas podem causar problemas com sistemas de controle de versão e alguns editores.
# Ruim - espaços invisíveis no fim da linha (mostrados como · para ilustração)
# AVISO: Estilo ruim - apenas para demonstração
def calculate(x):···
return x * 2···
# Bom - sem espaços no fim da linha
def calculate(x):
return x * 2A maioria dos editores modernos pode ser configurada para remover automaticamente os espaços em branco no fim da linha quando você salva um arquivo.
40.4) Documentação: Escrevendo Comentários e Docstrings Úteis
40.4.1) Quando Escrever Comentários
Comentários explicam por que o código faz algo, não o que ele faz. Variáveis e funções bem nomeadas deveriam tornar o “o que” óbvio.
# Comentário ruim - diz o óbvio
# AVISO: Estilo ruim - apenas para demonstração
x = x + 1 # Adicionar 1 a x
# Bom comentário - explica o porquê
x = x + 1 # Ajustar para indexação baseada em zero
# Comentário ruim - redundante com o código
# AVISO: Estilo ruim - apenas para demonstração
# Verificar se a idade é maior ou igual a 18
if age >= 18:
print("Adult")
# Bom comentário - explica a lógica de negócio
# Idade legal para beber nos EUA
if age >= 21:
print("Can purchase alcohol")Quando comentários são valiosos:
- Explicando algoritmos complexos:
def binary_search(sorted_list, target):
"""Search for target in sorted list using binary search."""
left = 0
right = len(sorted_list) - 1
while left <= right:
# Calcular o ponto do meio, evitando overflow de inteiros
# (right + left) // 2 poderia estourar com índices muito grandes
mid = left + (right - left) // 2
if sorted_list[mid] == target:
return mid
elif sorted_list[mid] < target:
left = mid + 1 # O target está na metade direita
else:
right = mid - 1 # O target está na metade esquerda
return -1 # Target não encontrado- Esclarecendo regras de negócio não óbvias:
def calculate_shipping_cost(weight, distance):
"""Calculate shipping cost based on weight and distance."""
base_cost = 5.00
# Promoção de frete grátis para itens pesados (política da empresa em 2024)
# Isso incentiva pedidos em volume e reduz custos de frete por unidade
if weight > 50:
return 0
# Tarifa padrão: $0.50 por libra mais $0.10 por milha
# Baseado no contrato com a transportadora negociado no Q1 2024
return base_cost + (weight * 0.50) + (distance * 0.10)- Documentando contornos (workarounds) ou soluções temporárias:
def process_data(data):
"""Process incoming data records."""
# TODO: Esta é uma correção temporária para registros malformados
# Remover quando a validação de dados for implementada na origem
if not isinstance(data, list):
data = [data]
for record in data:
# Processar cada registro
pass40.4.2) Escrevendo Docstrings Eficazes
Docstrings são comentários especiais que documentam módulos, classes e funções. Elas ficam entre aspas triplas e aparecem como a primeira instrução na definição.
def calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index (BMI).
BMI is calculated as weight in kilograms divided by the square of height in meters.
Args:
weight_kg: Weight in kilograms (float or int)
height_m: Height in meters (float or int)
Returns:
float: The calculated BMI value
Example:
>>> calculate_bmi(70, 1.75)
22.857142857142858
"""
return weight_kg / (height_m ** 2)
# Acessando docstrings
print(calculate_bmi.__doc__)
# Output:
# Calculate Body Mass Index (BMI).
#
# BMI is calculated as weight in kilograms divided by the square of height in meters.
# ...Docstrings de uma linha para funções simples:
def square(x):
"""Return the square of x."""
return x * x
def is_even(n):
"""Return True if n is even, False otherwise."""
return n % 2 == 0Docstrings de múltiplas linhas para funções complexas:
def find_prime_factors(n):
"""
Find all prime factors of a positive integer.
This function returns a list of prime numbers that, when multiplied
together, equal the input number. The factors are returned in ascending order.
Args:
n: A positive integer greater than 1
Returns:
list: Prime factors in ascending order
Raises:
ValueError: If n is less than 2
Example:
>>> find_prime_factors(12)
[2, 2, 3]
>>> find_prime_factors(17)
[17]
"""
if n < 2:
raise ValueError("n must be at least 2")
factors = []
divisor = 2
while n > 1:
while n % divisor == 0:
factors.append(divisor)
n = n // divisor
divisor += 1
return factorsDocstrings de classe:
class BankAccount:
"""
Represent a bank account with deposit and withdrawal operations.
This class maintains an account balance and provides methods for
depositing and withdrawing money. All transactions are validated to prevent negative balances.
Attributes:
account_number: Unique identifier for the account
balance: Current account balance in dollars
"""
def __init__(self, account_number, initial_balance=0):
"""
Initialize a new bank account.
Args:
account_number: Unique account identifier (string)
initial_balance: Starting balance (default: 0)
"""
self.account_number = account_number
self.balance = initial_balance
def deposit(self, amount):
"""
Add money to the account.
Args:
amount: Amount to deposit (must be positive)
Raises:
ValueError: If amount is not positive
"""
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount40.4.3) Convenções de Docstrings
Primeira linha: resumo breve do que a função/classe faz. Deve caber em uma linha.
Linha em branco: separa o resumo da descrição detalhada.
Descrição detalhada: explica o que a função faz, quaisquer detalhes importantes e como usar.
Args/Parameters: lista cada parâmetro com seu tipo e finalidade.
Returns: descreve o que a função retorna e seu tipo.
Raises: documenta quaisquer exceções que a função pode levantar.
Example: mostra um uso típico (opcional, mas útil).
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
"""
Calculate compound interest on an investment.
Uses the compound interest formula: A = P(1 + r/n)^(nt)
where A is the final amount, P is principal, r is annual rate,
n is compounds per year, and t is time in years.
Args:
principal: Initial investment amount (float)
rate: Annual interest rate as decimal (e.g., 0.05 for 5%)
time: Investment period in years (float)
compounds_per_year: Number of times interest compounds annually
(default: 1 for annual compounding)
Returns:
float: Final amount after compound interest
Example:
>>> calculate_compound_interest(1000, 0.05, 10, 12)
1647.0095
"""
return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)40.4.4) Comentários TODO para Trabalho Futuro
Use comentários TODO para marcar áreas que precisam de atenção no futuro:
def process_payment(amount, payment_method):
"""Process a payment transaction."""
# TODO: Adicionar suporte a pagamentos com criptomoedas
# TODO: Implementar verificações de detecção de fraude
if payment_method == "credit_card":
return process_credit_card(amount)
elif payment_method == "paypal":
return process_paypal(amount)
else:
raise ValueError(f"Unsupported payment method: {payment_method}")Muitos editores conseguem buscar comentários TODO, o que facilita encontrar áreas que precisam de trabalho.
40.5) Organizando Seu Código: Imports, Constantes, Funções e Main
40.5.1) Estrutura Padrão de Módulo
Um módulo Python bem organizado segue esta estrutura:
- Docstring do módulo: descreve o que o módulo faz
- Imports: biblioteca padrão, terceiros e depois imports locais
- Constantes: constantes no nível do módulo
- Funções e classes: código principal
- Bloco principal de execução: código que roda quando o script é executado
"""
student_manager.py
Manage student records including grades and GPA calculations.
This module provides functions for adding students, recording grades,
and calculating grade point averages.
"""
# Imports da biblioteca padrão
import sys
from datetime import datetime
# Imports de terceiros (se houver)
# import requests
# Imports locais (se houver)
# from .database import save_student
# Constantes
MAX_GRADE = 100
MIN_GRADE = 0
PASSING_GRADE = 60
# Funções
def calculate_gpa(grades):
"""
Calculate GPA from a list of numeric grades.
Args:
grades: List of numeric grades (0-100)
Returns:
float: GPA on 4.0 scale
"""
if not grades:
return 0.0
average = sum(grades) / len(grades)
# Converter para a escala 4.0
if average >= 90:
return 4.0
elif average >= 80:
return 3.0
elif average >= 70:
return 2.0
elif average >= 60:
return 1.0
else:
return 0.0
def validate_grade(grade):
"""
Check if a grade is within valid range.
Args:
grade: Numeric grade to validate
Returns:
bool: True if grade is valid, False otherwise
"""
return MIN_GRADE <= grade <= MAX_GRADE
# Execução principal
if __name__ == "__main__":
# Código que roda quando o script é executado diretamente
test_grades = [85, 92, 78, 88]
gpa = calculate_gpa(test_grades)
print(f"GPA: {gpa}") # Output: GPA: 3.040.5.2) Organização de Imports
Agrupe imports em três seções, separadas por linhas em branco:
- Imports da biblioteca padrão: módulos embutidos do Python
- Imports de terceiros: pacotes instalados (como
requests,numpy) - Imports locais: seus próprios módulos
# Imports da biblioteca padrão
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
# Imports de terceiros
import requests
from flask import Flask, render_template
# Imports locais da aplicação
from myapp.database import connect_db
from myapp.models import User, Product
from myapp.utils import format_currencyEstilos de import:
# Importar o módulo inteiro
import math
result = math.sqrt(16) # Output: 4.0
# Importar itens específicos
from math import sqrt, pi
result = sqrt(16) # Output: 4.0
# Importar com alias
import numpy as np
array = np.array([1, 2, 3])
# Importar múltiplos itens
from os import path, getcwd, listdirEvite imports coringa (from module import *) — eles deixam pouco claro de onde os nomes vêm:
# Ruim - não fica claro de onde sqrt vem
# AVISO: Estilo ruim - apenas para demonstração
from math import *
result = sqrt(16)
# Bom - import explícito
from math import sqrt
result = sqrt(16)40.5.3) Organizando Constantes
Coloque constantes no nível do módulo perto do topo, depois dos imports:
"""Configuration settings for the application."""
import os
# Constantes da aplicação
APP_NAME = "Student Manager"
VERSION = "1.0.0"
DEBUG_MODE = True
# Configuração do banco de dados
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///students.db")
MAX_CONNECTIONS = 10
# Regras de negócio
MAX_STUDENTS_PER_CLASS = 30
PASSING_GRADE = 60
GRADE_WEIGHTS = {
"homework": 0.3,
"midterm": 0.3,
"final": 0.4
}
def calculate_final_grade(homework, midterm, final):
"""Calculate weighted final grade."""
return (
homework * GRADE_WEIGHTS["homework"] +
midterm * GRADE_WEIGHTS["midterm"] +
final * GRADE_WEIGHTS["final"]
)40.5.4) Ordenação Lógica de Funções
Organize funções em uma ordem lógica:
- Funções públicas primeiro: funções que devem ser usadas por outros módulos
- Funções auxiliares depois: funções internas que dão suporte às públicas
- Funções relacionadas juntas: agrupe funções que trabalham em conjunto
"""Order processing module."""
# Funções da API pública
def process_order(order_items, customer):
"""
Process a customer order.
This is the main entry point for order processing.
"""
subtotal = _calculate_subtotal(order_items)
discount = _calculate_discount(subtotal, customer)
tax = _calculate_tax(subtotal - discount)
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}
def validate_order(order_items):
"""Validate that an order contains valid items."""
if not order_items:
return False
for item in order_items:
if not _validate_item(item):
return False
return True
# Funções auxiliares internas
def _calculate_subtotal(items):
"""Calculate order subtotal (internal use)."""
total = 0
for item in items:
total += item["price"] * item["quantity"]
return total
def _calculate_discount(subtotal, customer):
"""Calculate customer discount (internal use)."""
if customer.get("is_premium"):
return subtotal * 0.1
return 0
def _calculate_tax(amount):
"""Calculate sales tax (internal use)."""
TAX_RATE = 0.08
return amount * TAX_RATE
def _validate_item(item):
"""Validate a single order item (internal use)."""
required_fields = ["name", "price", "quantity"]
return all(field in item for field in required_fields)Observe como as funções públicas (process_order, validate_order) vêm primeiro, e as funções auxiliares (prefixadas com _) vêm depois. Isso deixa claro quais funções são a API principal.
40.5.5) Organização de Classes Dentro de Módulos
Quando um módulo contém classes, organize-as de forma lógica:
"""User management system."""
# Constantes
DEFAULT_ROLE = "user"
ADMIN_ROLE = "admin"
# Classes base primeiro
class User:
"""Base user class."""
def __init__(self, username, email):
self.username = username
self.email = email
self.role = DEFAULT_ROLE
def can_edit(self, resource):
"""Check if user can edit a resource."""
return resource.owner == self.username
# Classes derivadas depois das classes base
class AdminUser(User):
"""Administrator with elevated privileges."""
def __init__(self, username, email):
super().__init__(username, email)
self.role = ADMIN_ROLE
def can_edit(self, resource):
"""Admins can edit any resource."""
return True
# Classes relacionadas agrupadas
class Resource:
"""Represent a resource that can be owned and edited."""
def __init__(self, name, owner):
self.name = name
self.owner = owner
# Funções utilitárias relacionadas às classes
def create_user(username, email, is_admin=False):
"""Factory function to create appropriate user type."""
if is_admin:
return AdminUser(username, email)
return User(username, email)Princípios de organização de classes:
- Classes base antes de classes derivadas (leitores precisam entender a base primeiro)
- Classes relacionadas agrupadas (User e Resource são relacionadas)
- Funções utilitárias que trabalham com classes vêm depois das definições de classe
- Cada classe deve ter uma docstring clara explicando seu propósito
40.6) O Padrão if __name__ == "__main__"
40.6.1) Entendendo o Padrão
Todo arquivo Python tem uma variável embutida chamada __name__. O Python define automaticamente o valor dessa variável dependendo de como o arquivo está sendo usado:
- Quando você executa um arquivo diretamente (por exemplo,
python my_script.py), o Python define__name__como"__main__" - Quando você importa um arquivo como um módulo, o Python define
__name__como o nome do módulo (o nome do arquivo sem.py)
Isso permite que você escreva código que só roda quando o arquivo é executado diretamente, e não quando é importado:
"""math_utils.py - Mathematical utility functions."""
def add(a, b):
"""Add two numbers."""
return a + b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
# Este código só roda quando o arquivo é executado diretamente
if __name__ == "__main__":
# Testar as funções
print(f"5 + 3 = {add(5, 3)}") # Output: 5 + 3 = 8
print(f"5 * 3 = {multiply(5, 3)}") # Output: 5 * 3 = 15Quando você executa python math_utils.py, você verá a saída. Mas quando você o importa em outro arquivo:
# another_file.py
from math_utils import add, multiply
result = add(10, 20)
print(result) # Output: 30
# O código de teste de math_utils.py NÃO rodaPerceba que o código de teste (dentro de if __name__ == "__main__":) NÃO roda quando é importado!
40.6.2) Por que Esse Padrão Importa
Esse padrão serve a vários propósitos importantes:
1. Testes e demonstração: você pode incluir exemplos de uso no mesmo arquivo das suas funções:
"""temperature.py - Temperature conversion utilities."""
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit):
"""Convert Fahrenheit to Celsius."""
return (fahrenheit - 32) * 5/9
if __name__ == "__main__":
# Demonstrar as funções
print("Exemplos de Conversão de Temperatura:")
print(f"0°C = {celsius_to_fahrenheit(0)}°F") # Output: 0°C = 32.0°F
print(f"100°C = {celsius_to_fahrenheit(100)}°F") # Output: 100°C = 212.0°F
print(f"32°F = {fahrenheit_to_celsius(32)}°C") # Output: 32°F = 0.0°C2. Módulos reutilizáveis: o mesmo arquivo pode ser tanto um script independente quanto um módulo importável:
"""data_processor.py - Process and analyze data files."""
import sys
def load_data(filename):
"""Load data from a file."""
with open(filename) as f:
return [line.strip() for line in f]
def analyze_data(data):
"""Perform analysis on data."""
return {
"count": len(data),
"average_length": sum(len(item) for item in data) / len(data)
}
if __name__ == "__main__":
# Quando executado como script, processar argumentos da linha de comando
if len(sys.argv) < 2:
print("Usage: python data_processor.py <filename>")
sys.exit(1)
filename = sys.argv[1]
data = load_data(filename)
results = analyze_data(data)
print(f"Processed {results['count']} items")
print(f"Average length: {results['average_length']:.2f}")Você pode executar isso como um script:
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23Ou importar em outro arquivo:
# my_analysis.py
from data_processor import load_data, analyze_data
my_data = load_data("myfile.txt")
results = analyze_data(my_data)
print(f"Found {results['count']} items")40.6.3) Padrões Comuns para Blocos Main
Padrão 1: Casos de teste simples
"""calculator.py - Basic calculator operations."""
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
if __name__ == "__main__":
# Testes rápidos
assert add(2, 3) == 5
assert subtract(10, 4) == 6
print("All tests passed!") # Output: All tests passed!Padrão 2: Função main
Para scripts mais complexos, defina uma função main():
"""report_generator.py - Generate reports from data."""
import sys
def load_data(filename):
"""Load data from file."""
# Implementation here
pass
def generate_report(data):
"""Generate report from data."""
# Implementation here
pass
def save_report(report, output_file):
"""Save report to file."""
# Implementation here
pass
def main():
"""Main entry point for the script."""
if len(sys.argv) < 3:
print("Usage: python report_generator.py <input> <output>")
return 1
input_file = sys.argv[1]
output_file = sys.argv[2]
try:
data = load_data(input_file)
report = generate_report(data)
save_report(report, output_file)
print(f"Report saved to {output_file}")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
# Sair com o código de status retornado por main (0 = sucesso, 1 = erro)
sys.exit(main())Esse padrão tem várias vantagens:
- A função
main()pode ser testada de forma independente - Ponto de entrada claro para o script
- Códigos de saída adequados (0 para sucesso, diferente de zero para erros)
- Separação limpa entre a lógica do script e as funções do módulo
40.6.4) Boas Práticas para Blocos Main
Mantenha blocos main focados: o código dentro de if __name__ == "__main__" deveria principalmente lidar com a execução do script, e não conter lógica complexa:
# Ruim - lógica complexa no bloco main
# AVISO: Estilo ruim - apenas para demonstração
if __name__ == "__main__":
data = []
for i in range(100):
if i % 2 == 0:
data.append(i * 2)
result = sum(data) / len(data)
print(result)
# Bom - lógica em funções, bloco main coordena
def generate_even_doubles(limit):
"""Generate doubled even numbers up to limit."""
return [i * 2 for i in range(limit) if i % 2 == 0]
def calculate_average(numbers):
"""Calculate average of numbers."""
return sum(numbers) / len(numbers)
if __name__ == "__main__":
data = generate_even_doubles(100)
result = calculate_average(data)
print(result) # Output: 99.0Use uma função main() para scripts complexos: como mostrado antes, definir uma função main() torna seu script mais testável e organizado.
Documente o uso do script: se seu script aceita argumentos de linha de comando, documente-os na docstring do módulo:
"""
file_processor.py - Process text files with various operations.
Usage:
python file_processor.py <input_file> <output_file> [--uppercase]
Arguments:
input_file: Path to input file
output_file: Path to output file
--uppercase: Convert text to uppercase (optional)
"""
import sys
def process_file(input_path, output_path, uppercase=False):
"""Process file with specified options."""
with open(input_path) as f:
content = f.read()
if uppercase:
content = content.upper()
with open(output_path, 'w') as f:
f.write(content)
if __name__ == "__main__":
if len(sys.argv) < 3:
print(__doc__) # Imprimir a docstring do módulo
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
uppercase = "--uppercase" in sys.argv
process_file(input_file, output_file, uppercase)
print(f"Processed {input_file} -> {output_file}")Escrever código limpo e legível é uma habilidade que se desenvolve com prática. As convenções e padrões neste capítulo não são regras arbitrárias — são práticas comprovadas que tornam o código mais fácil de entender, manter e depurar. À medida que você escreve mais código Python, esses padrões vão se tornar naturais.
Lembre-se: código é lido muito mais vezes do que é escrito. Os poucos segundos extras que você gasta escolhendo um nome claro, adicionando um comentário útil ou organizando seus imports corretamente vão economizar horas de confusão depois — para você e para outras pessoas que trabalharem com o seu código.
No próximo capítulo, vamos explorar técnicas de debugging e testes que se baseiam nessas práticas de código limpo, ajudando você a escrever não apenas código legível, mas código correto e confiável.