Главная / Курсы / Python / Глава 20. Пакеты, модули и библиотечные типы данных
# Глава 20. Пакеты, модули и библиотечные типы данных [Модуль](https://docs.python.org/3/tutorial/modules.html) — это объект, содержащий пространство имен. Он считается единицей структурирования кода. А по сути модуль — это файл, из которого можно импортировать определения. Если поместить модули в директорию и добавить в нее служебный файл `__init__.py`, то эта директория станет [пакетом.](https://docs.python.org/3/tutorial/modules.html#packages) Стандартная библиотека питона изобилует модулями на все случаи жизни. «Из коробки» в питоне доступно почти все. Это важное достоинство, способствовавшее широкому распространению языка. Обо всем этом богатстве сегодня и поговорим. ## Для чего нужны модули и пакеты Разбиение кода на модули решает две основные задачи: - Переиспользование. В модуль выносятся классы, функции, константы. Затем модуль подключается по месту использования. - Инкапсуляция. Фактически модуль — это пространство имен, в которое группируются схожие по смыслу определения. Пакеты в свою очередь помогают выстроить иерархию из модулей, то есть создать вложенность пространств имен. ## Импорт модулей Модули импортируются с помощью ключевого слова `import`. По PEP8 их [следует перечислять](https://peps.python.org/pep-0008/#imports) в самом начале файла и группировать, отделяя каждую группу пустой строкой: - В начале идут модули стандартной библиотеки. - Затем модули third party. - И последними перечисляются модули текущего проекта. Для импорта предусмотрено несколько вариантов синтаксиса. Рассмотрим их на примере работы с модулями из стандартной библиотеки. Модуль `math` содержит математические функции и константы. Импортируем его, чтобы посчитать объем шара. Задействуем константу `pi` и функцию возведения в степень `pow()`: ```python import math def sphere_volume(r): return math.pi * math.pow(r, 3) * 4 / 3 ``` После импорта модуля его имя становится переменной, через которую доступны атрибуты модуля: содержащиеся в нем функции, классы и прочие объекты. Импортируйте `sys` — модуль для взаимодействия с интерпретатором. {.task_text} Выведите в консоль на разных строках: {.task_text} - Значение переменной `path`. Она хранит список путей, по которым интерпретатор ищет модули. - Значение переменной `version` — версии языка. ```python {.task_source #python_chapter_0200_task_0010} ``` После импорта модуля `sys` можно обращаться к его переменным через точку. Например, `sys.path`. {.task_hint} ```python {.task_answer} import sys print(sys.path) print(sys.version) ``` Всякий раз при обращении к функции из модуля предварять ее именем модуля может быть неудобно. На помощь приходят псевдонимы (aliases). Зададим для модуля `math` псевдоним `m`. Для этого воспользуемся квалификатором `as`: ```python import math as m def sphere_volume(r): return m.pi * m.pow(r, 3) * 4 / 3 ``` Сократив `math.pow()` до `m.pow()`, мы ничего не выиграли. Но имена других модулей могут быть довольно длинными. Также модули могут группироваться в пакеты. Тогда при импорте вложенные имена разделяются точкой: ```python import os.path as p print(p.join("documents", "scans", "passport.pdf")) ``` ``` documents/scans/passport.pdf ``` Импортируйте модуль `shutil` для работы с файлами и директориями. Дайте ему псевдоним `sh`. {.task_text} Выведите в консоль результат работы функции `disk_usage()` из модуля. Она возвращает статистику использования дискового пространства в байтах и принимает путь к директории. Нас интересует корневая директория `/`. {.task_text} ```python {.task_source #python_chapter_0200_task_0020} ``` Импорт с использованием псевдонима: `import shutil as sh`. {.task_hint} ```python {.task_answer} import shutil as sh print(sh.disk_usage("/")) ``` Чтобы импортировать из модуля только нужные определения, а не все подряд, их перечисляют с помощью конструкции `from ... import ...`. Тогда обращение к ним осуществляется без указания имени модуля: ```python from math import pi, pow def sphere_volume(r): return pi * pow(r, 3) * 4 / 3 ``` Однако импортируемые имена могут конфликтовать с теми, что уже присутствуют в коде: ```python from math import pi, pow pow = 8 print(pow(2, 3)) ``` ``` Traceback (most recent call last): File "example.py", line 4, in <module> print(pow(2, 3)) ^^^^^^^^^ TypeError: 'int' object is not callable ``` Здесь мы импортировали функцию `pow()`, но перекрыли ее видимость целочисленной переменной `pow`. Для разрешения конфликтов импортируемым именам даются псевдонимы: ```python from some_module import func as f, val as v ``` Устраните конфликт имен: заведите псевдонимы для импортируемых определений; воспользуйтесь ими в вызывающем коде. {.task_text} ```python {.task_source #python_chapter_0200_task_0030} from math import pi, pow # Local names: pi = 0 pow = 1 # Here we want to use imported names, not local: print(round(pi)) print(pow(2, 3)) ``` Задание псевдонима для импортируемого определения: `from math import pi as pi_val`. {.task_hint} ```python {.task_answer} from math import pi as pi_val, pow as pow_f # Local names: pi = 0 pow = 1 # Here we want to use imported names, not local: print(round(pi_val)) print(pow_f(2, 3)) ``` С помощью `from` можно организовать и **массовый импорт,** при котором в локальную область видимости попадают вообще все определения из модуля кроме приватных. Имена приватных объектов начинаются с символа `_`, и в момент импорта интерпретатор их пропускает. Для массового импорта применяется символ `*`: ```python from math import * print(pi) print(sqrt(25)) print(trunc(9.1)) ``` ``` 3.141592653589793 5.0 9 ``` Злоупотреблять массовым импортом не рекомендуется: разработчик теряет контроль, какие имена из модуля попадут в область видимости, будут ли они перекрыты локальными объектами. Откажитесь от массового импорта через `*` в пользу импорта конкретных функций, которые уже вызываются в коде. {.task_text} ```python {.task_source #python_chapter_0200_task_0040} from os.path import * print(exists("/tmp")) print(splitext("documents/scans/passport.pdf")) ``` Импорт определения из модуля: `from os.path import exists`. {.task_hint} ```python {.task_answer} from os.path import exists, splitext print(exists("/tmp")) print(splitext("documents/scans/passport.pdf")) ``` При импорте нескольких модулей их допускается перечислять на одной строке: ```python import os, sys ``` Но PEP8 настаивает на подходе «одна строка — один импорт»: ```python import os import sys ``` Импортируйте модуль `datetime` для работы с датой, временем и часовыми поясами. Воспользуйтесь одноименным классом `datetime` из модуля: выведите в консоль результат работы метода класса `today()`. {.task_text} Помимо этого, на двух отдельных строках выведите: {.task_text} - Тип объекта модуля. - Docstring модуля. Он доступен через dunder-поле `__doc__`. ```python {.task_source #python_chapter_0200_task_0050} ``` Внутри модуля `datetime` содержится одноименный класс `datetime`. Поэтому после импорта модуля через `import datetime` вызвать метод класса можно следующим образом: `datetime.datetime.today()`. {.task_hint} ```python {.task_answer} import datetime print(datetime.datetime.today()) print(type(datetime)) print(datetime.__doc__) ``` ## Создание пользовательских модулей Любой файл с кодом на питоне может быть импортирован как модуль. Создадим свой пользовательский модуль с именем `useful.py` и основной скрипт `run.py`: ``` ~/some_project/ ├── run.py └── useful.py ``` Содержимое модуля `useful.py`: ```python print("Loading useful module...") useful_var = 255 def useful_f(): print("function from module") ``` Содержимое основного скрипта `run.py`, импортирующего модуль: ```python import useful as u print(u.useful_var) u.useful_f() ``` При запуске скрипта в консоль выведется: ``` Loading useful module... 255 function from module ``` Как видите, создавать и подключать модули крайне просто. ## Процесс загрузки модулей Разработчики на C и C++ часто сравнивают инструкцию `import` с директивой `#include`. Это не очень правильно, потому что `#include` — это директива препроцессора компилятора. Она отрабатывает на этапе компиляции и копирует исходный код из подключаемого файла на место директивы. А `import` запускается в рантайме и может послужить вполне реальной причиной замедления скрипта. Разберем основные шаги, которые выполняет интерпретатор при импорте: 1. Поиск файла модуля. Интерпретатор перебирает пути, прописанные в переменной `path` модуля `sys`. А это текущая директория проекта, содержимое переменной окружения `PYTHONPATH`, директории стандартной библиотеки питона. Если модуль не найден, выбрасывается исключение `ImportError`. 2. Компиляция содержимого модуля в байт-код. Файлы с байт-кодом имеют расширение `.pyc`. 3. Создание объекта типа `module` — пространства имен модуля, инкапсулирующего его содержимое. Выполнение содержимого модуля в созданном пространстве имен. При импорте код модуля выполняется полностью. В примере выше именно из-за этого в консоль была выведена строка `Loading useful module...`. 4. Кэширование модуля. Все загруженные модули хранятся в специальном кэше `sys.modules`. Это словарь имен и объектов модулей. Он нужен, чтобы повторно не выполнять долгий процесс поиска и загрузки модуля. 5. Создание в вызывающем коде переменной — имени, которое ссылается на объект модуля. После выполнения этих шагов модуль считается успешно импортированным. Если из вызывающего кода происходит обращение к не существующему в модуле объекту, генерируется исключение `AttributeError`. Важно, что шаги 1-4 выполняются строго один раз. Если импортировать модуль повторно, его объект будет извлечен из **кэша модулей.** Импортируйте модуль `os` для взаимодействия с операционной системой. Воспользуйтесь словарем `environ` из этого модуля, содержащим переменные окружения: выведите в консоль значение переменной окружения `PYTHON_VERSION`. {.task_text} ```python {.task_source #python_chapter_0200_task_0060} ``` Для обработки ситуации, когда ключ `PYTHON_VERSION` отсутствует в словаре `environ`, вместо обращения к элементу через `[]` воспользуйтесь методом словаря `get()`. {.task_hint} ```python {.task_answer} import os print(os.environ.get("PYTHON_VERSION", "")) ``` ## Запуск модуля как самостоятельного скрипта {#block-if-main} Любой файл на питоне может быть выполнен в двух режимах: - Как основной скрипт из консоли. - В момент импорта в качестве модуля. Для чего может потребоваться запускать модуль из консоли? Например, для тестов или запуска примеров. Как правило, при этом мы не хотим, чтобы код, исполняющийся в консольном режиме, исполнялся и в момент импорта модуля в другие файлы. На помощь приходит dunder-поле `__name__`, соотносимое интерпретатором любому файлу на питоне. При импорте это имя становится равным имени файла без расширения. То есть превращается в имя модуля. А при запуске из консоли оно равняется строке `"__main__"`. Это позволяет организовать в модуле вот такую проверку: ```python if __name__ == "__main__": # Code for running in console ... ``` Импортируйте модуль `csv`. При условии, что файл запускается в качестве скрипта из консоли, выведите значение `__name__` модуля `csv`. {.task_text} ```python {.task_source #python_chapter_0200_task_0070} ``` Внутри условия `__name__ == "__main__"` требуется вывести значение `csv.__name__`. {.task_hint} ```python {.task_answer} import csv if __name__ == "__main__": print(csv.__name__) ``` ## Пакеты Средние и крупные проекты в питоне удобно структурировать на пакеты. **Пакет** — это модуль, содержащий сабмодули. Он представляет из себя директорию, в которой находятся файлы модулей, а также специальный файл `__init__.py`. Он нужен для кастомизации действий, выполняемых интерпретатором для инициализации пакета. `__init__.py` может содержать код и определения, которые будут доступны при импорте пакета. Но чаще всего его оставляют пустым. Начиная с версии языка 3.3 директории без `__init__.py` трактуются как пакеты особого типа. Это пакеты пространства имен (namespace packages), по сути являющиеся контейнерами подпакетов. Пакеты могут быть вложенными, например: ``` utils/ ├── __init__.py ├── logging/ │ ├── __init__.py │ ├── basic_file_log.py │ ├── rotating_file_log.py │ └── stdout_color_log.py └── fs/ ├── __init__.py └── filetypes/ ├── __init__.py ├── bin.py └── so.py ``` При импорте указывается путь к модулю, в котором разделителем является точка: ```python import utils.logging.rotating_file_log as log ``` Допустим, в вашем проекте есть пакет `utils` со структурой, изображенной на иллюстрации выше. Напишите, как будет выглядеть импорт сабмодуля `bin`.{.task_text} ```consoleoutput {.task_source #python_chapter_0200_task_0080} ``` Путь к `bin`: `utils` -> `fs` -> `filetypes` -> `bin`. {.task_hint} ```python {.task_answer} import utils.fs.filetypes.bin ``` Обратите внимание: с точки зрения пользователя неважно, что импортировать — модуль или пакет. ## Основные библиотечные модули В стандартной библиотеке питона содержится более 200 модулей. Полный список приведен [в официальной документации.](https://docs.python.org/3/library/) Кратко обсудим наиболее широко используемые. **Модули в помощь разработке:** - `logging` — [логирование](https://docs.python.org/3/library/logging.html). Даже в небольших скриптах вместо `print()` по очевидным причинам правильнее выводить сообщения в лог. - `typing` — [аннотации типов](https://docs.python.org/3/library/typing.html) для типизирования параметров функций, возвращаемых значений, полей класса и других объектов. Это делает код более прозрачным и помогает статическим анализаторам выявлять ошибки. - `unittest` — [фреймворк для юнит тестов.](https://docs.python.org/3/library/unittest.html) **Типы данных:** - `collections` — [контейнеры.](https://docs.python.org/3/library/collections.html) Альтернативы встроенным словарям, спискам, кортежам и множествам. - `enum` — [тип «перечисление»:](https://docs.python.org/3/library/enum.html) объект данного типа может принимать значение из фиксированного множества идентификаторов. - `dataclasses` — [декораторы для превращения классов в структуры,](https://docs.python.org/3/library/dataclasses.html) содержащие только поля. Методы `__init__()`, `__repr__()` и другие генерируются декораторами автоматически. - `datetime` — [типы для даты и времени.](https://docs.python.org/3/library/datetime.html) **Чтение и сохранение данных:** - `json` — [чтение/запись json.](https://docs.python.org/3/library/json.html) - `csv` — [чтение/запись csv](https://docs.python.org/3/library/csv.html) - `pickle` — [сериализация/десериализация](https://docs.python.org/3/library/pickle.html) объектов в бинарный формат для пересылки и хранения. - `sqlite3` — [работа с бд sqlite.](https://docs.python.org/3/library/sqlite3.html) **Алгоритмы:** - `re` — [регулярные выражения.](https://docs.python.org/3/library/re.html) - `copy` — [поверхностное и глубокое копирование.](https://docs.python.org/3/library/copy.html) - `math` — [математические формулы и константы.](https://docs.python.org/3/library/math.html) - `random` — [генераторы псевдослучайных чисел.](https://docs.python.org/3/library/random.html) - `itertools` — [итераторы для работы с данными в функциональном стиле.](https://docs.python.org/3/library/itertools.html) - `functools` — [функции высшего порядка,](https://docs.python.org/3/library/functools.html) которые принимают в себя функции и возвращают функции. **Файловая система, операционная система, сеть:** - `os` — [интерфейсы для работы с операционной системой.](https://docs.python.org/3/library/os.html) - `os.path` — [работа с путями в файловой системе.](https://docs.python.org/3/library/os.path.html) - `shutil` — [высокоуровневые функции для работы с файлами.](https://docs.python.org/3/library/shutil.html) - `urllib` — [набор модулей для общения по HTTP и парсинга URL.](https://docs.python.org/3/library/urllib.html) Полный список модулей стандартной библиотеки хранится в атрибуте `stdlib_module_names` модуля `sys`. {.task_text} Выведите в консоль модули стандартной библиотеки по одному на строку, отсортированные в алфавитном порядке. Среди них не должно быть модулей для внутреннего использования (имена которых начинаются с `_`). {.task_text} ```python {.task_source #python_chapter_0200_task_0090} ``` Для сорировки имен модулей воспользуйтесь встроенной функцией `sorted()`. {.task_hint} ```python {.task_answer} from sys import stdlib_module_names for m in sorted(stdlib_module_names): if not m.startswith("_"): print(m) ``` В питоне есть встроенная функция `help()`, которая в интерактивном режиме выдает справку по переданному в нее объекту. С ее помощью вы можете без доступа в интернет узнать, для чего предназначен тот или иной модуль и какие он содержит определения. ## Резюмируем - Ключевое слово `import` используется для импортирования модуля. - Для импорта отдельных определений из модуля есть ключевое слово `from`. - Модулям можно давать псевдонимы: `import datetime as dt`. - Массовым импортом `from sys import *` пользоваться не рекомендуется. - Модули структурируются в пакеты. При импорте имя модуля указывается после имени пакета: `import some_package.some_module`.
Отправка...

Если вам нравится проект, вы можете поддержать его!

Задонатить