Сколько библиотек можно импортировать в один проект python
Перейти к содержимому

Сколько библиотек можно импортировать в один проект python

  • автор:

Rukovodstvo

статьи и идеи для разработчиков программного обеспечения и веб-разработчиков.

Модули Python: создание, импорт и совместное использование

Введение Модули — это организационная единица высшего уровня в Python. Если вы хотя бы немного знакомы с Python, вы, вероятно, не только использовали готовые модули, но и создали несколько самостоятельно. Так что же такое модуль? Модули [https://docs.python.org/3/tutorial/modules.html] — это модули, которые хранят код и данные, обеспечивают повторное использование кода для проектов Python, а также полезны при разделении пространств имен системы в автономных пакетах. . Они самодостаточны, потому что вы можете только

Время чтения: 15 мин.

Вступление

Модули — это организационная единица высшего уровня в Python. Если вы хотя бы немного знакомы с Python, вы, вероятно, не только использовали готовые модули, но и создали несколько самостоятельно. Так что же такое модуль? Модули — это единицы, которые хранят код и данные, обеспечивают повторное использование кода для проектов Python, а также полезны при разделении пространств имен системы на автономные пакеты. Они самодостаточны, потому что вы можете получить доступ к атрибутам модуля только после его импорта. Вы также можете понимать их как пакеты имен, которые при импорте становятся атрибутами импортируемого объекта модуля. Фактически, любой файл Python с расширением .py представляет собой модуль.

В этой статье мы начнем с основных основ создания и импорта модулей, чтобы перейти к более сложным сценариям использования модулей, а также к упаковке и отправке ваших модулей в «официальный» репозиторий программного обеспечения Python, разделенных соответственно на три части: Создание модуля , Использование модуля и отправка пакета в PyPI .

Создание модуля

Основы

На самом деле нет особой философии в создании модуля Python, поскольку файлы с суффиксом .py представляют собой модуль. Хотя не каждый файл Python предназначен для импорта в виде модуля. Файлы Python, которые используются для запуска в качестве автономного приложения Python (файлы верхнего уровня), обычно предназначены для запуска как сценарии, и их импорт фактически запускает команды в сценарии.

Модули, которые предназначены для импорта другим кодом, не будут выполнять какой-либо код, а только отображают его имена верхнего уровня в качестве атрибутов для импортируемого объекта. Также возможно разработать модули Python с двухрежимным кодом, которые можно использовать как для импорта, так и для запуска в качестве сценария верхнего уровня.

В то время как правила создания модуля являются довольно расслаблены, есть одно правило на модуле именования. Поскольку имена файлов модулей при импорте становятся именами переменных в Python, не разрешается называть модули зарезервированными словами Python. Например, модуль for.py может быть создан, но не может быть импортирован, потому что «for» — зарезервированное слово. Давайте проиллюстрируем то, что мы уже упоминали, в «Hello world!» пример.

My_module.py разработан как модуль, код которого можно импортировать и повторно использовать в других файлах Python. Вы можете видеть это по его содержанию: он не требует никаких действий, а просто определяет функции и переменные. Напротив, my_script.py разработан как сценарий верхнего уровня, который запускает программу Python — он явно вызывает функцию hello_printer и выводит значение переменной на экран.

Запустим в терминале файл my_script.py:

Как отмечалось ранее, важным выводом из этого первого базового примера является то, что имена файлов модулей важны. После импорта они становятся переменными / объектами в модуле импортера. Все определения кода верхнего уровня в модуле становятся атрибутами этой переменной.

Под «верхним уровнем» я подразумеваю любую функцию или переменную, которая не вложена в другую функцию или класс. Затем к этим атрибутам можно получить доступ с помощью стандартного <object>.<attribute> в Python.

В следующем разделе мы сначала рассмотрим «общую картину» многофайловых программ Python, а затем рассмотрим файлы Python «двойного режима».

Архитектура программы

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

  • файл верхнего уровня : файл Python или скрипт , который является основной точкой входа в программу. Этот файл запускается для запуска вашего приложения.
  • определяемые пользователем модули : файлы Python, которые импортируются в файл верхнего уровня или между собой и предоставляют отдельные функции. Эти файлы обычно не запускаются непосредственно из командной строки и создаются специально для целей проекта.
  • стандартные библиотечные модули : предварительно закодированные модули, встроенные в установочный пакет Python, такие как платформенно-независимые инструменты для системных интерфейсов, сценарии Интернета, создание графического интерфейса и другие. Эти модули не являются частью самого исполняемого файла Python, а являются частью стандартной библиотеки Python .

На рисунке 1 показан пример структуры программы с тремя типами файлов:

Рисунок 1: Пример структуры программы, включая сценарий верхнего уровня, пользовательские модули и стандартные библиотечные модули.

На этом рисунке модуль top_module.py представляет собой файл Python верхнего уровня, который импортирует инструменты, определенные в модуле «module1», но также имеет доступ к инструментам в «module2» через «module1». Два настраиваемых модуля используют ресурсы друг друга, а также другие модули из стандартной библиотеки Python. Цепочка импорта может быть сколь угодно глубокой: количество импортируемых файлов не ограничено, и они могут импортировать друг друга, хотя с циклическим импортом нужно быть осторожным.

Проиллюстрируем это на примере кода:

В приведенном выше примере «top_module.py» — это модуль верхнего уровня, который будет запускаться пользователем, и он импортирует инструменты из других модулей через «module1.py». module1 и module2 — это определяемые пользователем модули, а модуль «math» импортируется из стандартной библиотеки Python. При запуске скрипта верхнего уровня получаем:

При запуске файла Python верхнего уровня его операторы исходного кода и операторы в импортированных модулях компилируются в промежуточном формате, известном как байт-код , который не зависит от платформы. Файлы байтового кода импортированных модулей хранятся с расширением .pyc в том же каталоге, что и файл .py для версий Python до 3.2, и в каталоге __pycache__ в домашнем каталоге программы в Python 3.2+.

Двухрежимный код

Как упоминалось ранее, файлы Python также могут быть спроектированы как импортируемые модули, так и сценарии верхнего уровня. То есть при запуске модуль Python будет работать как отдельная программа, а при импорте он будет действовать как импортируемый модуль, содержащий определения кода.

Это легко сделать с помощью атрибута __name__ , который автоматически встроен в каждый модуль. Если модуль запускается как сценарий верхнего уровня, атрибут __name__ будет равен строке «__main__», в противном случае, если он импортирован, он будет содержать имя фактического модуля.

Вот пример двухрежимного кода:

Вышеупомянутый файл ‘hiprinter.py’ определяет функцию, которая будет доступна клиенту при импорте. Если файл запускается как отдельная программа, та же функция вызывается автоматически. Разница здесь по сравнению с примером my_script.py в разделе «Основы» заключается в том, что при импорте hiprinter.py он не будет запускать код, вложенный в оператор if __name__ == ‘__main__’

Двухрежимный код очень распространен на практике и особенно полезен для модульного тестирования: в то время как переменные и функции определены как имена верхнего уровня в файле, часть внутри if может служить областью тестирования вышеупомянутого определенного имена.

Использование модуля

Заявления об импорте

Пример из раздела « Архитектура программы» был полезен, чтобы увидеть разницу между двумя операторами import : import и from . Основное отличие состоит в том, что import загружает весь модуль как единый объект, а from загружает определенные свойства и функции из модуля. Импорт имен с помощью from затем можно использовать непосредственно в модуле импортера, не вызывая имя импортированного объекта.

Использование from разрешено только на верхнем уровне файла модуля в Python 3.x, но не внутри функции. Python 2.x позволяет использовать его в функции, но выдает предупреждение. С точки зрения производительности from работает медленнее, чем import потому что он выполняет всю работу, которую import — просматривает все содержимое импортированного модуля, а затем выполняет дополнительный шаг по выбору подходящих имен для импорта.

Также существует третий оператор импорта from * который используется для импорта всех имен верхнего уровня из импортированного модуля и использования их непосредственно в классе импортера. Например, мы могли бы использовать:

Это позволит импортировать все имена (переменные и функции) из файла module2.py. Этот подход не рекомендуется из-за возможного дублирования имен — импортированные имена могут перезаписать уже существующие имена в модуле импортера.

Путь поиска модуля

Одним из важных аспектов написания модульных приложений Python является поиск модулей, которые необходимо импортировать. Хотя модули стандартной библиотеки Python настроены для глобального доступа, импорт определяемых пользователем модулей через границы каталогов может стать более сложным.

Python использует список каталогов, в которых он ищет модули, известный как путь поиска . Путь поиска состоит из следующих каталогов:

  1. Домашний каталог программы. Расположение сценария верхнего уровня. Обратите внимание, что домашний каталог может не совпадать с текущим рабочим каталогом .
  2. PYTHONPATH . Если установлено, PYTHONPATH определяет конкатенацию определенных пользователем каталогов, в которых интерпретатор Python должен искать модули.
  3. Стандартные библиотечные каталоги. Эти каталоги автоматически устанавливаются при установке Python и всегда ищутся.
  4. Каталоги, перечисленные в файлах .pth. Этот вариант является альтернативой PYTHONPATH , и он работает, добавляя ваши каталоги, по одному на строку, в текстовый файл с суффиксом .pth , который следует поместить в каталог установки Python, который обычно / usr / local / lib / python3 .6 / на машине Unix или C: \ Python36 \ на машине Windows.
  5. Каталог сайтов-пакетов. В этот каталог автоматически добавляются все сторонние расширения.

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

Чтобы создать переменную на компьютере с Windows, вы должны использовать инструкции в «Панель управления -> Система -> Дополнительно», в то время как в MacOS и других системах Unix проще всего добавить следующую строку либо в

/. bash_profile , где ваши каталоги объединены знаком двоеточия (":").

После того, как все каталоги будут найдены в пути поиска во время запуска программы, они сохранятся в списке, который можно просмотреть с помощью sys.path в Python. Конечно, вы также можете добавить каталог в sys.path , а затем импортировать свои модули, которые будут изменять путь поиска только во время выполнения программы.

В любом случае параметры PYTHONPATH и .pth допускают более постоянную модификацию пути поиска. Важно знать, что Python сканирует строку пути поиска слева направо, поэтому модули в самых левых перечисленных каталогах могут перезаписывать те, которые имеют то же имя в самой правой части. Обратите внимание, что пути поиска модулей нужны только для импорта модулей из разных каталогов.

Как показано в следующем примере, пустая строка в начале списка предназначена для текущего каталога:

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

Модуль перезагружается

Благодаря кешированию модуль можно импортировать только один раз для каждого процесса. Поскольку Python является интерпретируемым языком, он запускает код импортированного модуля, как только он достигает инструкции import или from Более поздний импорт в том же процессе (например, тот же интерпретатор Python) больше не запускает код импортированного модуля. Он просто получит модуль из кеша.

Вот пример. Давайте повторно используем приведенный выше код в my_module.py, импортируем его в интерпретатор Python, затем изменим файл и повторно импортируем его снова.

Чтобы отключить кеширование и разрешить повторный импорт модулей, Python предоставляет функцию reload . Давайте попробуем это в том же окне Python, что и раньше:

Функция reload изменяет модуль на месте. То есть, не затрагивая другие объекты, которые ссылаются на импортированный модуль. Вы можете заметить, что функция также возвращает сам модуль, указывая его имя и путь к файлу. Эта функция особенно полезна на этапе разработки, но также и в более крупных проектах.

Например, для программ, которым требуется постоянное подключение к серверу, гораздо дороже перезапустить все приложение, чем выполнять динамическую перезагрузку или горячую перезагрузку для использования во время разработки.

Модульные пакеты

При импорте имен модулей вы фактически загружаете файлы Python, хранящиеся где-то в вашей файловой системе. Как упоминалось ранее, импортированные модули должны находиться в каталоге, который указан в пути поиска вашего модуля ( sys.path ). В Python есть нечто большее, чем просто «импорт имен» — вы можете импортировать весь каталог, содержащий файлы Python, как пакет модуля . Этот импорт известен как импорт пакетов .

Так как же импортировать пакеты модулей? Давайте создадим каталог с именем «mydir», который включает модуль «mod0.py» и два подкаталога «subdir1» и «subdir2», содержащие модули «mod1.py» и «mod2.py» соответственно. Структура каталогов выглядит так:

Обычный подход, объясненный до сих пор, заключался в добавлении путей mydir, subdir1 и subdir2 к пути поиска модуля ( sys.path ), чтобы иметь возможность импортировать mod0.py, mod1. py ‘и’ mod2.py ‘. Это может привести к большим накладным расходам, если ваши модули распределены по множеству разных подкаталогов, что обычно и имеет место. В любом случае, импорт пакетов здесь, чтобы помочь. Они работают с импортом названия самой папки.

Эта команда, например, недопустима и приведет к ошибке InvalidSyntax:

Правильный способ сделать это — установить только каталог контейнера ‘/ Users / Code / Projects /’ в пути поиска вашего модуля (добавив его в PYTHONPATH или перечислить в файле .pth ), а затем импортировать ваши модули, используя пунктирный синтаксис. Вот некоторые допустимые импортные данные:

Вы, наверное, заметили ранее, что некоторые каталоги Python включают файл __init__.py. На самом деле это было требованием в Python2.x, чтобы сообщить Python, что ваш каталог является модульным пакетом. Файл __init__.py также является обычным файлом Python, который запускается всякий раз, когда этот каталог импортируется, и подходит для инициализации значений, например, для подключения к базе данных.

В любом случае, в большинстве случаев эти файлы остаются пустыми. В Python3.x эти файлы необязательны, и вы можете использовать их при необходимости. Следующие несколько строк показывают, как имена, определенные в __init__.py, становятся атрибутами импортируемого объекта (имя каталога, в котором он находится).

Еще одна важная тема, когда речь идет о пакетах модулей, — это относительный импорт . Относительный импорт полезен при импорте модулей внутри самого пакета. В этом случае Python будет искать импортированный модуль в пределах пакета, а не в пути поиска модуля.

Продемонстрируем один полезный случай на примере:

import mod2 сообщает Python, что нужно искать модуль mod2 в пути поиска модуля, и, следовательно, это не удается. Вместо этого будет работать относительный импорт. В следующем операторе относительного импорта используется двойная точка (".."), которая обозначает родительский элемент текущего пакета (‘mydir /’). Следующий подкаталог subdir2 должен быть включен для создания полного относительного пути к модулю mod2 .

Относительный импорт — огромная тема, которая может занять целую главу книги. Они также сильно различаются между версиями Python2.x и 3.x. На данный момент мы показали только один полезный случай, но в отдельных сообщениях блога должны быть другие, которым мы будем следовать.

Что касается Python 2.x, поддержка этой версии заканчивается в 2020 году , поэтому в случаях, когда между версиями Python есть большая разница, например, при относительном импорте, лучше сосредоточиться на версии 3.x.

Отправка пакета в PyPi

До сих пор вы научились писать модули Python, различать импортируемые модули и модули верхнего уровня, использовать определяемые пользователем модули в границах каталогов, изменять путь поиска модулей и, среди прочего, создавать / импортировать пакеты модулей. Создав полезное программное обеспечение, упакованное в пакет модулей, вы можете поделиться им с большим сообществом Python. В конце концов, Python создается и поддерживается сообществом.

Индекс пакетов Python (PyPI) — это программный репозиторий для Python, который в настоящее время содержит более 120 тыс. Пакетов (на момент написания этой статьи). Возможно, вы ранее устанавливали модули из этого репозитория с помощью команды pip .

Например, следующая строка загрузит и установит библиотеку Numpy для научных вычислений:

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

  • Во-первых, соблюдайте требования к упаковке и распространению. Здесь необходимо выполнить два шага:
    • Установите pip, setuptools и wheel. Подробнее об этом здесь .
    • Установите шпагат , который используется для загрузки вашего проекта в PyPI.
    • Следующим шагом будет настройка вашего проекта. В общем, это означает добавление в проект нескольких файлов Python, которые будут содержать информацию о конфигурации, руководства по использованию и т. Д. PyPI предоставляет пример проекта, который вы можете использовать в качестве руководства. Вот самые важные файлы, которые вам нужно добавить:
      • setup.py: этот файл необходимо добавить в корень вашего проекта, он служит интерфейсом командной строки для установки. Он должен содержать функцию setup() которая будет принимать в качестве аргументов такую информацию, как: имя проекта, версия, описание, лицензия, зависимости проекта и т. Д.
      • README.rst: текстовый файл с описанием вашего пакета.
      • licence.txt: текстовый файл, содержащий вашу лицензию на программное обеспечение. Подробнее о выборе лицензии читайте на GitHub.
      • Последний шаг — загрузка вашего дистрибутива в PyPI. В основном здесь два шага:
        • Создайте учетную запись PyPI.
        • Загрузите содержимое каталога dist /, созданного на предыдущем шаге. Здесь вы можете сначала загрузить тест с помощью тестового сайта PyPI .

        Это почти все. Для получения дополнительной информации на веб-сайте PyPI есть все подробные инструкции, если вы застряли.

        Заключение

        Этот пост был предназначен для того, чтобы направить вас от основных основ модулей Python (создание и импорт ваших первых импортируемых модулей) к более сложным темам (изменение пути поиска, пакетов модулей, перезагрузок и некоторых базовых относительных импортов) до отправки ваших Пакет Python в репозиторий программного обеспечения Python PyPI.

        По этой теме много информации, и мы не смогли охватить все в этом одном сообщении, поэтому вы не сможете выполнить все эти шаги и отправить официальный пакет в течение времени чтения этого сообщения. Однако каждый шаг должен быть кратким введением, чтобы направлять вас на вашем пути обучения.

        How to group Python imports?

        python

        A key part of the PEP-8 Style Guide for Python Code is the Imports section.

        This part on grouping imports is particularly important.

        • Standard library imports.
        • Related third party imports.
        • Local application/library specific imports.

        “Standard library imports”

        This refers to imports from Python’s built-in standard library.

        “Related third party imports”

        “Third party” confuses a lot of people. These imports could be maintained by yourself or your organisation, but are installed as a separate package.

        “Local application/library specific imports”

        I take this to mean “imports from the same repository”.

        Separate groups with a blank line

        Be sure to separate the groups with a blank line.

        You should put a blank line between each group of imports.

        PEP-8: The Style Guide for Python Code

        Import order

        Within each group, sort imports alphabetically. (This is not part of the style guide, just my recommendation.)

        Основы

        Андрей Шагин

        В Python ключевое слово import применяется для того, чтобы сделать код в одном модуле доступным для работы в другом. Импорт в Python важен для эффективного структурирования кода. Правильное применение импорта повысит вашу продуктивность: вы сможете повторно использовать код и при этом продолжать осуществлять поддержку своих проектов.

        В статье представлен подробный обзор инструкции import в Python и того, как она работает. Здесь мощная система импорта. Вам предстоит узнать, как эту мощь задействовать, а также изучить ряд понятий, лежащих в основе системы импорта в Python. Их изложение в статье построено главным образом на примерах (в помощь вам будут несколько примеров кода).

        В этой статье вы узнаете, как:

        • Работать с модулями, пакетами и пакетами пространств имён.
        • Импортировать ресурсы и файлы данных внутри ваших пакетов.
        • Динамически импортировать модули во время выполнения.
        • Настраивать систему импорта в Python.

        На протяжении всей статьи даются примеры: вы сможете поэкспериментировать с тем, как организован импорт в Python, чтобы работать наиболее эффективно. Хотя в статье показан весь код, имеется также возможность скачать его по ссылке ниже:

        Базовый импорт Python

        Код в Python организован в виде модулей и пакетов. В этой части статьи мы объясним, чем они отличаются друг от друга и как с ними можно работать.

        Чуть дальше вы узнаете о нескольких продвинутых и менее известных примерах применения системы импорта в Python. Но начнём с основ — импортирования модулей и пакетов.

        Модули

        В Python.org glossary даётся следующее определение модуля:

        Объект, который служит организационной единицей кода в Python. Модули имеют пространство имён, в котором содержатся произвольные объекты Python. Модули загружаются в Python посредством импортирования. (Источник)

        На практике модуль соответствует, как правило, одному файлу с расширением .py . В этом файле содержится код на Python.

        Модули обладают сверхспособностью импортироваться и повторно использоваться в другом коде. Рассмотрим следующий пример:

        В первой строке import math вы импортируете код в модуль math и делаете его доступным для использования. Во второй строке вы получаете доступ к переменной в модуле math . Модуль math является частью стандартной библиотеки Python, поэтому он всегда доступен для импорта, когда вы работаете с Python.

        Обратите внимание, что пишется не просто pi , а math.pi .

        math — это не только модуль, а ещё и пространство имён, в котором содержатся все атрибуты этого модуля. Пространства имён важны для читаемости и структурированности кода.

        Содержимое пространства имён можно посмотреть с помощью dir() :

        Если не указывать при этом никаких аргументов, т.е. напечатать просто dir() , то можно увидеть, что находится в глобальном пространстве имён. Посмотреть содержимое пространства имён math можно, указав его в качестве аргумента вот так: dir(math) .

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

        Вот код, который импортирует из модуля math только переменную pi :

        Обратите внимание, что pi помещается в глобальное пространство имён, а не в пространство имён math .

        А вот как в процессе импортирования переименовываются модули и атрибуты:

        Пакеты

        Пакет представляет собой следующий после модуля уровень в организационной иерархии кода. В Python.org glossary даётся следующее определение пакета:

        Это модуль Python, который может содержать подмодули или (рекурсивно) подпакеты. Строго говоря, пакет — это модуль Python с атрибутом __path__ . (Источник.)

        То есть пакет — это тоже модуль. Пользователю обычно не приходится задумываться о том, что у него импортируется: модуль или пакет.

        На практике пакет — это, как правило, каталог файлов, внутри которого находятся файлы Python и другие каталоги. Чтобы создать пакет Python самостоятельно, создайте каталог, а внутри него — файл с именем __init__.py . В __init__.py файле находится содержимое этого пакета-модуля. И он может быть пустым.

        Обратите внимание: каталоги без файла __init__.py Python всё равно считает пакетами. Но это уже будут не обычные пакеты, а то, что можно назвать пакетами пространства имён. Подробнее о них чуть дальше в статье.

        Вообще подмодули и подпакеты нельзя импортировать вместе с пакетом. Это можно сделать с помощью __init__.py , включив любой или все подмодули и подпакеты, если захотите. В качестве примера создадим пакет для Hello world на разных языках. Пакет будет состоять из следующих каталогов и файлов:

        Для файла каждой страны выводится соответствующее приветствие, а файлы __init__.py выборочно импортируют некоторые подпакеты и подмодули. Вот точное содержимое этих файлов:

        Обратите внимание: world/__init__.py импортирует только africa , а не europe ; world/africa/__init__.py ничего не импортирует; world/europe/__init__.py импортирует greece и norway , а не spain . Модуль каждой страны при импортировании выводит приветствие.

        Разберёмся, как ведут себя подпакеты и подмодули в пакете world :

        При импортировании europe модули europe.greece и europe.norway тоже импортируются. Это происходит потому, что модули этих стран выводят приветствие при импортировании:

        Файл world/africa/__init__.py пуст. Это означает, что импортирование пакета world.africa создаёт пространство имён, но этим и ограничивается:

        Не забывайте: при импорте модуля загружается его содержимое и одновременно создаётся пространство имён с этим содержимым. Последние несколько примеров показывают, что один и тот же модуль может быть частью разных пространств имён.

        Технические нюансы: пространство имён модуля реализовано в виде словаря Python и доступно в атрибуте .__dict__ :

        Но вам не придётся часто взаимодействовать с .__dict__ напрямую.

        Глобальное пространство имён в Python тоже является словарём. Доступ к нему можно получить через globals() .

        Импортировать подпакеты и подмодули в файле __init__.py — это обычное дело. Так они становятся более доступными для пользователей. Вот вам пример того, как это происходит в популярном пакете запросов.

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

        Напомним исходный код world/__init__.py предыдущего примера:

        Чуть ранее мы уже разбирали операторы типа from. import , такие как from math import pi . Что же означает точка ( . ) в from . import africa ?

        Точка указывает на текущий пакет, а сам оператор — это пример относительного импорта. Можно прочитать этот

        так: «из текущего пакета импортируется подпакет africa ».

        Существует эквивалентный ему оператор абсолютного импорта, в котором прямо указывается название этого текущего пакета:

        На самом деле, все импорты в world можно было бы сделать в виде таких вот абсолютных импортов с указанием названия текущего пакета.

        Относительные импорты должны иметь такую from. import форму, причём обозначение места, откуда вы импортируете, должно начинаться с точки.

        В руководстве по стилю PEP 8 рекомендуется в основном абсолютный импорт. Однако относительный импорт в качестве альтернативы абсолютному тоже имеет право на существование при организации иерархии пакетов.

        Путь импорта в Python

        А как Python находит модули и пакеты, которые импортирует? Более подробно о специфике системы импорта в Python расскажем чуть дальше в статье. А пока нам достаточно просто знать, что Python ищет модули и пакеты в своём пути импорта. Это такой список адресов, по которым выполняется поиск модулей для импорта.

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

        Так, он заглянет в кэш модулей и проверит, не было ли это что-то уже импортировано, а также проведёт поиск среди встроенных модулей.

        Подробнее о том, как организован импорт в Python, расскажем чуть дальше в статье.

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

        1. Каталог текущего скрипта или текущий каталог, если скрипта нет (например, когда Python работает в интерактивном режиме).
        2. Содержимое переменной окружения PYTHONPATH .
        3. Другие каталоги, зависящие от конкретной системы.

        Поиск Python, как правило, стартует в начале списка адресов и проходит по всем адресам до первого совпадения с искомым модулем. Каталог скрипта или текущий каталог всегда идёт первым в этом списке. Поэтому можно организовать каталоги так, чтобы скрипты находили ваши самодельные модули и пакеты. При этом надо внимательно следить за тем, из какого каталога вы запускаете Python.

        Стоит следить и за тем, чтобы не создавались модули, которые затеняют или скрывают другие важные модули. В качестве примера предположим, что вы определяете следующий модуль math :

        Всё пока идёт как надо:

        Вот только модуль этот затеняет модуль math , который входит в состав стандартной библиотеки. Это приводит к тому, что наш предыдущий пример поиска значения π больше не работает:

        Вместо того, чтобы искать модуль math в стандартной библиотеке, Python теперь ищет ваш новый модуль math для pi .

        Во избежание подобных проблем надо быть осторожным с названиями модулей и пакетов. Имена модулей и пакетов верхнего уровня должны быть уникальными. Если math определяется как подмодуль внутри пакета, то он не будет затенять встроенный модуль.

        Структурируем импорт

        Несмотря на то, что мы можем организовать импорт, используя текущий каталог, переменную окружения PYTHONPATH и даже sys.path , этот процесс часто оказывается неконтролируемым и подверженным ошибкам. Типичный пример даёт нам следующее приложение:

        Приложение воссоздаст данную файловую структуру с каталогами и пустыми файлами. Файл structure.py содержит основной скрипт, а files.py — это библиотечный модуль с функциями для работы с файлами. Вот что выводит приложение, запускаемое в данном случае в каталоге structure :

        Два файла исходного кода плюс автоматически созданный файл .pyc повторно создаются внутри нового каталога с именем 001 .

        Обратимся теперь к исходному коду. Основная функциональность приложения определяется в structure.py :

        В строках с 12 по 16 читается корневой путь из командной строки. Точкой здесь обозначается текущий каталог. Этот путь — root файловой иерархии, которую вы воссоздадите.

        Вся работа происходит в строках с 19 по 23. Сначала создаётся уникальный путь new_root , который будет корневым каталогом новой файловой иерархии. Затем в цикле проходятся все пути ниже исходного root , и они воссоздаются в виде пустых файлов внутри новой файловой иерархии.

        В строке 26 вызывается main() . О проверке условия if в строке 25 подробнее узнаем дальше в статье. А пока нам достаточно знать, что специальная переменная __name__ внутри скриптов имеет значение __main__ , а внутри импортируемых модулей получает имя модуля.

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

        unique_path() работает со счётчиком для обнаружения пути, которого уже не существует. В приложении он нужен, чтобы найти уникальный подкаталог, который будет использоваться в качестве new_root вновь созданной файловой иерархии. add_empty_file() обеспечивает создание всех необходимых каталогов до того, как с помощью .touch() будет создан пустой файл.

        Ещё раз взглянем на импорт файлов :

        Выглядит он совершенно невинно. Однако по мере роста проекта эта строка станет источником некоторых проблем. Даже если импорт файлов происходит из проекта structure , этот импорт абсолютный: он не начинается с точки. А это означает, что файлы должны быть найдены в пути импорта, чтобы импорт состоялся.

        К счастью, каталог с текущим скриптом всегда находится в пути импорта Python. Так что, пока проект не набрал обороты, импорт работает нормально. Но дальше возможны варианты.

        Например, кто-то захочет импортировать скрипт в Jupyter Notebook и запускать его оттуда. Или иметь доступ к библиотеке файлов в другом проекте. Могут даже с помощью PyInstaller создавать исполняемые файлы, чтобы упростить их дальнейшее распространение. К сожалению, любой из этих сценариев может вызвать проблемы с импортом файлов.

        Каким образом? Вот вам пример. Возьмём руководство по PyInstaller и создадим точку входа в приложение. Добавим дополнительный каталог за пределами каталога приложения:

        В этом внешнем каталоге создадим скрипт точки входа cli.py :

        Этот скрипт импортирует из исходного скрипта main() и запускает его. Обратите внимание: когда импортируется structure , main() не запускается из-за проверки условия if в строке 25 внутри structure.py . То есть нужно запускать main() явным образом.

        По идее, это должно быть аналогично прямому запуску приложения:

        Почему же запуск не удался? При импорте файлов неожиданно возникает ошибка.

        Проблема в том, что при запуске приложения с cli.py поменялся адрес текущего скрипта, а это, в свою очередь, меняет путь импорта. Файлы больше не находятся в пути импорта, поэтому их абсолютный импорт невозможен.

        Одно из возможных решений — поменять путь импорта Python. Вот так:

        Здесь в пути импорта есть папка со structure.py и files.py . Поэтому это решение работает. Но такой подход неидеален, ведь путь импорта может стать очень неаккуратным и трудным для понимания.

        Фактически происходит воссоздание функции ранних версий Python, называемой неявным относительным импортом. Она была удалена из языка в руководстве по стилю PEP 328 со следующим обоснованием:

        В Python 2.4 и более ранних версиях при чтении модуля, расположенного внутри пакета, неясно: относится ли import foo к модулю верхнего уровня или к другому модулю внутри пакета. По мере расширения библиотеки Python всё больше и больше имеющихся внутренних модулей пакета вдруг случайно затеняют модули стандартной библиотеки. Внутри пакетов эта проблема усугубляется из-за невозможности указать, какой модуль имеется в виду. (Источник.)

        Другое решение — использовать вместо этого относительный импорт. Меняем импорт в structure.py :

        Теперь приложение можно запустить через скрипт точки входа:

        Но вызвать напрямую приложение больше не получится:

        Проблема в том, что относительный импорт разрешается в скриптах иначе, чем импортируемые модули. Конечно, можно вернуться и восстановить абсолютный импорт, а затем выполнить непосредственный запуск скрипта или даже попытаться провернуть акробатический трюк с try. except и реализовать абсолютный или относительный импорт файлов (в зависимости от того, что сработает).

        Есть даже официально санкционированный хакерский приём, позволяющий работать с относительным импортом в скриптах. Вот только в большинстве случаев при этом придётся менять sys.path . Цитируя Реймонда Хеттинджера, можно сказать:

        И действительно, лучшее (и более стабильное) решение — поэкспериментировать с системой управления пакетами и импорта Python, устанавливая проект в качестве локального пакета с помощью pip .

        Создание и установка локального пакета

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

        Создание локального пакета не приводит к большому расходу вычислительных ресурсов. Сначала создаём минимальный набор файлов setup.cfg и setup.py во внешнем каталоге structure :

        Теоретически name и version могут быть любыми. Надо лишь учесть, что они задействованы pip при обращении к пакету, поэтому стоит выбрать для него значения, легко узнаваемые и выделяющие его из массы других пакетов.

        Рекомендуется давать всем таким локальным пакетам общий префикс, например local_ или ваше имя пользователя. В пакетах должен находиться каталог или каталоги, содержащие исходный код. Теперь можно установить пакет локально с помощью pip :

        Эта команда установит пакет в вашу систему. structure после этого будет находиться в пути импорта Python. То есть можно будет выполнить её в любом месте, не беспокоясь о каталоге скрипта, относительном импорте или других сложностях. -e означает editable (редактируемый). Это важная опция, позволяющая менять исходный код пакета без его переустановки.

        Примечание: такой установочный файл отлично подходит для самостоятельной работы с проектами. Если же вы планируете поделиться кодом ещё с кем-то, то стоит добавить в установочный файл кое-какую дополнительную информацию.

        Теперь, когда structure в системе установлена, можно использовать следующую инструкцию импорта:

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

        Совет: старайтесь разделять в коде скрипты и библиотеки. Вот хорошее практическое правило:

        • Скрипт предназначен для запуска.
        • Библиотека предназначена для импорта.

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

        Разделять скрипты и библиотеки — неплохая идея, тем не менее в Python все файлы можно запускать и импортировать. Ближе к завершению статьи подробнее расскажем о том, как создавать модули, которые хорошо справляются и с тем, и с другим.

        Пакеты пространства имён

        Модули и пакеты в Python очень тесно связаны с файлами и каталогами. Это отличает Python от многих других языков программирования, в которых пакеты — это не более чем пространства имён без обязательной привязки к тому, как организован исходный код. Для примера можете ознакомиться с обсуждением на PEP 402.

        Пакеты пространства имён доступны в Python с версии 3.3. Они в меньшей степени зависят от имеющейся здесь файловой иерархии. Так, пакеты пространств имён могут быть разделены на несколько каталогов. Пакет пространства имён создаётся автоматически, если у вас есть каталог, содержащий файл .py , но нет __init__.py . Подробное объяснение смотрите в PEP 420.

        Замечание: справедливости ради стоит отметить, что пакеты неявных пространств имён появились в Python 3.3. В более ранних версиях Python пакеты пространств имён можно было создавать вручную несколькими различными несовместимыми способами. Все эти ранние подходы обобщены и в упрощённом виде представлены в PEP 420.

        Для лучшего понимания пакетов пространства имён попробуем реализовать один из них. В качестве поясняющего примера рассмотрим такую задачу. Дано: объект Song . Требуется преобразовать его в одно из строковых представлений. То есть нужно сериализовать объекты Song .

        А конкретнее — нужно реализовать код, который работает примерно так:

        Предположим, нам повезло наткнуться на стороннюю реализацию нескольких форматов, в которые нужно сериализовать объекты, и она организована как пакет пространства имён:

        В файле json.py содержится код, который может сериализовать объект в формат JSON:

        Этого несколько ограниченного интерфейса сериализатора будет достаточно, чтобы продемонстрировать, как работают пакеты пространства имён.

        В файле xml.py содержится аналогичный XmlSerializer , который может преобразовать объект в XML:

        Обратите внимание, что оба этих класса реализуют один и тот же интерфейс с помощью методов .start_object() , .add_property() и .__str__() .

        Затем создаём класс Song , который может применять эти сериализаторы:

        Song (песня) определяется по идентификатору, названию и исполнителю. Обратите внимание, что .serialize() не нужно знать, в какой формат происходит преобразование, потому что он использует общий интерфейс, определённый ранее.

        Установив пакет сторонних serializers , можно работать с ним так:

        Для разных объектов сериализатора, вызывая .serialize() получаем разные представления песни.

        Примечание: при запуске кода можно получить ModuleNotFoundError или ImportError . Всё потому, что serializers нет в пути импорта Python. Но скоро мы увидим, как решить эту проблему.

        Пока все идёт хорошо. Но теперь песни нужно преобразовать и в представление YAML, которое не поддерживается сторонней библиотекой. Тут-то в дело и вступают пакеты пространства имён: можем добавить в пакет serializers собственный YamlSerializer , не прибегая к сторонней библиотеке.

        Сначала создаём каталог в локальной файловой системе под названием serializers . Важно, чтобы имя каталога совпадало с именем настраиваемого пакета пространства имён:

        В файле yaml.py определяем собственный YamlSerializer . Делаем это с помощью пакета PyYAML , который должен быть установлен из PyPI:

        Форматы YAML и JSON очень похожи, поэтому здесь можно повторно использовать большую часть реализации JsonSerializer :

        Смотрите: YamlSerializer здесь основан на JsonSerializer , который импортируется из этих самых serializers . А раз json и yaml являются частью одного и того же пакета пространства имён, то мы можем даже использовать относительный импорт: from .json import JsonSerializer .

        Поэтому, продолжая этот пример, мы теперь можем преобразовать песню в YAML:

        Подобно обычным модулям и пакетам, пакеты пространства имён должны находиться в пути импорта Python. Если бы мы делали, как в предыдущих примерах, то могли бы столкнуться с проблемами: Python не находил бы serializers . В реальном коде мы бы использовали pip для установки сторонней библиотеки, так что они автоматически оказывались бы в нашем пути.

        Примечание: в исходном примере выбор сериализатора делался более динамично. Позже мы увидим, как использовать пакеты пространств имён в соответствующем шаблоне «фабричный метод».

        И нужно позаботиться о том, чтобы локальная библиотека была доступна так же, как и обычный пакет. Как мы уже убедились, это можно сделать либо запустив Python из соответствующего каталога, либо опять-таки используя pip для установки локальной библиотеки.

        В этом примере мы тестируем, как можно интегрировать фейковый сторонний пакет с нашим локальным пакетом. Будь сторонний third_party реальным пакетом, то мы бы загрузили его из PyPI с помощью pip . А так мы можем сымитировать его, установив third_party локально, как уже было сделано ранее в примере со structure .

        Или же можно поколдовать с путём импорта. Поместите каталоги third_party и local в одну папку, а затем настройте путь Python вот так:

        Теперь можно использовать все сериализаторы, не беспокоясь о том, где они определены: в стороннем пакете или локально.

        Руководство по стилю импорта

        В руководстве по стилю Python PEP 8 есть ряд рекомендаций, касающихся импорта. Как всегда, в Python важное значение придаётся читаемости и лёгкости сопровождения кода. Вот несколько общих практических правил относительно того, какого стиля надо придерживаться при оформлении импорта:

        • Находится в верхней части файла.
        • Прописывается в отдельных строках.
        • Организуется в группы: сначала идут импорты стандартной библиотеки, затем сторонние импорты, а после — импорты локальных приложений или библиотек.
        • Внутри каждой группы импорты располагаются в алфавитном порядке.
        • Предпочтение отдаётся абсолютному импорту над относительным.
        • Импорты со спецсимволами типа звёздочки ( from module import * ) стараются не использовать.

        Инструменты isort и reorder-python-imports отлично подходят для реализации этих рекомендаций в последовательном стиле импорта. Вот пример раздела импорта внутри пакета Real Python feed reader package:

        Обратите внимание на чёткую организацию по группам. Сразу позволяет обозначить зависимости этого модуля, которые должны быть установлены: feedparser и html2text . Обычно подразумевается, что стандартная библиотека доступна. Разделение импортов внутри пакета даёт некоторое представление о внутренних зависимостях кода.

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

        Импорт в Python. Ресурсы и динамический импорт

        Иногда наш код зависит от файлов данных или других ресурсов. В небольших скриптах это не проблема — мы можем указать путь к файлу данных и продолжить работу!

        Однако, если файл ресурсов важен для нашего пакета и хочется поделиться им с другими пользователями, возникает несколько проблем:

        1. У нас не будет контроля над путём к ресурсу, так как это будет зависеть от настроек пользователя, а также от того, как пакет распространяется и устанавливается. Можно попробовать узнать путь к ресурсу с помощью атрибутов пакета __file__ или __path__ , но такой способ не всегда может сработать так, как мы ожидаем.
        2. Пакет может находиться внутри ZIP-файла или старого файла .eggfile, и в этом случае ресурс даже не будет физическим файлом в компьютере пользователя.

        Было предпринято несколько попыток решить эти проблемы, в том числе с помощью setuptools.pkg_resources . Однако с появлением в стандартной библиотеке Python 3.7 importlib.resources теперь есть один стандартный способ работы с ресурсными файлами.

        Представляем importlib.resources

        importlib.resources предоставляет доступ к ресурсам внутри пакетов. В этом контексте ресурс — это любой файл, находящийся в импортируемом пакете. Файл может соответствовать, а может и не соответствовать физическому файлу в файловой системе.

        Здесь есть несколько преимуществ: при повторном использовании системы импорта получаем более последовательный способ работы с файлами внутри пакетов плюс более лёгкий доступ к ресурсным файлам в других пакетах. Вот что об этом сказано в документации:

        Если вы можете импортировать пакет, то можете иметь доступ к ресурсам внутри этого пакета. (Источник.)

        importlib.resources стали частью стандартной библиотеки в Python 3.7. А для более старых версий Python имеется бэкпорт importlib_resources . Чтобы задействовать бэкпорт, надо установить его из PyPI:

        Бэкпорт совместим с Python 2.7, а также Python 3.4 и более поздними версиями.

        При работе с importlib.resources есть одно условие: ресурсные файлы должны быть доступны внутри обычного пакета. Пакеты пространства имён не поддерживаются. На практике это означает, что файл должен находиться в каталоге, содержащем файл __init__.py .

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

        __init__.py — это просто пустой файл, необходимый для указания на то, что books (книги) — это обычный пакет.

        Затем можем использовать open_text() и open_binary() для открытия текстовых и бинарных файлов соответственно:

        open_text() и open_binary() эквивалентны встроенному open() с параметром mode , имеющим значения rt и rb соответственно. Также доступны в виде read_text() и read_binary() удобные функции для чтения текстовых или двоичных файлов. Ещё больше узнать можно в официальной документации.

        Примечание: чтобы полностью перейти на бэкпорт для старых версий Python, импортируем importlib.resources :

        Больше узнать об этом можно в разделе «Полезные советы» этой статьи. Далее в этой части статьи покажем несколько сложных примеров работы с ресурсными файлами на практике.

        Файлы данных

        В качестве более полного примера работы с файлами данных рассмотрим, как можно реализовать программу викторины, основанную на демографических данных Организации Объединенных Наций. Сначала создаём пакет data и загружаем WPP2019_TotalPopulationBySex.csv с веб-сайта ООН:

        Откроем файл CSV и посмотрим на данные:

        В каждой строке мы видим данные о населении страны за определённый год и вариант, указывающий на соответствующий прогнозный сценарий. В файле содержатся прогнозы численности населения по странам мира до 2100 года.

        Следующая функция считывает этот файл и выдаёт общую численность населения той или иной страны за конкретный year (год) и variant (вариант):

        Выделенные жирным шрифтом строки показывают применение importlib.resources для открытия файла данных. Функция возвращает словарь с численностью населения:

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

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

        Исходный код демографической викторины:

        Демографическая викторина состоит из двух функций: одна считывает данные по численности населения (как мы это делали чуть выше), а другая запускает саму викторину:

        Обратите внимание: здесь в строке 24 мы проверяем, что LocID меньше 900 . LocID , равный 900 и выше, указывает не на страновые данные, а на данные по миру, частям света, такие как World , Asia и т.д.

        Пример: значки и Tkinter

        При создании графических пользовательских интерфейсов (ГПИ) часто требуется включать ресурсные файлы, такие как значки. На следующем примере научимся делать это с помощью importlib.resources . В итоге приложение будет выглядеть довольно просто, но со вкусом благодаря оригинальному значку и оформлению кнопки Goodbye:

        В примере используется пакет ГПИ Tkinter, доступный в стандартной библиотеке. Он основан на оконной системе Tk, изначально разработанной для языка программирования Tcl. Существует множество других пакетов ГПИ, доступных для Python. Если вы используете один из них, то должны уметь добавлять значки в своё приложение с помощью идей, подобных тем, что представлены здесь.

        В Tkinter изображения обрабатываются классом PhotoImage . Чтобы создать PhotoImage , передаём путь к файлу изображения.

        При распространении пакета вовсе не гарантируется, что ресурсные файлы будут существовать в файловой системе как физические файлы. importlib.resources решает эту проблему с помощью path() . Эта функция вернёт путь к ресурсному файлу, создав при необходимости временный файл.

        Чтобы убедиться, что все временные файлы очищены правильно, задействуем path() в качестве менеджера контекста, используя ключевое слово with :

        Для полного примера предположим, что у нас есть следующая файловая иерархия:

        Хотите попробовать пример самостоятельно? Скачайте эти файлы вместе с остальным исходным кодом, приведённым в этой статье, перейдя по ссылке ниже:

        Получить исходный код: Нажмите здесь и получите исходный код, используемый для изучения системы импорта Python в этой статье.

        Код хранится в файле со специальным именем __main__.py . Это имя указывает на то, файл является точкой входа для пакета. Благодаря файлу __main__.py наш пакет может выполняться с python -m :

        ГПИ определяется в классе Hello . Обратите внимание, что для получения пути к файлам изображений используется importlib.resources :

        Официальная документация содержит хороший список ресурсов, с которого можно начать изучение. Ещё один отличный ресурс — это «Руководство по TkDocs», которое показывает, как использовать Tk в других языках.

        Примечание: единственное, что может вызывать неудобство при работе с изображениями в Tkinter, так это то, что здесь нужно следить за тем, чтобы изображения не удалялись механизмом автоматического управления памятью. Из-за того, как Python и Tk взаимодействуют, сборщик мусора в Python (по крайней мере, в CPython) не регистрирует, что .iconphoto() и Button используют изображения.

        Чтобы убедиться, что изображения сохраняются, нужно вручную добавлять ссылку на них. В нашем коде это было сделано в строках 18 и 31.

        Динамический импорт

        Одна из отличительных особенностей Python в том, что это очень динамичный язык. Можно много чего сделать с программой Python во время её выполнения (хотя иногда делать этого не стоит), например добавлять атрибуты к классу, переопределять методы или изменять строку документации модуля. Мы можем изменить print() так, чтобы он ничего не делал:

        На самом деле, мы не переопределяем print() . Мы определяем другой print() , который затеняет встроенный print() . Для возвращения к исходному print() удаляем наш пользовательский print() с помощью del print . Так можно затенить любой объект Python, встроенный в интерпретатор.

        Обратите внимание: в приведенном выше примере мы переопределяем print() с помощью лямбда-функции. Также можно было бы использовать определение обычной функции:

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

        importlib

        До сих пор мы использовали ключевое слово import для явного импорта модулей и пакетов в Python. Однако весь механизм импорта доступен в пакете importlib , что позволяет нам выполнять импорт более динамично. Следующий скрипт запрашивает у пользователя имя модуля, импортирует этот модуль и выводит строку его документации:

        import_module() возвращает объект модуля, который можно привязать к любой переменной. После чего мы можем обращаться с этой переменной как с обычным импортируемым модулем. Этот скрипт можно использовать вот так:

        В каждом случае модуль импортируется динамически с помощью import_module() .

        Фабричный метод с пакетами пространства имён

        Вернёмся к примеру с сериализаторами. Благодаря serializers , реализованным в качестве пакета пространства имён, у нас появилась возможность добавлять пользовательские сериализаторы. Сериализаторы создаются с помощью фабрики сериализаторов. Попробуем сделать это, использовав importlib .

        Добавим в наш локальный пакет пространства имён serializers следующий код:

        Фабрика get_serializer() может создать сериализаторы динамически на основе параметра format , а затем serialize() может применить сериализатор к любому объекту, реализующему метод .serialize() .

        Фабрика делает строгие предположения об именовании модуля и класса, которые содержат конкретные сериализаторы. Далее в статье мы узнаем об архитектуре плагинов, которая придаёт больше гибкости.

        А пока воссоздадим предыдущий пример вот таким образом:

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

        Обратите внимание: в обычном пакете мы бы, наверное, реализовали get_serializer() и serialize() в файле __init__.py . Так мы могли бы просто импортировать serializers , а затем вызвать serializers.serialize() .

        Но пакеты пространства имён не могут использовать __init__.py , поэтому нужно реализовать эти функции в отдельном модуле.

        Последний пример показывает, что мы получаем соответствующее сообщение об ошибке, если пытаемся сериализоваться в формат, который не был реализован.

        Пакет плагинов

        Рассмотрим ещё один пример использования динамического импорта. Мы можем использовать следующий модуль для настройки гибкой архитектуры плагинов в коде. Это похоже на то, что было в предыдущем примере, в котором мы могли подключить сериализаторы для различных форматов, добавив новые модули.

        Эскпериментальное средство визуализации Glue — это одно из приложений, эффективно использующих плагины. Оно сходу может читать множество различных форматов данных. Если всё-таки нужный формат данных не поддерживается, можно написать собственный пользовательский загрузчик данных.

        Просто добавляется функция, которая декорируется и помещается в специальное место, чтобы Glue было легче её найти. И никакую часть исходного кода Glue менять не надо. Все детали смотрите в документации.

        Мы можем настроить аналогичную архитектуру плагина для использования в своих проектах. В этой архитектуре два уровня:

        1. Пакет плагинов — это набор связанных плагинов, соответствующих пакету Python.
        2. Плагин — это пользовательское поведение, доступное в модуле Python.

        Модуль plugins , который предоставляет архитектуру плагина, имеет следующие функции:

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

        Рассматривать код во всех деталях не будем: это выходит за рамки статьи. Если вам интересно, можем показать реализацию ниже.

        В следующем коде показана реализация plugins.py , описанная выше:

        Эта реализация немного упрощена. Так, она не выполняет явной обработки ошибок. Более полная реализация доступна по ссылке на проект PyPlugs.

        _import() использует importlib.import_module() для динамической загрузки плагинов. А _import_all() использует importlib.resources.contents() для перечисления всех доступных плагинов в данном пакете.

        Рассмотрим несколько примеров использования плагинов. Первый пример — это пакет greeter , который можно использовать для добавления различных приветствий в приложение. Полная архитектура плагинов здесь определённо избыточна, но она показывает, как работают плагины. Представьте, что у вас такой пакет greeter :

        Каждый модуль greeter определяет функцию, которая принимает один аргумент name . Посмотрите, как с помощью декоратора @register все они регистрируются в качестве плагинов:

        Обратите внимание: для упрощения обнаружения и импорта плагинов имя каждого плагина содержит не имя функции, а имя модуля, в котором он находится. Поэтому на каждый файл может быть только один плагин.

        В завершение настройки greeter как пакета плагинов можно использовать фабричные функции в plugins для добавления функциональности в сам пакет greeter :

        Теперь мы можем использовать greetings() и greet() вот так:

        Заметьте, что greetings() автоматически обнаруживает все плагины, доступные в пакете.

        Мы также можем более динамически выбирать, какой плагин вызывать. В следующем примере выбираем плагин случайным образом. Плагин также можно выбрать на основе конфигурационного файла или пользовательских данных:

        Для обнаружения и вызова различных плагинов их нужно импортировать. Остановимся ненадолго на том, как plugins работают с импортом. Всё самое главное происходит в следующих двух функциях внутри plugins.py :

        _import() внешне кажется простым. Для импорта модуля он использует importlib . Но здесь происходит ещё кое-что:

        1. Система импорта Python гарантирует, что каждый плагин импортируется только один раз.
        2. Декораторы @register , определённые внутри каждого модуля plugin, регистрируют каждый импортированный плагин.
        3. В полной реализации для работы с отсутствующими плагинами будет обработка ошибок.

        _import_all() обнаруживает все плагины в пакете. Вот как это работает:

        1. contents() из importlib.resources выводит список всех файлов внутри пакета.
        2. Результаты фильтруются для поиска потенциальных плагинов.
        3. Каждый файл Python, не начинающийся с подчеркивания, импортируется.
        4. Плагины в любом из файлов обнаруживаются и регистрируются.

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

        Первым делом добавляем строку, регистрирующую каждый из сериализаторов. Вот пример того, как это делается в сериализаторе yaml :

        Затем обновляем get_serializers() для использования plugins :

        Мы реализуем get_serializer() с помощью call_factory() , так как это автоматически инстанцирует каждый сериализатор. При таком рефакторинге сериализаторы работают точно так же, как и раньше. Но теперь у нас больше гибкости в именовании классов сериализаторов.

        Ещё больше об использовании плагинов можно узнать в PyPlugs на PyPI и презентации Плагины: добавление гибкости приложениям из PyCon 2019.

        Урок 5.
        Модули и пакеты в Python. Импорт. Виртуальная среда venv.

        Рассматриваем модули и пакеты из стандартной библиотеки Python и PyPI. Учимся использовать инструкции import и from..import и различать абсолютный и относительный импорт. Разбираемся с виртуальными пространствами venv. Создаем собственные модули.

        Logo Python Course Lesson 5

        One

        Программы на Python содержат тысячи, десятки тысяч и сотни тысяч строк кода (есть проекты с миллионами). В таких условиях одним файлом с кодом не обойдёшься — его нужно разбивать на части. И с целью получения доступа к коду, который находится в других файлах, в Python реализован механизм модулей.

        Что такое модуль в терминологии Python? Официальная документация дает следующее определение:

        Python Модуль

        1. Имеет расширение *.py (имя файла без расширения является именем модуля).
        2. Может быть импортирован.
        3. Может быть многократно использован.
        4. Позволяет вам логически организовать ваш код на Python.

        Two

        Идем дальше. Если начать делить код достаточно большого проекта на модули, то довольно быстро может возникнуть желание сгруппировать несколько близких по тематике модулей. Или же мы захотим вынести часть модулей из проекта, чтобы их можно было использовать в других проектах. И тут нам на помощь приходят пакеты(packages) в Python, которые служат для объединения модулей в группы.

        Вот что на эту тему говорит документация Python:

        Python Пакет

        1. Именем пакета является название данного каталога.
        2. С версии Python 3.3 любая папка (даже без __init__.py ) считается пакетом.
        3. Пакет может быть импортирован(так же как и модуль).
        4. Пакет может быть многократно использован(так же как и модуль).

        Three

        1. Стандартная библиотека Python (англ. Standard Library).
        2. Сторонние модули (англ. 3rd Party Modules)
        3. Пользовательские модули

        Python. Классификация модулей.

        Поговорим о каждой из групп немного подробнее — расскажем, где найти модули в каждом случае, и приведем примеры самых популярных и полезных из них.

        1. Стандартная библиотека Python

        1. Встроенные модули. Входят в состав интерпретатора и написаны на языке С, что позволяет им обеспечивать эффективный доступ к функциональности на уровне ОС — например, к системе ввода-вывода данных. Многие из встроенных модулей являются платформозависимыми. Например, модуль winreg , предназначенный для работы с реестром ОС Windows, устанавливается только на соответствующей ОС.
        2. Модули расширения. Написаны на Python. Представляют собой стандартные способы решения наиболее часто встречающихся задач программирования. Сюда входят модули для работы со многими сетевыми протоколами и форматами интернета, регулярными выражениями, текстовыми кодировками, мультимедийными форматами, криптографическими протоколами, архивами, а также для сериализации данных, поддержки юнит-тестирования и др.

        2. Сторонние модули (англ. 3rd Party Modules)

        Это модули и пакеты, которые не входят в дистрибутив Python и могут быть установлены из каталога пакетов Python с помощью пакетного менеджера (например, pip или easy_install ).

        PyPI (Python Package Index) — главный каталог библиотек на Python. Содержит более 200 000 пакетов.

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

        Four

        Python. Инструкция Import для модулей

        Чтобы воспользоваться функционалом модуля или пакета, необходимо его импортировать внутрь вашей программы. При импорте интерпретатор сначала ищет встроенный модуль с заданным именем. Если такого модуля нет, то идёт поиск файла <имя_модуля>.py в списке директорий, определённых в переменной sys.path .

        1. рабочую директорию скрипта (основного модуля);
        2. переменную окружения PYTHONPATH и пути инсталляции Python

        Посмотреть данный список можно следующим образом:

        После ключевого слова import указывается название модуля. Одной инструкцией можно подключить несколько модулей, хотя этого не рекомендуется делать, так как это снижает читаемость кода. Правильным тоном считается располагать новый импорт на новой строке.

        После импортирования модуля его название становится переменной, через которую можно получить доступ к атрибутам модуля. Если название модуля слишком длинное, или оно вам не нравится по каким-то другим причинам, то для него можно создать псевдоним с помощью ключевого слова as . При использовании псевдонима доступ ко всем атрибутам модуля осуществляется только с помощью переменной-псевдонима, а переменной с оригинальным именем модуля в этой программе уже не будет (если, конечно, вы после этого не напишете import <модуль> , тогда модуль будет доступен как под именем-псевдонимом, так и под оригинальным именем).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *