# Глава 20. Пакеты, модули и библиотечные типы данных
Модуль — это объект, содержащий пространство имен. Он считается единицей структурирования кода. А по сути модуль — это файл, из которого можно импортировать определения.
Если поместить модули в директорию и добавить в нее служебный файл `__init__.py`, то эта директория станет пакетом.
Стандартная библиотека питона изобилует модулями на все случаи жизни. «Из коробки» в питоне доступно почти все. Это важное достоинство, способствовавшее широкому распространению языка.
## Для чего нужны модули и пакеты
Разбиение кода на [модули](https://docs.python.org/3/tutorial/modules.html) решает две основные задачи:
- Переиспользование. В модуль выносятся классы, функции, константы. Затем модуль подключается по месту использования.
- Инкапсуляция. Фактически модуль — это пространство имен, в которое группируются схожие по смыслу определения.
[Пакеты](https://docs.python.org/3/tutorial/modules.html#packages) в свою очередь помогают выстроить иерархию из модулей, то есть создать вложенность пространств имен.
## Импорт модулей
Модули импортируются с помощью ключевого слова `import`. По PEP8 их [следует перечислять](https://peps.python.org/pep-0008/#imports) в самом начале файла и группировать, отделяя каждую группу пустой строкой:
- В начале идут модули стандартной библиотеки.
- Затем модули third party.
- И последними перечисляются модули текущего проекта.
Для импорта предусмотрено несколько вариантов синтаксиса. Рассмотрим их на примере работы с модулями из стандартной библиотеки.
Модуль `math` содержит математические функции и константы. Импортируем его, чтобы посчитать объем шара. Задействуем константу `pi` и функцию возведения в степень `pow()`:
```python {.example_for_playground}
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 {.example_for_playground}
import math as m
def sphere_volume(r):
return m.pi * m.pow(r, 3) * 4 / 3
```
Сократив `math.pow()` до `m.pow()`, мы ничего не выиграли. Но имена других модулей могут быть довольно длинными. Также модули могут группироваться в пакеты. Тогда при импорте вложенные имена разделяются точкой:
```python {.example_for_playground}
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 {.example_for_playground}
from math import pi, pow
def sphere_volume(r):
return pi * pow(r, 3) * 4 / 3
```
Однако импортируемые имена могут конфликтовать с теми, что уже присутствуют в коде:
```python {.example_for_playground}
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 {.example_for_playground}
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 {.example_for_playground}
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) для типизирования параметров функций, возвращаемых значений, полей класса и других объектов. Это делает код более прозрачным и помогает статическим анализаторам выявлять ошибки.
- `argparse` — [разбор аргументов командной строки.](https://docs.python.org/3/library/argparse.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`.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!