Organizando Programas Python Maiores

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Ao escrever um programa maior, você realmente não quer organizá-lo como uma grande coleção de arquivos independentes no nível superior. Esta seção introduz o conceito de um pacote (package).

Módulos (Modules)

Qualquer arquivo fonte Python é um módulo.

## foo.py
def grok(a):
    ...
def spam(b):
    ...

Uma declaração import carrega e executa um módulo.

## program.py
import foo

a = foo.grok(2)
b = foo.spam('Hello')
...

Pacotes (Packages) vs Módulos (Modules)

Para coleções maiores de código, é comum organizar módulos em um pacote.

## De
pcost.py
report.py
fileparse.py

## Para
porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

Você escolhe um nome e cria um diretório de nível superior. porty no exemplo acima (claramente, escolher este nome é o primeiro passo mais importante).

Adicione um arquivo __init__.py ao diretório. Ele pode estar vazio.

Coloque seus arquivos fonte no diretório.

Usando um Pacote (Package)

Um pacote serve como um namespace para imports.

Isso significa que agora existem imports de múltiplos níveis.

import porty.report
port = porty.report.read_portfolio('portfolio.csv')

Existem outras variações de declarações de import.

from porty import report
port = report.read_portfolio('portfolio.csv')

from porty.report import read_portfolio
port = read_portfolio('portfolio.csv')

Dois problemas

Existem dois problemas principais com esta abordagem.

  • Imports entre arquivos no mesmo pacote quebram.
  • Scripts principais colocados dentro do pacote quebram.

Então, basicamente, tudo quebra. Mas, fora isso, funciona.

Problema: Imports

Imports entre arquivos no mesmo pacote agora devem incluir o nome do pacote no import. Lembre-se da estrutura.

porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

Exemplo de import modificado.

from porty import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

Todos os imports são absolutos, não relativos.

import fileparse    ## QUEBRA. fileparse não encontrado

...

Imports Relativos

Em vez de usar diretamente o nome do pacote, você pode usar . para se referir ao pacote atual.

from . import fileparse

def read_portfolio(filename):
    return fileparse.parse_csv(...)

Sintaxe:

from . import modname

Isso facilita a renomeação do pacote.

Problema: Scripts Principais

Executar um submódulo de pacote como um script principal quebra.

$ python porty/pcost.py ## QUEBRA
...

Razão: Você está executando Python em um único arquivo e Python não vê o restante da estrutura do pacote corretamente (sys.path está errado).

Todos os imports quebram. Para corrigir isso, você precisa executar seu programa de uma maneira diferente, usando a opção -m.

$ python -m porty.pcost ## FUNCIONA
...

Arquivos __init__.py

O objetivo principal desses arquivos é unir módulos.

Exemplo: consolidando funções

## porty/__init__.py
from .pcost import portfolio_cost
from .report import portfolio_report

Isso faz com que os nomes apareçam no nível superior (top-level) ao importar.

from porty import portfolio_cost
portfolio_cost('portfolio.csv')

Em vez de usar os imports de múltiplos níveis.

from porty import pcost
pcost.portfolio_cost('portfolio.csv')

Outra solução para scripts

Como observado, agora você precisa usar -m package.module para executar scripts dentro do seu pacote.

$ python3 -m porty.pcost portfolio.csv

Há outra alternativa: Escrever um novo script de nível superior (top-level).

#!/usr/bin/env python3
## pcost.py
import porty.pcost
import sys
porty.pcost.main(sys.argv)

Este script reside fora do pacote. Por exemplo, observando a estrutura de diretórios:

pcost.py       ## script de nível superior (top-level-script)
porty/         ## diretório do pacote
    __init__.py
    pcost.py
    ...

Estrutura da Aplicação

A organização do código e a estrutura de arquivos são fundamentais para a manutenibilidade de uma aplicação.

Não existe uma abordagem "tamanho único" para Python. No entanto, uma estrutura que funciona para muitos problemas é algo como isto.

porty-app/
  README.txt
  script.py         ## SCRIPT
  porty/
    ## CÓDIGO DA BIBLIOTECA
    __init__.py
    pcost.py
    report.py
    fileparse.py

O nível superior porty-app é um contêiner para todo o resto -- documentação, scripts de nível superior, exemplos, etc.

