22. Organizando Código com Módulos e Pacotes
À medida que seus programas em Python vão crescendo, manter todo o seu código em um único arquivo se torna impraticável. Você vai querer organizar funções, classes e variáveis relacionadas em arquivos separados que possam ser reutilizados em diferentes programas. O sistema de módulo e pacote do Python oferece exatamente essa capacidade — uma forma de organizar, compartilhar e reutilizar código de maneira eficaz.
Neste capítulo, vamos explorar como funciona o sistema de importação (import) do Python, como criar e usar seus próprios módulos, e como organizar vários módulos em pacotes. Também vamos examinar a variável especial __name__ que permite escrever arquivos que funcionam tanto como módulos importáveis quanto como scripts independentes.
22.1) O Que São Módulos e Como import Funciona
Entendendo Módulos
Um módulo é simplesmente um arquivo Python contendo definições e instruções. Qualquer arquivo .py que você cria é um módulo. Quando você escreve uma função em um arquivo chamado calculator.py, esse arquivo se torna um módulo chamado calculator que outros arquivos Python podem usar.
Módulos servem a vários propósitos importantes:
- Reutilização de Código: Escreva uma função uma vez e use-a em vários programas
- Organização: Agrupe funcionalidades relacionadas
- Gerenciamento de Namespace: Mantenha nomes separados para evitar conflitos
- Manutenibilidade: Arquivos menores e focados são mais fáceis de entender e modificar
Vamos criar um módulo simples para ver como isso funciona. Crie um arquivo chamado greetings.py:
# greetings.py
def say_hello(name):
"""Return a friendly greeting."""
return f"Hello, {name}!"
def say_goodbye(name):
"""Return a farewell message."""
return f"Goodbye, {name}. See you soon!"
# Uma variável no nível do módulo
default_greeting = "Welcome"Este arquivo agora é um módulo. Ele contém duas funções e uma variável que outros arquivos Python podem usar.
A Instrução import
Para usar código de um módulo, você o importa(import). A instrução import diz ao Python para carregar um módulo e disponibilizar seu conteúdo. Crie outro arquivo no mesmo diretório chamado main.py:
# main.py
import greetings
message = greetings.say_hello("Alice")
print(message) # Output: Hello, Alice!
farewell = greetings.say_goodbye("Bob")
print(farewell) # Output: Goodbye, Bob. See you soon!
print(greetings.default_greeting) # Output: WelcomeQuando você executa main.py, o Python executa a instrução import greetings. Veja o que acontece nos bastidores:
Importante: o Python executa o código de um módulo apenas na primeira vez em que ele é importado em um programa. Importações posteriores no mesmo programa reutilizam o módulo já carregado. Isso evita execução duplicada e economiza tempo.
Acessando o Conteúdo do Módulo
Depois de importar um módulo, você acessa seu conteúdo usando notação de ponto (dot notation): module_name.item_name. Isso é parecido com como acessamos métodos de string como text.upper() ou métodos de lista como numbers.append(), como aprendemos nos Capítulos 5 e 14.
import greetings
# Acessar funções
result = greetings.say_hello("Charlie")
# Acessar variáveis
greeting = greetings.default_greeting
# Você pode até verificar o que há em um módulo
print(dir(greetings)) # Lista todos os nomes definidos no móduloA notação de ponto deixa claro de onde cada nome vem. Quando você vê greetings.say_hello(), você sabe imediatamente que essa função vem do módulo greetings.
Caminho de Busca de Módulos
Quando você escreve import greetings, como o Python encontra greetings.py? O Python procura módulos em uma ordem específica:
- Diretório Atual: o diretório que contém o script que você está executando
- PYTHONPATH: diretórios listados na variável de ambiente
PYTHONPATH(se estiver definida) - Biblioteca Padrão: os diretórios de módulos embutidos do Python
- Site-Packages: pacotes de terceiros instalados com pip
Você pode ver o caminho de busca do Python examinando sys.path:
import sys
for path in sys.path:
print(path)Saída (exemplo — seus caminhos reais vão variar conforme seu sistema e a instalação do Python):
/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packagesO primeiro caminho no output é o diretório de trabalho atual. O Python procura nesse diretório primeiro, então ele pode encontrar módulos no mesmo diretório.
Nomes de Módulos e Nomes de Arquivos
O nome do módulo é o nome do arquivo sem a extensão .py. Se seu arquivo é string_utils.py, o nome do módulo é string_utils. Nomes de módulos devem seguir as regras de identificadores do Python (como aprendemos no Capítulo 3):
- Começar com uma letra ou underscore
- Conter apenas letras, dígitos e underscores
- Não pode ser palavra-chave do Python
# Nomes de módulos válidos (e nomes de arquivos)
import data_processor # data_processor.py
import user_auth # user_auth.py
import _internal_helpers # _internal_helpers.py
# Inválido - causaria erros
# import 2d_graphics # Can't start with digit
# import my-module # Hyphens not allowed
# import class # 'class' is a keywordArmadilha Comum: Shadowing de Módulos da Biblioteca Padrão
Tome cuidado para não dar aos seus módulos o mesmo nome de módulos da biblioteca padrão. Se você criar um arquivo chamado random.py no diretório do seu projeto, o Python vai importar o seu arquivo em vez do módulo random da biblioteca padrão, causando erros confusos:
# Seu arquivo: random.py
def my_function():
return 42
# Outro arquivo no seu projeto
import random
print(random.randint(1, 6)) # ERROR! Your random.py doesn't have randint()Para evitar isso, verifique se um nome já é usado pela biblioteca padrão antes de criar um módulo com esse nome. Você pode checar tentando importá-lo no shell interativo do Python. Se ele importar sem erro, esse nome já está em uso.
O Que Acontece Durante o Import
Vamos examinar o que realmente acontece quando você importa um módulo. Crie um arquivo chamado demo_module.py:
# demo_module.py
print("Module is being loaded!")
def greet():
print("Hello from demo_module")
print("Module loading complete!")Agora importe:
# test_import.py
print("Before import")
import demo_module
print("After import")
demo_module.greet()Output:
Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_moduleObserve que os print() em demo_module.py são executados durante o import. Isso demonstra que importar um módulo executa todo o código no nível superior. Definições de funções são armazenadas para uso posterior, mas qualquer código fora de funções executa imediatamente.
Se você importar o mesmo módulo novamente no mesmo programa, as mensagens de carregamento não vão aparecer de novo:
import demo_module # Primeiro import - executa o código do módulo
import demo_module # Segundo import - usa o módulo em cache
import demo_module # Terceiro import - ainda usa o módulo em cacheOutput:
Module is being loaded!
Module loading complete!O código do módulo é executado apenas uma vez, não importa quantas vezes você o importe.
22.2) Diferentes Formas de Importar: import, from e as
O Python oferece várias maneiras de importar módulos e seus conteúdos. Cada abordagem tem implicações diferentes sobre como você acessa os nomes importados e como eles afetam seu namespace.
Instrução import Básica
A instrução import básica que já vimos carrega o módulo inteiro:
import math
result = math.sqrt(16)
print(result) # Output: 4.0
pi_value = math.pi
print(pi_value) # Output: 3.141592653589793Com essa abordagem, você sempre usa o nome do módulo como prefixo. Isso deixa o código bem claro — você sempre consegue dizer de onde um nome vem.
Importando Nomes Específicos com from
Às vezes você só precisa de um ou dois itens de um módulo. A instrução from permite importar nomes específicos diretamente para o seu namespace:
from math import sqrt, pi
result = sqrt(25) # Não precisa do prefixo 'math.'
print(result) # Output: 5.0
print(pi) # Output: 3.141592653589793Agora você pode usar sqrt e pi diretamente sem o prefixo math.. Isso é conveniente quando você usa esses nomes com frequência.
Vamos ver outro exemplo com o nosso módulo greetings:
# Usando from import
from greetings import say_hello
message = say_hello("Diana") # Acesso direto
print(message) # Output: Hello, Diana!
# Porém, say_goodbye não está disponível já que não o importamos
# say_goodbye("Diana") # NameError: name 'say_goodbye' is not definedVocê pode importar vários nomes em uma única instrução:
from greetings import say_hello, say_goodbye, default_greeting
print(say_hello("Eve")) # Output: Hello, Eve!
print(say_goodbye("Frank")) # Output: Goodbye, Frank!
print(default_greeting) # Output: WelcomeO Import com Wildcard (e Por Que Evitar)
O Python permite importar tudo de um módulo usando *:
from math import *
print(sqrt(9)) # Output: 3.0
print(cos(0)) # Output: 1.0
print(pi) # Output: 3.141592653589793Isso importa todos os nomes públicos do módulo (nomes que não começam com underscore). Embora isso pareça conveniente, em geral é considerado uma má prática porque:
- Poluição do Namespace: você não sabe exatamente quais nomes está importando
- Conflitos de Nome: nomes importados podem sobrescrever suas próprias variáveis
- Legibilidade: quem lê o código não consegue dizer de onde os nomes vêm
# Exemplo problemático
from math import *
# Mais tarde no seu código...
def sqrt(x):
"""Your own square root function."""
return x ** 0.5
# Qual sqrt você está usando? O seu ou o do math?
result = sqrt(16) # Confuso!Boa Prática: importe nomes específicos ou use a instrução import básica. Evite from module import * exceto em sessões interativas onde você está experimentando.
Renomeando Imports com as
Às vezes nomes de módulos ou funções são longos, ou você quer evitar conflitos de nome. A palavra-chave as permite criar um alias:
import math as m
result = m.sqrt(36)
print(result) # Output: 6.0Isso é particularmente útil para módulos com nomes longos ou quando você segue convenções comuns:
import datetime as dt
today = dt.date.today()
print(today) # Output: 2025-12-19 (or current date)Você também pode renomear imports específicos:
from math import sqrt as square_root
result = square_root(49)
print(result) # Output: 7.0Isso ajuda quando você tem conflitos de nome:
from math import sqrt as math_sqrt
def sqrt(x):
"""Custom square root with input validation."""
if x < 0:
return None
return math_sqrt(x)
print(sqrt(25)) # Output: 5.0 (your function)
print(sqrt(-4)) # Output: None (your function)Combinando Estilos de Import
Você pode misturar diferentes estilos de import no mesmo arquivo:
import math
from datetime import date, time
from random import randint as random_int
# Usar math com prefixo
radius = 5
area = math.pi * radius ** 2
# Usar date e time diretamente
today = date.today()
current_time = time(14, 30)
# Usar função renomeada
dice_roll = random_int(1, 6)Escolhendo o Estilo de Import Certo
Aqui vai um guia de decisão:
Use import module quando:
- Você precisa de vários itens do módulo
- Você quer máxima clareza sobre de onde os nomes vêm
- O nome do módulo é curto e claro
Use from module import name quando:
- Você precisa de apenas um ou dois itens específicos
- Os nomes são distintivos e pouco prováveis de conflitar
- Você vai usar os nomes com frequência
Use import module as alias quando:
- O nome do módulo é muito longo
- Você está seguindo uma convenção comum (como
import numpy as np) - Você precisa evitar conflitos com outros módulos
Evite from module import * em código de produção:
- Use apenas para experimentos rápidos no shell interativo
- Nunca use em módulos que outros vão importar
Vamos ver um exemplo completo demonstrando boas práticas de import:
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
def calculate_statistics(numbers):
"""Calculate various statistics for a list of numbers."""
if not numbers:
return None
avg = mean(numbers)
mid = median(numbers)
std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
return {
'mean': avg,
'median': mid,
'std_dev': std_dev,
'timestamp': dt.now()
}
# Testar a função
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}") # Output: Mean: 30.0
print(f"Median: {stats['median']}") # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}") # Output: Std Dev: 14.14Este exemplo mostra:
import mathpara o módulo completo (podemos usar outras funções math depois)from statistics import mean, medianpara funções específicas que usamos com frequênciafrom datetime import datetime as dtpara um módulo com alias comum
22.3) Visão Geral de Módulos Comuns da Biblioteca Padrão do Python
O Python vem com uma rica biblioteca padrão (standard library) — uma coleção de módulos que fornecem soluções para tarefas comuns de programação. Esses módulos estão sempre disponíveis; você não precisa instalar nada extra. Entender o que está disponível na biblioteca padrão ajuda você a evitar “reinventar a roda”.
O Módulo math
O módulo math fornece funções matemáticas além da aritmética básica:
import math
# Funções trigonométricas
angle_rad = math.radians(45) # Converter graus para radianos
print(math.sin(angle_rad)) # Output: 0.7071067811865476
print(math.cos(angle_rad)) # Output: 0.7071067811865475
# Arredondamento e valor absoluto
print(math.ceil(4.2)) # Output: 5 (arredondar para cima)
print(math.floor(4.8)) # Output: 4 (arredondar para baixo)
print(math.fabs(-7.5)) # Output: 7.5 (valor absoluto como float)
# Exponencial e logarítmico
print(math.exp(2)) # Output: 7.38905609893065 (e^2)
print(math.log(100)) # Output: 4.605170185988092 (log natural)
print(math.log10(100)) # Output: 2.0 (log base 10)
# Constantes
print(math.pi) # Output: 3.141592653589793
print(math.e) # Output: 2.718281828459045Como aprendemos no Capítulo 4, o módulo math é essencial para operações matemáticas avançadas.
O Módulo random
O módulo random gera números pseudoaleatórios e faz seleções aleatórias:
import random
# Inteiros aleatórios
dice = random.randint(1, 6) # Inteiro aleatório de 1 a 6 (inclusive)
print(f"Dice roll: {dice}")
# Floats aleatórios
probability = random.random() # Float aleatório de 0.0 a 1.0
print(f"Probability: {probability:.4f}")
# Escolha aleatória de uma sequência
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
# Embaralhar uma lista(list) in place
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
# Amostra aleatória sem reposição
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")Saída (exemplo — vai variar por causa da aleatoriedade):
Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]O Módulo datetime
O módulo datetime lida com datas e horários:
from datetime import date, time, datetime, timedelta
# Data e hora atuais
today = date.today()
now = datetime.now()
print(f"Today: {today}") # Output: Today: 2025-12-19
print(f"Now: {now}") # Output: Now: 2025-12-19 14:30:45.123456
# Criando datas e horários específicos
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
print(f"Birthday: {birthday}") # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}") # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}") # Output: Appointment: 2025-12-25 10:00:00
# Aritmética de datas com timedelta
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}") # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}") # Output: Next week: 2025-12-26
# Extraindo componentes
print(f"Year: {today.year}") # Output: Year: 2025
print(f"Month: {today.month}") # Output: Month: 12
print(f"Day: {today.day}") # Output: Day: 19O Módulo os
O módulo os fornece funcionalidades do sistema operacional. Vamos explorar isso em detalhes no Capítulo 26, mas aqui vai uma prévia:
import os
# Diretório de trabalho atual
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
# Listar arquivos em um diretório
files = os.listdir('.')
print(f"Files: {files[:3]}") # Mostrar os 3 primeiros arquivos
# Verificar se um caminho existe
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
# Juntar componentes de caminho (funciona entre sistemas operacionais)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}") # Output: data/users/profile.txt (or data\users\profile.txt on Windows)O Módulo sys
O módulo sys fornece parâmetros e funções específicos do sistema:
import sys
# Informações da versão do Python
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
# Informações da plataforma
print(f"Platform: {sys.platform}") # Output: linux, darwin, win32, etc.
# Tamanho máximo de inteiro
print(f"Max int: {sys.maxsize}")O Módulo statistics
O módulo statistics fornece funções para cálculos estatísticos:
import statistics
grades = [85, 92, 78, 90, 88, 95, 82]
# Tendência central
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
print(f"Mean: {avg}") # Output: Mean: 87.14285714285714
print(f"Median: {mid}") # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
# Dispersão
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
print(f"Standard deviation: {std_dev:.2f}") # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}") # Output: Variance: 34.81O Módulo collections
O módulo collections fornece tipos de coleções especializadas. Vamos explorar isso mais no Capítulo 39, mas aqui vai um gostinho:
from collections import Counter, defaultdict
# Counter - contar ocorrências
text = "hello world"
letter_counts = Counter(text)
print(letter_counts) # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l']) # Output: 3
# defaultdict - dicionário com valores padrão
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists)) # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}Encontrando Mais Módulos da Biblioteca Padrão
A biblioteca padrão do Python contém mais de 200 módulos. Você pode explorá-los de várias formas:
# Ver todos os módulos disponíveis (isso demora um pouco)
help('modules')
# Obter ajuda sobre um módulo específico
import math
help(math)
# Ver o que há em um módulo
import random
print(dir(random))A documentação do Python (https://docs.python.org/3/library/) fornece informações abrangentes sobre cada módulo da biblioteca padrão. À medida que você ganha experiência, vai descobrir quais módulos são mais úteis para o seu trabalho.
22.4) Criando e Usando Seus Próprios Módulos
Criar seus próprios módulos é simples — qualquer arquivo Python pode ser um módulo. A chave é organizar seu código de forma cuidadosa para que os módulos sejam focados, reutilizáveis e fáceis de entender.
Criando um Módulo Simples
Vamos criar um módulo para trabalhar com notas de alunos. Crie um arquivo chamado grade_calculator.py:
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
def calculate_average(grades):
"""Calculate the average of a list of grades."""
if not grades:
return 0
return sum(grades) / len(grades)
def get_letter_grade(numeric_grade):
"""Convert a numeric grade to a letter grade."""
if numeric_grade >= 90:
return 'A'
elif numeric_grade >= 80:
return 'B'
elif numeric_grade >= 70:
return 'C'
elif numeric_grade >= 60:
return 'D'
else:
return 'F'
def find_highest(grades):
"""Find the highest grade in a list."""
if not grades:
return None
return max(grades)
def find_lowest(grades):
"""Find the lowest grade in a list."""
if not grades:
return None
return min(grades)
# Constantes no nível do módulo
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90Agora crie outro arquivo para usar esse módulo:
# student_report.py
import grade_calculator
# Notas dos testes do aluno
test_scores = [85, 92, 78, 88, 95]
# Calcular estatísticas
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
# Gerar relatório
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
# Verificar honor roll
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
print("Status: Passing")
else:
print("Status: Needs Improvement")Output:
Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: PassingDocumentação do Módulo
Note a docstring no topo de grade_calculator.py. Essa docstring no nível do módulo descreve o que o módulo faz. Ela aparece quando alguém usa help():
import grade_calculator
help(grade_calculator)Isso exibe a documentação do módulo, incluindo a docstring do módulo e todas as docstrings das funções. Uma boa documentação torna seus módulos mais fáceis de usar.
Variáveis e Constantes no Nível do Módulo
Módulos podem conter variáveis que são compartilhadas em todos os usos do módulo. Isso é frequentemente usado para configuração ou constantes:
# config.py
"""Application configuration settings."""
# Configurações do banco de dados
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
# Configurações da aplicação
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800 # segundos
DEBUG_MODE = False
# Caminhos de arquivo
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
# Flags de funcionalidades
ENABLE_CACHING = True
ENABLE_LOGGING = TrueUsando configuração de um módulo:
# app.py
import config
def connect_database():
"""Connect to the database using config settings."""
print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
print(f"Database: {config.DB_NAME}")
if config.DEBUG_MODE:
print("DEBUG: Connection details logged")
def check_login_attempts(attempts):
"""Check if login attempts exceed the limit."""
if attempts >= config.MAX_LOGIN_ATTEMPTS:
print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
return False
return True
connect_database()
print(check_login_attempts(2)) # Output: True
print(check_login_attempts(4)) # Output: Too many attempts! Maximum is 3Output:
Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3Importante: variáveis no nível do módulo são compartilhadas entre todos os imports. Se você modificar uma variável do módulo, a mudança afeta todo o código que usa esse módulo:
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}") # Será True!Esse comportamento pode ser útil, mas também surpreendente. Tenha cuidado ao modificar variáveis no nível do módulo.
Nomes Privados em Módulos
Por convenção, nomes que começam com underscore são considerados privados ou internos ao módulo:
# user_manager.py
"""Module for managing user accounts."""
# Função auxiliar privada
def _validate_email(email):
"""Internal function to validate email format."""
return '@' in email and '.' in email
# Função pública
def create_user(username, email):
"""Create a new user account."""
if not _validate_email(email):
return None
user = {
'username': username,
'email': email,
'active': True
}
return user
# Constante privada
_MAX_USERNAME_LENGTH = 20
# Constante pública
MIN_PASSWORD_LENGTH = 8Quando você usa from user_manager import *, nomes privados (aqueles que começam com underscore) não são importados. No entanto, você ainda pode acessá-los explicitamente se necessário:
import user_manager
# Função pública - destinada ao uso
user = user_manager.create_user("alice", "alice@example.com")
# Função privada - dá para acessar, mas você não deveria depender dela
# (ela pode mudar em versões futuras)
is_valid = user_manager._validate_email("test@test.com")O prefixo underscore é um sinal para outros programadores: “Isso é um detalhe de implementação. Não dependa que ele permaneça igual.”
22.5) Entendendo Pacotes e __init__.py
À medida que projetos crescem, você vai querer organizar vários módulos relacionados em um pacote. Um pacote é um diretório contendo módulos Python e um arquivo especial __init__.py.
O Que É um Pacote?
Um pacote é uma forma de organizar vários módulos em uma estrutura hierárquica. Pense nisso como uma pasta que contém arquivos Python, onde a própria pasta pode ser importada.
Aqui está uma estrutura simples de pacote:
myproject/
main.py
utilities/
__init__.py
text.py
math.py
file.pyNessa estrutura, utilities é um pacote contendo três módulos: text, math e file. O arquivo __init__.py (que pode estar vazio) diz ao Python que utilities é um pacote.
Criando um Pacote Simples
Vamos criar um pacote para processamento de dados. Primeiro, crie esta estrutura de diretórios:
data_tools/
__init__.py
validators.py
formatters.pyCrie validators.py:
# data_tools/validators.py
"""Data validation functions."""
def is_valid_email(email):
"""Check if email has basic valid format."""
return '@' in email and '.' in email.split('@')[1]
def is_valid_phone(phone):
"""Check if phone number has valid format (simple check)."""
digits = ''.join(c for c in phone if c.isdigit())
return len(digits) == 10
def is_positive_number(value):
"""Check if value is a positive number."""
try:
return float(value) > 0
except (ValueError, TypeError):
return FalseCrie formatters.py:
# data_tools/formatters.py
"""Data formatting functions."""
def format_phone(phone):
"""Format phone number as (XXX) XXX-XXXX."""
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) != 10:
return phone
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
def format_currency(amount):
"""Format number as currency."""
return f"${amount:,.2f}"
def format_percentage(value, decimals=1):
"""Format number as percentage."""
return f"{value * 100:.{decimals}f}%"Crie um __init__.py vazio:
# data_tools/__init__.py
"""Data processing tools package."""Importando a Partir de Pacotes
Agora você pode importar do pacote de várias maneiras:
# Método 1: Importar o módulo a partir do pacote
import data_tools.validators
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}") # Output: Email valid: True
# Método 2: Importar um módulo específico com from
from data_tools import formatters
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}") # Output: Formatted phone: (123) 456-7890
# Método 3: Importar funções específicas
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
print(is_valid_phone("555-1234")) # Output: False (not 10 digits)
print(format_currency(1234.56)) # Output: $1,234.56O Arquivo __init__.py
O arquivo __init__.py serve a dois propósitos:
- Marca o diretório como um pacote: o Python reconhece diretórios com
__init__.pycomo pacotes - Código de inicialização do pacote: o código em
__init__.pyroda quando o pacote é importado pela primeira vez
O arquivo __init__.py pode estar vazio, mas ele também pode conter código para tornar o pacote mais fácil de usar:
# data_tools/__init__.py
"""Data processing tools package."""
# Importar funções usadas com frequência para o namespace do pacote
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
# Versão do pacote
__version__ = '1.0.0'
# Constante no nível do pacote
DEFAULT_CURRENCY_SYMBOL = '$'Agora usuários podem importar diretamente do pacote:
# Em vez de: from data_tools.validators import is_valid_email
# Você pode escrever:
from data_tools import is_valid_email, format_currency
print(is_valid_email("test@test.com")) # Output: True
print(format_currency(99.99)) # Output: $99.9922.6) A Variável __name__ e if __name__ == "__main__": Executando um Arquivo como Script
Arquivos Python podem servir a dois propósitos: eles podem ser importados como módulos ou executados como scripts independentes. A variável especial __name__ ajuda você a escrever código que funciona bem nas duas situações.
Entendendo __name__
Todo módulo Python tem uma variável embutida chamada __name__. O Python define essa variável de forma diferente dependendo de como o arquivo está sendo usado:
- Quando importado:
__name__é definido com o nome do módulo - Quando executado diretamente:
__name__é definido como"__main__"
Vamos ver isso em ação. Crie um arquivo chamado demo_name.py:
# demo_name.py
print(f"The __name__ variable is: {__name__}")Agora execute-o diretamente:
python demo_name.pyOutput:
The __name__ variable is: __main__Agora importe-o de outro arquivo:
# test_import.py
import demo_nameOutput:
The __name__ variable is: demo_nameQuando você executa demo_name.py diretamente, o Python define __name__ como "__main__". Quando você o importa, o Python define __name__ como o nome do módulo ("demo_name").
O Padrão if __name__ == "__main__":
Esse comportamento permite escrever código que só roda quando o arquivo é executado diretamente, e não quando é importado. Isso é feito com o padrão:
if __name__ == "__main__":
# O código aqui roda apenas quando o arquivo é executado diretamente
passVeja por que isso é útil. Crie math_utils.py:
# math_utils.py
"""Utility functions for mathematical operations."""
def calculate_area(radius):
"""Calculate the area of a circle."""
return 3.14159 * radius ** 2
def calculate_circumference(radius):
"""Calculate the circumference of a circle."""
return 2 * 3.14159 * radius
# Código de teste - roda apenas quando o arquivo é executado diretamente
if __name__ == "__main__":
print("Testing math_utils functions...")
test_radius = 5
area = calculate_area(test_radius)
circumference = calculate_circumference(test_radius)
print(f"Radius: {test_radius}")
print(f"Area: {area:.2f}")
print(f"Circumference: {circumference:.2f}")Quando você executa esse arquivo diretamente:
python math_utils.pyOutput:
Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42Mas quando você o importa:
# use_math_utils.py
import math_utils
# O código de teste não roda!
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}") # Output: Area of circle: 314.16O código de teste dentro do bloco if __name__ == "__main__": não é executado durante o import. Isso permite incluir código de teste, exemplos ou demonstrações nos seus módulos sem afetar o código que os importa.
Usos Comuns de if __name__ == "__main__":
Testes e Demonstrações
Inclua exemplos mostrando como usar seu módulo:
# string_tools.py
def reverse_string(text):
"""Reverse a string."""
return text[::-1]
def count_vowels(text):
"""Count vowels in text."""
vowels = 'aeiouAEIOU'
return sum(1 for char in text if char in vowels)
if __name__ == "__main__":
# Código de demonstração
sample = "Hello, World!"
print(f"Original: {sample}")
print(f"Reversed: {reverse_string(sample)}")
print(f"Vowels: {count_vowels(sample)}")Neste capítulo, aprendemos como organizar código Python usando módulos e pacotes. Exploramos como funciona o sistema de importação, diferentes formas de importar código, e como criar nossos próprios módulos e pacotes. Também aprendemos sobre a variável __name__ e o padrão if __name__ == "__main__": que permite que arquivos funcionem tanto como módulos importáveis quanto como scripts independentes.
Essas ferramentas de organização se tornam cada vez mais importantes conforme seus programas crescem. No próximo capítulo, vamos explorar como usar funções como dados e aplicar técnicas simples de programação funcional, construindo sobre a base sólida de organização de código que estabelecemos aqui.