Организация больших программ на Python

Beginner

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

Введение

Если вы пишете более крупную программу, вы не хотите организовывать ее как большой набор отдельных файлов на верхнем уровне. В этом разделе представляется концепция пакета.

Модули

Любой исходный файл на Python - это модуль.

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

Инструкция import загружает и исполняет модуль.

## program.py
import foo

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

Пакеты versus Модули

Для более крупных коллекций кода распространено организовывать модули в пакет.

## От этого
pcost.py
report.py
fileparse.py

## До этого
porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

Вы выбираете имя и создаете верхнеуровневую директорию. porty в примере выше (ясно, что выбор этого имени - это самый важный первый шаг).

Добавьте файл __init__.py в директорию. Он может быть пустым.

Разместите исходные файлы в директории.

Использование пакета

Пакет служит именнованным пространством для импортов.

Это означает, что теперь существуют импорты多层次。

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

Существуют и другие варианты инструкций import.

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

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

Два проблемы

При этом подходе есть две главные проблемы.

  • Импорты между файлами в одном пакете сломаются.
  • Главные скрипты, размещенные внутри пакета, сломаются.

Таким образом, в основном все сломается. Но, помимо этого, это работает.

Проблема: Импорты

Импорты между файлами в одном пакете теперь должны включать имя пакета в импорте. Помните структуру.

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

Измененный пример импорта.

from porty import fileparse

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

Все импорты абсолютные, а не относительные.

import fileparse    ## BREAKS. fileparse не найден

...

Относительные импорты

Вместо прямого использования имени пакета можно использовать . для ссылки на текущий пакет.

from. import fileparse

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

Синтаксис:

from. import modname

Это упрощает переименование пакета.

Проблема: Главные скрипты

Запуск подмодуля пакета в качестве основного скрипта завершается ошибкой.

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

Причина: Вы запускаете Python на одном файле, и Python не видит остальную структуру пакета правильно (sys.path неправильный).

Все импорты завершаются ошибкой. Чтобы исправить это, вам нужно запускать программу по-другому, используя параметр -m.

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

Файлы __init__.py

Основная цель этих файлов - скрепить модули вместе.

Пример: объединение функций

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

Это позволяет именам появляться на верхнем уровне при импорте.

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

Вместо использования многоуровневых импортов.

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

Другой способ запуска скриптов

Как уже упоминалось, теперь для запуска скриптов внутри пакета нужно использовать -m package.module.

$ python3 -m porty.pcost portfolio.csv

Есть другой вариант: написать новый верхнеуровневый скрипт.

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

Этот скрипт находится за пределами пакета. Например, рассмотрим структуру каталогов:

pcost.py       ## верхнеуровневый скрипт
porty/         ## каталог пакета
    __init__.py
    pcost.py
 ...

Структура приложения

Организация кода и структура файлов являются ключевыми для поддержки работоспособности приложения.

Для Python нет подхода, который бы "подошел для всех случаев". Однако одна структура, которая подходит для многих задач, выглядит примерно так.

porty-app/
  README.txt
  script.py         ## СКРИПТ
  porty/
    ## КОД БИБЛИОТЕКИ
    __init__.py
    pcost.py
    report.py
    fileparse.py

Верхний уровень porty-app является контейнером для всего остального - документации, верхнеуровневых скриптов, примеров и т.д.

Опять же, верхнеуровневые скрипты (если есть) должны находиться вне пакета кода. На одном уровне выше.

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

porty.report.main(sys.argv)

На этом этапе у вас есть каталог с несколькими программами:

pcost.py          ## вычисляет стоимость портфеля
report.py         ## Создает отчет
ticker.py         ## Генерирует реальный-time биржевую ленту

Есть также различные вспомогательные модули с другой функциональностью:

stock.py          ## Класс акции
portfolio.py      ## Класс портфеля
fileparse.py      ## Парсинг CSV
tableformat.py    ## Форматированные таблицы
follow.py         ## Следить за лог-файлом
typedproperty.py  ## Типизированные свойства класса

В этом упражнении мы собираемся очистить код и поместить его в общий пакет.

Упражнение 9.1: Создание простого пакета

Создайте директорию с именем porty/ и поместите все вышеперечисленные Python-файлы в нее. Кроме того, создайте пустой файл __init__.py и поместите его в директорию. У вас должна быть директория с файлами следующего вида:

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

Удалите файл __pycache__, находящийся в вашей директории. В нем содержатся предварительно скомпилированные Python-модули из предыдущего времени. Мы хотим начать с чистого листа.

Попробуйте импортировать некоторые модули пакета:

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

Если эти импорты не удаются, перейдите в соответствующий файл и исправьте импорты модулей, чтобы включить относительный импорт пакета. Например, инструкция вида import fileparse может измениться следующим образом:

## report.py
from. import fileparse

...

Если у вас есть инструкция вида from fileparse import parse_csv, измените код на следующий:

## report.py
from.fileparse import parse_csv

...

Упражнение 9.2: Создание директории для приложения

Поместить весь ваш код в "пакет" часто недостаточно для приложения. Иногда есть вспомогательные файлы, документация, скрипты и прочие вещи. Эти файлы должны находиться ВНЕ директории porty/, которую вы создали выше.

Создайте новую директорию с именем porty-app. Переместите директорию porty, созданную вами в Упражнении 9.1, в эту директорию. Скопируйте тестовые файлы portfolio.csv и prices.csv в эту директорию. Кроме того, создайте файл README.txt с некоторой информацией о себе. Ваш код теперь должен быть организован следующим образом:

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

Для запуска вашего кода вы должны убедиться, что вы работаете в верхнем уровне директории porty-app/. Например, из терминала:

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

Попробуйте запустить некоторые из ваших предыдущих скриптов в качестве основного программы:

$ 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

$

Упражнение 9.3: Верхнеуровневые скрипты

Использование команды python -m часто выглядит несколько странно. Возможно, вы захотите написать верхнеуровневый скрипт, который просто будет обрабатывать специфические аспекты пакетов. Создайте скрипт print-report.py, который генерирует вышеупомянутый отчет:

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

Разместите этот скрипт в верхнем уровне директории porty-app/. Убедитесь, что вы можете запустить его в этом месте:

$ 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

$

Ваш окончательный код теперь должен быть структурирован примерно так:

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

Резюме

Поздравляем! Вы завершили лабораторную работу по пакетам. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.