Novamente, os scripts de nível superior (se houver) precisam existir fora do pacote de código. Um nível acima.

#!/usr/bin/env python3
## porty-app/script.py
import sys
import porty

porty.report.main(sys.argv)

Neste ponto, você tem um diretório com vários programas:

pcost.py          ## calcula o custo do portfólio
report.py         ## Cria um relatório
ticker.py         ## Produz um ticker de ações em tempo real

Há uma variedade de módulos de suporte com outras funcionalidades:

stock.py          ## Classe Stock
portfolio.py      ## Classe Portfolio
fileparse.py      ## Parsing CSV
tableformat.py    ## Tabelas formatadas
follow.py         ## Seguir um arquivo de log
typedproperty.py  ## Propriedades de classe tipadas

Neste exercício, vamos limpar o código e colocá-lo em um pacote comum.

Exercício 9.1: Criando um pacote simples

Crie um diretório chamado porty/ e coloque todos os arquivos Python acima nele. Adicionalmente, crie um arquivo __init__.py vazio e coloque-o no diretório. Você deve ter um diretório de arquivos como este:

porty/
    __init__.py
    fileparse.py
    follow.py
    pcost.py
    portfolio.py
    report.py
    stock.py
    tableformat.py
    ticker.py
    typedproperty.py

Remova o arquivo __pycache__ que está no seu diretório. Ele contém módulos Python pré-compilados de antes. Queremos começar do zero.

Tente importar alguns dos módulos do pacote:

>>> import porty.report
>>> import porty.pcost
>>> import porty.ticker

Se essas importações falharem, vá para o arquivo apropriado e corrija as importações do módulo para incluir uma importação relativa ao pacote. Por exemplo, uma instrução como import fileparse pode mudar para o seguinte:

## report.py
from . import fileparse
...

Se você tiver uma instrução como from fileparse import parse_csv, altere o código para o seguinte:

## report.py
from .fileparse import parse_csv
...

Exercício 9.2: Criando um diretório de aplicação

Colocar todo o seu código em um "pacote" (package) geralmente não é suficiente para uma aplicação. Às vezes, existem arquivos de suporte, documentação, scripts e outras coisas. Esses arquivos precisam existir FORA do diretório porty/ que você criou acima.

Crie um novo diretório chamado porty-app. Mova o diretório porty que você criou no Exercício 9.1 para dentro desse diretório. Copie os arquivos de teste portfolio.csv e prices.csv para este diretório. Adicionalmente, crie um arquivo README.txt com algumas informações sobre você. Seu código agora deve ser organizado da seguinte forma:

porty-app/
    portfolio.csv
    prices.csv
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py

Para executar seu código, você precisa ter certeza de que está trabalhando no diretório de nível superior porty-app/. Por exemplo, no terminal:

$ cd porty-app
$ python3
>>> import porty.report
>>>

Tente executar alguns de seus scripts anteriores como um programa principal:

$ cd porty-app
$ python3 -m porty.report portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

$

Exercício 9.3: Scripts de nível superior

Usar o comando python -m é frequentemente um pouco estranho. Você pode querer escrever um script de nível superior que simplesmente lida com as peculiaridades dos pacotes. Crie um script print-report.py que produza o relatório acima:

#!/usr/bin/env python3
## print-report.py
import sys
from porty.report import main
main(sys.argv)

Coloque este script no diretório de nível superior porty-app/. Certifique-se de que você pode executá-lo nesse local:

$ cd porty-app
$ python3 print-report.py portfolio.csv prices.csv txt
      Name     Shares      Price     Change
---------- ---------- ---------- ----------
        AA        100       9.22     -22.98
       IBM         50     106.28      15.18
       CAT        150      35.46     -47.98
      MSFT        200      20.89     -30.34
        GE         95      13.48     -26.89
      MSFT         50      20.89     -44.21
       IBM        100     106.28      35.84

$

Seu código final agora deve ser estruturado mais ou menos assim:

porty-app/
    portfolio.csv
    prices.csv
    print-report.py
    README.txt
    porty/
        __init__.py
        fileparse.py
        follow.py
        pcost.py
        portfolio.py
        report.py
        stock.py
        tableformat.py
        ticker.py
        typedproperty.py

Resumo

Parabéns! Você concluiu o laboratório de Pacotes (Packages). Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.