Пакети
Модулі можуть об'єднуватись в пакети. Пакети слугують як простори імен для модулів і спосіб їх структурування.
Будь-який пакет є модулем, але не кожен модуль є пакетом.
Як правило (але не завжди) модулі представляються у вигляді файлів, а пакети — каталогів у файловій системі.
Для того, щоб каталог був пакетом, у ньому повинен знаходитись файл __init__.py
. Він автоматично виконується при імпортуванні певного модуля і може містити певні дії для ініціалізації або бути порожнім.
Приклад (структура файлів):
- package (тека, пакет)
__init__.py
(файл, модуль)module1.py
(файл, модуль)module2.py
(файл, модуль)
Всередині пакетів можуть бути не тільки модулі, а й інші пакети також:
- package (тека, пакет)
__init__.py
(файл, модуль)module1.py
(файл, модуль)module2.py
(файл, модуль)subpackage
(тека, пакет)__init__.py
(файл, модуль)submodule1.py
(файл, модуль)submodule2.py
(файл, модуль)
Приклади імпорту з пакетами:
import package.module # модуль з пакета необхідно імпортувати явно
import package.subpackage.module
from package import module
from package import item
from package.subpackage import module, item
from module import *
При використанні оператора
from package import item
item
може бути пакетом, модулем або будь-яким ім'ям, описаним у пакеті.
При використанні оператора
import package.item
item
повинен бути модулем або пакетом.
Для того, щоб можна було імпортувати усі імена пакета
from package.subpackage import *
пакет має описувати список __all__
, котрий містить імена підпакетів і модулів.
Відносний імпорт
Крім абсолютних існують також відносні імпорти:
- крапка вказує на поточний пакет (відповідає імені пакета, у якому викликається імпорт)
- дві крапки — на батьківський і так далі
Ці ж символи можуть бути використані одразу ж перед іменем пакета або модуля і впливати на те, де інтерпретатор буде шукати його. Приклади:
from . import name
from .. import name
from .package import name
from ..package import name
Механізм відносних імпортів дозволяє уникнути зміни імпортів у модулях при переіменуванні або переміщені пакета, то ж користуйтесь цим частіше.
Фасад
Задача модуля __init__.py
полягає в ініціалізації пакета, тому не варто реалізовувати у ньому всю логіку.
В цьому модулі варто робити:
- нічого
- оголосити глобальні для модуля змінні
- огородити пакет фасадом
Фасад (Facade) — шаблон проектування, який дозволяє приховати складність системи шляхом зведення усіх можливих зовнішніх викликів до одного об'єкта, який делегує їх відповідним об'єктам системи.
В Python, у контексті пакетів, фасад — це імпорт імен з вкладених у пакет модулів і пакетів і визначення змінної __all__
.
Розглянемо на прикладі. Припустимо маємо пакет з такою структурою:
package
__init__.py
module.py
subpackage
__init__.py
subpackage_module.py
Спочатку для пакета subpackage
ми імпортуємо увесь вміст вкладеного модуля і вказуємо, що це буде вмістом цього пакета:
# файл package/subpackage/__init__.py
from .subpackage_module import *
__all__ = subpackage_module.__all__
Тепер аналогічно поступимо з пакетом package
: імпортуємо усе з вкладеного модуля module
,
потім усе з вкладеного пакета subpacage
,
і оголосимо __all__
як конкатенацію аналогічних змінних двох його нащадків:
# файл package/__init__.py
from .module import *
from .subpackage import *
__all__ = module.__all__ + subpacage.__all__
Тобто ми як би абстрагували структуру пакета. Тепер необов'язково пам'ятати як називаються модулі,
як у пакеті взагалі все організовано.
Ми можемо використовувати пакет package
приблизно так:
from package import some_entity
Плюси такого "фасаду":
- не треба запам'ятовувати внутрішню структуру пакета і думати, з чим саме він працює: модулем чи пакетом. Наприклад, ми можемо не задумуватись як імпортувати функцію
urlopen
:from urllib import urlopen from urllib.request import urlopen from urllib.requests import urlopen
- інтерфейс не залежить від деталей реалізації — можна переміщувати код між внутрішніми модулями і пакетами
Мінуси:
- на завантаження і ініціалізацію витрачається багато часу