Модули в Python
Модули – это организационная единица самого высокого уровня в Python. Если вы хотя бы немного знакомы с Python, вероятно уже использовали готовые модули или создали свои. Что же такое модуль? Модули – это программные единицы, хранящие код и данные, обеспечивающие повторное использование кода для Python проектов и также полезны при разбиении пространств имён в автономных пакетах. Они автономны, потому что получить доступ к атрибутам модуля только после их импорта. Их также можно рассматривать, как пакеты имён, которые при импорте становятся атрибутами импортированного модуля. Фактически, любой файл Python с расширением *.py является модулем.
Создание модуля
Для создания модуля в Python достаточно определить файл с суффиксом .py. Хотя не каждый файл Python предназначен для импорта в качестве модуля. Файлы Python, которые используются для работы в качестве автономного приложения Python (файлы верхнего уровня), обычно предназначены для запуска в виде скриптов, а импорт их, фактически запускает команды внутри скрипта.
Модули, которые предназначены для импорта другим кодом, не будут выполнять какое-либо действия, а только предоставят доступ к своим переменным и функциям в качестве атрибутов импортируемого объекта. Также возможно создавать двухрежимные модули, которые могут быть использованы для импорта и запуска в качестве сценария верхнего уровня.
Хотя правила создания модулей довольно условные, существует одно правило для именования модулей. Поскольку имена файлов модулей становятся именами переменных в Python при импорте не разрешается называть модули зарезервированными словами Python. Например, модуль for.py может быть создан, но не может быть импортирован, поскольку «for» – зарезервированное слово. Проиллюстрируем всё вышесказанной примером с «Hello world!».
# Файл модуля: my_module.py
def hello_printer():
print("Hello world!")
name = "John"
# Скрипт файл: my_script.py
import my_module
my_module.hello_printer()
print("Creator:", my_module.name)
My_module.py представлен как модуль, код которого может быть импортирован и повторно использован в других файлах Python. Он не требует какого-либо дополнительных действий, просто определяет функции и переменные. «my_script.py» разработан как скрипт верхнего уровня, который явно вызывает функцию hello_printer и печатает значение переменной.
Запустим файл my_script.py в терминале:
$ python my_script.py
Hello world!
Creator: John
Как отмечалось ранее, важный вывод из этого первого базового примера – это то, что важны имена файлов модулей. После импорта они становятся переменными/объектами в модуле импортера. Все определения верхнего уровня в модуле становятся атрибутами этой переменной.
Под «верхним уровнем» подразумевается любая другая функция или переменная, которая не вложена внутри другой функции или класса. Затем эти атрибуты можно получить с помощью стандартного оператора <object>.<Attribute>.
Архитектура программы
Любая нетривиальная программа будет представлена несколькими файлами, связанными друг с другом импортами. Python, как и большинство других языков программирования, использует модульную структуру программы, где функциональные возможности группируются в многократно используемые единицы. Различают три типа файлов в многофайловом приложении:
- Файл верхнего уровня. Python скрипт, который является основной точкой входа в программу. Используется для запуска приложения.
- Пользовательские модули. Файлы Python, которые импортируются в файл верхнего уровня или между собой, определяющие отдельные функции. Такие файлы обычно не запускаются непосредственно из командной строки и создаются для решения узкоспециализированных задач проекта.
- Модули стандартной библиотеки. Модули, встроенные в установочный пакет Python, такие как независимые от платформы инструменты для системных интерфейсов, сценарии работы с Интернет протоколами, построение графических интерфейсов и другие.
На этом рисунке модуль top_module.py – это файл Python верхнего уровня, который импортирует функции, определенные в модуле module1, но также имеет доступ к функциям в module2 через module1. Два пользовательских модуля используют ресурсы друг друга, а также другие модули из стандартной библиотеки Python. Цепочка импорта может идти настолько глубоко, насколько нужно – нет ограничений в количестве импортированных файлов, и они могут импортировать друг друга, хотя нужно быть осторожным с циклическими импортами.
# top_module.py
import module1
module1.print_parameters()
print(module1.combinations(5, 2))
# module1.py
from module2 import k, print_parameters
from math import factorial
n = 5.0
def combinations(n, k):
return factorial(n) / factorial(k) / factorial(n-k)
# module2.py
import module1
k = 2.0
def print_parameters():
print('k = %.f n = %.f' % (k, module1.n))
В приведенном выше примере top_module.py – это модуль верхнего уровня, который будет запускаться пользователем, и он импортирует инструменты из других модулей через module1.py. module1 и module2 являются определяемыми пользователем модулями, а модуль math импортируется из стандартной библиотеки. При запуске скрипта верхнего уровня получаем:
$ python top_module.py
k = 2 n = 5
10.0
Когда запускается файл Python верхнего уровня, его операторы исходного кода и операторы в импортированных модулях компилируются в промежуточный формат, известный как байт код, который является независимым от платформы. Файлы байт кода импортированных модулей хранятся с расширением .pyc в том же каталоге, что и .py-файл для версий Python до 3.2, и в каталоге pycache/ в текущем каталоге программы в Python 3.2+.
$ ls __pycache__/
module1.cpython-36.pyc module2.cpython-36.pyc
Двухрежимный код
Как упоминалось ранее, файлы Python также могут быть разработаны как импортируемые модули, так и сценарии верхнего уровня. То есть при запуске модуль Python запускается как автономная программа, а при импорте он будет работать как импортируемый модуль.
Это легко сделать, используя атрибут name, который автоматически встроен в каждый модуль. Если модуль запущен как скрипт верхнего уровня, атрибут name будет равен строке «main», иначе, если импортирован, он будет содержать имя фактического модуля.
Пример двухрежимного кода:
# hiprinter.py
# Часть определений имен
multiply = 3
def print_hi():
print("Hi!" * multiply)
# Стэндалон часть скрипта
if __name__ == '__main__':
print_hi()
Если файл запускается как отдельная программа, то такая же функция вызывается автоматически. Разница здесь, по сравнению с примером my_script.py в разделе main, заключается в том, что когда hiprinter.py импортируется, он не будет запускать код, вложенный в оператор if name == 'main'.
# Терминальное окно
$ python hiprinter.py
Hi!Hi!Hi!
# интерпритатор Python
>> import hiprinter
>> hiprinter.print_hi()
Hi!Hi!Hi!
Двухрежимный код очень распространен на практике и особенно полезен для юниттестов, т. е. в то время как переменные и функции определяются как имена файла верхнего уровня, часть внутри оператора if может служить в качестве тестовых значений вышеописанных имён.
Оператор import
Основное различие между import и from заключается в том, что import загружает весь модуль в виде единого объекта, а from загружает определенные свойства и функции из модуля. Импорт имен с помощью оператора from может быть использован непосредственно в модуле импортера без вызова имени импортируемого объекта.
Использование инструкции from разрешено только в файле верхнего уровня модуля в Python 3.x, а не внутри функции. Python 2.x позволяет использовать его в функции, но выдает предупреждение. Оператор from медленнее import, поскольку он выполняет всю работу, которую выполняет import через весь контент импортированного модуля, а затем выполняет дополнительные действия при выборе соответствующих имён для импорта.
Существует также третий оператор импорта *, который используется для импорта всех имен в верхний уровень из импортированного модуля и использования их непосредственно в классе импортера. Например:
from module2 import *
Код импортирует все имена (переменные и функции) из файла module2.py. Этот подход не рекомендуется из-за возможного дублирования имен, импортированные имена могут перезаписывать уже существующие имена в модуле импортера.
Каталог поиска модуля
Одним из важных аспектов при написании модульных приложений Python является поиск импортируемых модулей. Хотя модули стандартной библиотеки Python настроены на глобальную доступность, импорт пользовательских модулей за границей каталога может усложниться.
Python использует внутренний список каталогов, в которых он ищет модули, известные как путь поиска. Путь поиска состоит из каталогов, построенных в следующем порядке:
- Домашняя директория программы. Расположение сценария верхнего уровня. Обратите внимание, что домашний каталог может не совпадать с текущим рабочим каталогом.
- Каталоги, перечисленные в переменной окружения PYTHONPATH.
- Стандартные библиотечные каталоги. Каталоги автоматически устанавливаются с установкой Python и в них всегда выполняются поиск.
- Каталоги, перечисленные в .pth файлах. Этот вариант является альтернативой PYTHONPATH, и он добавляет ваши каталоги, указанные по одному в строке, в текстовый файл с суффиксом .pth, который должен быть помещен в каталог установки Python, который обычно является /usr/local/lib/python3.6/ в Unix или C:\Python36\ в Windows.
- Каталог site-packages. В этом каталоге автоматически располагаются все сторонние расширения.
PYTHONPATH, вероятно, является наиболее предпочтительным способом. Вы можете легко проверить, установлена ли переменная на вашем компьютере выполнив в командной строке:
$ echo $PYTHONPATH
/Users/Code/Projects/:
Чтобы создать переменную на компьютере с Windows, вы должны использовать инструкции в «Панель управления -> Система -> Дополнительно», в то время как в MacOS и других Unix-системах проще всего добавить следующую строку в ~ / .bashrc или ~ /. bash_profile, где каталоги конкатенируются символом двоеточия (":").
export PYTHONPATH=<Directory1:Directory2:...:DirectoryN>:$PYTHONPATH".
После того, как все каталоги поиска во время запуска программы будут определены, они сохраняются в списке, который можно увидеть с помощью sys.path. Также можно добавить каталог в sys.path, а затем импортировать свои модули, которые будут изменять каталоги поиска во время выполнения программы.
В любом случае параметры PYTHONPATH и .pth позволяют обеспечить более долговременное хранение каталогов поиска. Важно знать, что Python сканирует строку каталога поиска слева направо, поэтому модули в левых каталогах могут переписаны именами с одним и тем же именем в правой части. Обратите внимание, что каталоги поиска модулей необходимы только для импорта модулей, находящихся в разных каталогах.
Как показано в следующем примере, пустая строка в начале списка определяет текущий каталог:
import sys
sys.path
['',
'/Users/Code/Projects',
'/Users/Code/Projects/Blogs',
'/Users/Code/anaconda3/lib/python36.zip',
'/Users/Code/anaconda3/lib/python3.6',
'/Users/Code/anaconda3/lib/python3.6/site-packages',
'/Users/Code/anaconda3/lib/python3.6/site-packages/IPython/extensions',
'/Users/Code/.ipython']
В заключение, организация программы Python в нескольких взаимосвязанных модулях будет простой, если программа хорошо структурирована.
Перезагрузка модуля
Благодаря кэшированию модуль может быть импортирован процессом только один раз. Поскольку язык Python интерпретируемый, он запускает код импортированного модуля, когда достигнет инструкции import. Следующий import в рамках одного процесса (например, тот же интерпретатор Python) повторно не будет запускать код импортированного модуля. Он просто извлечет модуль из кеша.
Например, импортируем код в my_module.py в интерпретаторе Python, затем изменим файл и снова импортируем.
>> import my_module
>> print(my_module.name)
John
# Теперь модифицируем переменную 'name' в 'my_module.py' name = 'Jack' и реимпортируем модуль
>> import my_module
>> print(my_module.name)
John
Чтобы отключить кеширование и включить повторный импорт модулей, Python предоставляет функцию reload. Повторим пример:
>> from imp import reload # Python3.x
>> reload(my_module)
<module 'my_module' from '/Users/Code/Projects/small_example/my_module.py'>
>> print(my_module.name)
Jack
Функция reload изменяет модуль на месте. То есть, не затрагивая другие объекты, ссылающиеся на импортированный модуль. Обращаем ваше внимание, что функция также возвращает сам модуль, указывая его имя и путь к файлу. Эта функция особенно полезна на этапе разработки крупных проектов.
Например, для программ, которым требуется постоянная связь с сервером, гораздо сложнее перезапустить все приложение, чем выполнять динамическую перезагрузку для выполнения во время разработки.
Пакеты модулей
При импорте имен модулей фактически загружаются файлы Python, хранящиеся где-то в файловой системе. Как уже упоминалось ранее, импортированные модули должны находиться в каталоге, который указан в пути поиска модуля (sys.path). В Python есть нечто большее, чем «импорт имен» – вы можете импортировать весь каталог, содержащий файлы Python, в виде пакета модулей. Этот импорт известен как импорт пакетов.
Как импортировать пакеты модулей? Давайте создадим каталог с именем mydir, который включает в себя модуль mod0.py и два подкаталога subdir1 и subdir2, содержащий модули mod1.py и mod2.py соответственно. Структура каталогов выглядит так:
$ ls -R mydir/
mod0.py subdir1 subdir2
my_dir/subdir1:
mod1.py
my_dir/subdir2:
mod2.py
Обычный подход, который объяснялся до сих пор, заключался в том, чтобы добавить пути mydir, subdir1 и subdir2 к каталоги поиска модуля (sys.path), чтобы иметь возможность импортировать mod0.py, mod1. py и mod2.py. Это может привести к большим накладным расходами, если модули распределены по множеству разных подкаталогов. В любом случае, импорт пакетов будет предпочтительнее, потому что работают с импортом имени самой папки.
Недопустимая инструкция, приводящая к ошибке InvalidSyntax:
>> import /Users/Code/Projects/mydir/
File "<stdin>", line 1
import /Users/Code/Projects/mydir/
^
SyntaxError: invalid syntax
Правильный способ выполнить это – установить каталог /Users/Code/Projects/ в путь поиска модуля (добавив его в переменную среды PYTHONPATH или указав её в файле .pth), а затем импортировать модули, используя точечный синтаксис.
>> import mydir.mod0
>> import mydir.subdir1.mod1 as mod1
>> from mydir.subdir2.mod2 import print_name
Вы, наверное, уже замечали, что некоторые Python каталоги содержат файл init.py. Это было фактически требование в Python2.x, чтобы сообщить Python, что ваш каталог является пакетым модулем. Файл init.py также является обычным файлом Python, который запускается всякий раз, когда этот каталог импортируется, и подходит для инициализации значений, например, для подключения к базе данных.
В большинстве случаев эти файлы просто пусты. В Python3.x эти файлы являются необязательными, и их можно использовать, если это необходимо. Следующие несколько строк показывают, как имена, определенные в init.py, становятся атрибутами импортируемого объекта (имя каталога, содержащего его).
# __init__.py file in mydir/subdir1/ :
param = "init subdir1"
print(param)
# Импорт модуля из интерпретатора Python
>> import mydir.subdir1.mod1
init subdir1
# Параметр также доступен как атрибут объекта mydir.subdir1
>> print(mydir.subdir1.param)
init subdir1
Еще одна важная тема, когда речь идёт о пакетных модулях – относительный импорт. Относительный импорт полезен при импорте модулей внутри самого пакета. В этом случае Python будет искать импортированный модуль внутри пакета, а не в путях поиска модуля.
# mydir/subdir1/mod1.py
import mod2
# В интерпретаторе Python:
>> import mydir.subdir1.mod1
ModuleNotFoundError: No module named 'mod2'
Строка импорта mod2 сообщает Python о поиске модуля mod2 в sys.path, и поэтому он не увенчался успехом. В данном случае нам поможет относительный импорт. Следующий оператор относительного импорта использует двойную точку (".."), которая обозначает родительский элемент текущего пакета ('mydir /'). Для создания полного относительного пути к модулю mod2 необходимо включить следующий subdir2.
# mydir/subdir1/mod1.py
from ..subdir2 import mod2
Относительный импорт – это огромная тема, которая может занять целую книгу. Они также сильно отличаются между версиями Python2.x и 3.x. Поддержка Python 2.x заканчивается в 2020 году, поэтому в тех случаях, когда существует большая разница между версиями Python, например, в относительном импорте, лучше сосредоточиться на версии 3.x.
Публикация пакета в PyPi
На текущий момент вы узнали, как писать модули Python, различать импортируемые модули и высокоуровневые, использовать определенные пользователем модули в границах каталога, изменять путь поиска модулей и, кроме всего прочего, создавать/импортировать пакеты модулей. После создания полезного программного обеспечения, упакованного в пакет модулей, вы можете поделиться им с большим сообществом Python. Пакетный индекс Python (PyPi) – это программный репозитарий, в настоящее время содержащий более 120 тыс. пакетов (на момент написания этой статьи). Возможно, вы уже устанавливали модули из репозитория с помощью команды pip.
Например, следующая строка загрузит и устанавливать библиотеку Numpy:
$ pip install numpy
Как опубликовать свой пакет в репозитарии? Во-первых, он должен удовлетворять требованиям к упаковке и распространению. Необходимо выполнить два условия: установить pip, setuptools, wheel и установить twine, который используется для загрузки проекта в PyPi.
$ pip install twine
Следующим шагом будет настройка вашего проекта. В общем, это означает добавление нескольких файлов Python в проект, которые будут содержать информацию о конфигурации, руководства по использованию и т. д. Далее перечислены самые важные файлы, которые нужно добавить: setup.py. Этот файл необходимо добавить в корень проекта и использовать в качестве интерфейса установки из командной строки. Он должен содержать функцию setup(), которая будет принимать в качестве аргумента информацию, такую как: имя проекта, версия, описание, лицензия, зависимости проекта и т. д. README.rst: текстовый файл, описывающий пакет. licence.txt: текстовый файл, содержащий лицензию на программное обеспечение.
Наиболее востребованным форматом пакета является wheel. Запуск одной из следующих команд приведет к созданию каталога dist/ в корне вашего проекта, который содержит файлы в пакет для загрузки в PyPi.
# Пакет как источник распространения
$ python setup.py sdist
# wheel пакет, поддерживает одну версию Python
$ python setup.py bdist_wheel
Последний шаг – загрузка вашего дистрибутива в PyPi. Для этого нудно создать учетную запись на сайте PyPi и загрузить содержимое каталога dist/, созданного на предыдущем шаге.
twine upload dist/*
Для получения дополнительной информации обратитесь к официальному сайту PyPi.