---
title: "Módulo Python: O que É e Como Funciona | Python Brasil"
url: "https://python.dev.br/glossario/modulo/"
markdown_url: "https://python.dev.br/glossario/modulo.MD"
description: "Guia completo sobre módulos Python: importações, sys.path, __all__, importações circulares, importlib, lazy loading e arquivos .pyc."
date: "2025-11-20"
author: ""
---

# Módulo Python: O que É e Como Funciona | Python Brasil

Guia completo sobre módulos Python: importações, sys.path, __all__, importações circulares, importlib, lazy loading e arquivos .pyc.


## O que é um Módulo?

Um **módulo** em Python é simplesmente um **arquivo `.py`** que contém código Python — funções, classes, variáveis e instruções executáveis. Módulos são a unidade básica de organização e reutilização de código na linguagem.

Ao importar um módulo, o Python executa o arquivo inteiro uma vez e armazena o resultado em memória, disponibilizando seu conteúdo com um namespace específico. A biblioteca padrão do Python inclui mais de 200 módulos prontos, o que originou a frase **"Python vem com baterias incluídas"**.

## Como importar módulos

```python
# Importar o módulo completo — acesso via namespace
import math
print(math.sqrt(16))  # 4.0
print(math.pi)        # 3.141592653589793

# Importar nomes específicos — acesso direto
from datetime import datetime, timedelta
agora = datetime.now()
amanha = agora + timedelta(days=1)

# Importar com alias — convenção para bibliotecas comuns
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Importar tudo (evite em produção — polui o namespace)
from math import *
print(sqrt(25))  # 5.0 — mas qual sqrt é essa? math.sqrt? numpy.sqrt?

# Verificar o que está no namespace atual
print(dir())
```

A forma preferida é `import modulo` ou `from modulo import nome_especifico`. O `from modulo import *` raramente é adequado, pois torna difícil saber a origem de cada nome.

## O caminho de busca: sys.path

Quando você escreve `import requests`, o Python precisa encontrar o arquivo correspondente. Ele percorre uma lista de diretórios na ordem definida em `sys.path`:

```python
import sys

# Ver o caminho de busca atual
for caminho in sys.path:
    print(caminho)

# Saída típica:
# ''                                         # diretório atual
# /usr/lib/python312.zip
# /usr/lib/python3.12
# /usr/lib/python3.12/lib-dynload
# /home/usuario/.local/lib/python3.12/site-packages  # pip --user
# /usr/local/lib/python3.12/dist-packages            # pip global

# Adicionar diretório temporariamente
sys.path.insert(0, "/caminho/para/meus/modulos")
import meu_modulo

# Adicionar permanentemente via variável de ambiente
# export PYTHONPATH="/caminho/para/meus/modulos:$PYTHONPATH"
```

A string vazia `''` no início representa o diretório de trabalho atual, por isso um arquivo `requests.py` na pasta atual sobrescreveria a biblioteca `requests` instalada. Cuidado ao nomear seus arquivos com nomes de bibliotecas populares.

## Criando seus próprios módulos

```python
# utils.py — seu módulo de utilitários
"""
Módulo de utilitários para formatação de dados brasileiros.
"""

def formatar_cpf(cpf: str) -> str:
    """Formata um CPF: '12345678901' -> '123.456.789-01'"""
    cpf = "".join(filter(str.isdigit, cpf))
    if len(cpf) != 11:
        raise ValueError(f"CPF deve ter 11 dígitos, recebeu {len(cpf)}")
    return f"{cpf[:3]}.{cpf[3:6]}.{cpf[6:9]}-{cpf[9:]}"

def formatar_moeda(valor: float, simbolo: str = "R$") -> str:
    """Formata valor como moeda: 1500.5 -> 'R$ 1.500,50'"""
    return f"{simbolo} {valor:_.2f}".replace(".", ",").replace("_", ".")

VERSAO = "1.2.0"

# app.py — usando o módulo
from utils import formatar_cpf, formatar_moeda, VERSAO

print(formatar_cpf("12345678901"))  # 123.456.789-01
print(formatar_moeda(1500.50))      # R$ 1.500,50
print(f"Utils versão {VERSAO}")     # Utils versão 1.2.0
```

## Controlando a API pública com __all__

A variável `__all__` define quais nomes são exportados quando alguém usa `from modulo import *`. Também serve como documentação da API pública:

```python
# meu_modulo.py

__all__ = ["funcao_publica", "ClassePublica"]  # API pública explícita

def funcao_publica():
    """Esta função é parte da API pública."""
    return _funcao_interna()

def _funcao_interna():
    """Convenção: underscore indica uso interno."""
    return "resultado interno"

class ClassePublica:
    pass

class _ClasseInterna:
    """Não exportada — uso interno do módulo."""
    pass

# from meu_modulo import * importa apenas funcao_publica e ClassePublica
# _funcao_interna e _ClasseInterna NÃO são importadas
```

## O bloco if __name__ == "__main__"

```python
# calculadora.py

def somar(a: float, b: float) -> float:
    return a + b

def subtrair(a: float, b: float) -> float:
    return a - b

# Este bloco SÓ executa quando o arquivo é rodado diretamente
# Quando importado como módulo, __name__ == "calculadora" (não "__main__")
if __name__ == "__main__":
    print(f"2 + 3 = {somar(2, 3)}")
    print(f"10 - 4 = {subtrair(10, 4)}")

# python calculadora.py  -> executa o bloco
# import calculadora     -> NÃO executa o bloco
```

Esse padrão é essencial para criar módulos que funcionam tanto como scripts independentes quanto como bibliotecas importáveis.

## Importações relativas vs absolutas

Dentro de pacotes, você pode usar importações relativas (com ponto) ou absolutas:

```python
# Estrutura do pacote:
# meu_app/
# ├── __init__.py
# ├── core.py
# └── utils/
#     ├── __init__.py
#     ├── formatacao.py
#     └── validacao.py

# Em meu_app/core.py — importações absolutas (sempre funcionam)
from meu_app.utils.formatacao import formatar_cpf
from meu_app.utils.validacao import validar_email

# Em meu_app/core.py — importações relativas (só dentro de pacotes)
from .utils.formatacao import formatar_cpf    # . = diretório atual
from .utils.validacao import validar_email
from ..outro_pacote import algo               # .. = diretório pai
```

Importações absolutas são mais explícitas e geralmente preferidas. Importações relativas são úteis em pacotes grandes para evitar repetir o nome do pacote raiz.

## Importações circulares e como resolvê-las

Importações circulares ocorrem quando o módulo A importa o módulo B, e B importa A. O Python detecta ciclos e pode lançar `ImportError`:

```python
# PROBLEMA — importação circular
# modulo_a.py
from modulo_b import funcao_b

def funcao_a():
    return funcao_b()

# modulo_b.py
from modulo_a import funcao_a  # ImportError!

def funcao_b():
    return funcao_a()

# SOLUÇÃO 1 — importar dentro da função (lazy import)
# modulo_b.py
def funcao_b():
    from modulo_a import funcao_a  # importado só quando necessário
    return funcao_a()

# SOLUÇÃO 2 — extrair o código compartilhado para um terceiro módulo
# modulo_comum.py
def logica_compartilhada():
    return "resultado"

# modulo_a.py e modulo_b.py importam de modulo_comum

# SOLUÇÃO 3 — reestruturar a arquitetura
# Geralmente importações circulares indicam problema de design
```

A melhor solução é sempre a reestruturação: se A e B dependem mutuamente, provavelmente há uma abstração faltando.

## importlib: importações dinâmicas

O módulo `importlib` permite importar módulos de forma programática em tempo de execução:

```python
import importlib

# Importar um módulo pelo nome como string
nome_modulo = "json"
modulo = importlib.import_module(nome_modulo)
print(modulo.dumps({"chave": "valor"}))

# Sistema de plugins dinâmico
PLUGINS_DISPONIVEIS = ["plugin_csv", "plugin_json", "plugin_xml"]

plugins = {}
for nome in PLUGINS_DISPONIVEIS:
    try:
        plugins[nome] = importlib.import_module(f"meu_app.plugins.{nome}")
    except ImportError:
        print(f"Plugin {nome} não encontrado, ignorando.")

# Recarregar um módulo (útil em desenvolvimento interativo)
import meu_modulo
importlib.reload(meu_modulo)
```

## Lazy loading: importações sob demanda

Lazy loading adia a importação de módulos pesados até que sejam realmente necessários, melhorando o tempo de inicialização da aplicação:

```python
# Sem lazy loading — tensorflow carregado mesmo se não usado
import tensorflow as tf

def treinar_modelo(dados):
    modelo = tf.keras.Sequential(...)

# Com lazy loading — tensorflow só carregado quando treinar_modelo() for chamado
def treinar_modelo(dados):
    import tensorflow as tf  # importado aqui, na primeira chamada
    modelo = tf.keras.Sequential(...)

# Abordagem mais sofisticada com __getattr__ no módulo
# meu_pacote/__init__.py
def __getattr__(nome):
    if nome == "tensorflow":
        import tensorflow
        return tensorflow
    raise AttributeError(f"Módulo não tem atributo '{nome}'")
```

## Cache de módulos e __pycache__

Quando o Python importa um módulo pela primeira vez, ele **compila o código para bytecode** e armazena em arquivos `.pyc` dentro do diretório `__pycache__`:

```bash
meu_projeto/
├── utils.py
└── __pycache__/
    └── utils.cpython-312.pyc   # bytecode compilado para CPython 3.12
```

O cache de módulos vive em `sys.modules`. O Python reutiliza o módulo já importado em vez de ler e compilar o arquivo novamente:

```python
import sys

# Ver todos os módulos carregados
print(list(sys.modules.keys()))

# Verificar se um módulo específico está no cache
"json" in sys.modules  # True se json foi importado

# Remover módulo do cache (força reimportação)
del sys.modules["meu_modulo"]
import meu_modulo  # reimportado do disco
```

Os arquivos `.pyc` aceleram a importação (Python não precisa compilar novamente) mas são gerados automaticamente — **nunca os versione no Git**. Adicione `__pycache__/` e `*.pyc` ao `.gitignore`.

## Termos Relacionados

- [Pacote](/glossario/pacote/) - Coleção de módulos organizados em diretórios
- [pip](/glossario/pip/) - Gerenciador para instalar módulos de terceiros
- [Python](/glossario/python/) - A linguagem de programação
