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

PythonPythonBeginner
Практиковаться сейчас

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, чтобы улучшить свои навыки.