Перевод: "Практическое программирование на Python" 09_01_Packages

Python
Перевод: "Практическое программирование на Python" 09_01_Packages

содержание| Предыдущий раздел (8.3 Отладка) | Следующий раздел (9.2 Сторонние пакеты)

9.1 Пакет

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

модуль

Любой исходный файл Python называется модулем.

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

одинimportоператор загружает ивоплощать в жизньмодуль.

# program.py
import foo

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

пакет против модуля

Для больших коллекций кода модули обычно объединяются в пакеты.

# From this
pcost.py
report.py
fileparse.py

# To this
porty/
    __init__.py
    pcost.py
    report.py
    fileparse.py

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

Затем добавьте__init__.pyфайлы в этот каталог.__init__.pyфайл может быть пустым файлом.

Наконец, поместите исходные файлы в этот каталог.

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

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

Это означает, что теперь есть многоуровневый импорт.

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

Существуют и другие варианты операторов импорта:

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

Пример измененного импорта в соответствии с указанными выше правилами (импорт между разными файлами в одном пакете должен содержать имя пакета):

# report.py
from porty import fileparse

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

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

# report.py
import fileparse    # BREAKS. fileparse not found

...

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

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

# report.py
from . import fileparse

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

грамматика:

from . import modname

Использование приведенного выше синтаксиса упрощает переименование пакетов.

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

Запуск подмодуля внутри пакета в качестве основного скрипта приводит к поломке программы:

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

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

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

bash $ 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Запустите скрипт внутри пакета.

bash % python3 -m porty.pcost portfolio.csv

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

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

Скрипт находится вне пакета. Структура каталогов следующая:

pcost.py       # top-level-script
porty/         # package directory
    __init__.py
    pcost.py
    ...

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

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

Не существует универсального подхода к Python, но есть одна структура, которая работает для многих видов проблем:

porty-app/
  README.txt
  script.py         # SCRIPT
  porty/
    # LIBRARY CODE
    __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          # computes portfolio cost
report.py         # Makes a report
ticker.py         # Produce a real-time stock ticker

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

stock.py          # Stock class
portfolio.py      # Portfolio class
fileparse.py      # CSV parsing
tableformat.py    # Formatted tables
follow.py         # Follow a log file
typedproperty.py  # Typed class properties

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

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

Пожалуйста, создайтеportyкаталог и поместите в него все вышеперечисленные файлы Python. Кроме того, вportyсоздать пустой каталог__init__.pyдокумент. Наконец, каталог файлов выглядит так:

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

пожалуйстаportyв каталоге__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новый каталог. Затем используйте тот, который мы создали в упражнении 9.1.portyкаталог перемещен вporty-appв каталоге. Далее копируем тестовый файлData/portfolio.csvиData/prices.csvприбытьporty-appсодержание. Кроме того, вporty-appсоздать каталог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/Вниз. Например, из терминала запустите:

shell % cd porty-app
shell % python3
>>> import porty.report
>>>

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

shell % cd porty-app
shell % 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

shell %

Упражнение 9.3. Сценарий верхнего уровня

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

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

потом поставь скриптprint-report.py положить в верхний каталогporty-app/середина. и убедитесь, чтоporty-app/Запустите его в каталоге:

shell % cd porty-app
shell % 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

shell %

В итоге организация кода должна выглядеть так:

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

содержание| Предыдущий раздел (8.3 Отладка) | Следующий раздел (9.2 Сторонние пакеты)

Примечание. Полный перевод см.GitHub.com/co Статья 3 — /PRA…