# 5. Система імпорту

Код Python в одному module отримує доступ до коду в іншому модулі шляхом процесу importing його. Інструкція `import` є найпоширенішим способом виклику механізму імпорту, але це не єдиний спосіб. Такі функції, як `importlib.import_module()` і вбудований `__import__()` також можна використовувати для виклику механізму імпорту.

Оператор `import` поєднує дві операції; він шукає названий модуль, а потім прив’язує результати цього пошуку до імені в локальній області. Операція пошуку оператора `import` визначається як виклик функції `__import__()` з відповідними аргументами. Значення, що повертається `__import__()`, використовується для виконання операції зв’язування імені оператора `import`. Перегляньте оператор `import` для отримання точних деталей цієї операції зв’язування імені.

Прямий виклик `__import__()` виконує лише пошук модуля та, якщо знайдено, операцію створення модуля. Хоча можуть виникнути певні побічні ефекти, такі як імпортування батьківських пакетів і оновлення різних кешів (включаючи `sys.modules`), тільки оператор `import` виконує операцію зв’язування імені.

Коли виконується оператор `import`, викликається стандартна вбудована функція `__import__()`. Інші механізми для виклику системи імпорту (такі як `importlib.import_module()`) можуть вибрати обхід `__import__()` і використовувати власні рішення для реалізації семантики імпорту.

Коли модуль імпортовано вперше, Python шукає модуль і, якщо знайдено, створює об’єкт модуля [1], ініціалізуючи його. Якщо вказаний модуль не знайдено, виникає помилка `ModuleNotFoundError`. Python реалізує різні стратегії для пошуку названого модуля під час виклику механізму імпорту. Ці стратегії можна модифікувати та розширювати за допомогою різноманітних хуків, описаних у розділах нижче.

Змінено в версії 3.3: Систему імпорту оновлено для повної реалізації другої фази PEP 302. Більше немає механізму неявного імпорту – повна система імпорту доступна через `sys.meta_path`. Крім того, була реалізована підтримка рідного пакету простору імен (див. PEP 420).

## 5.1. `importlib`

Модуль `importlib` надає багатий API для взаємодії із системою імпорту. Наприклад, `importlib.import_module()` надає рекомендований простіший API, ніж вбудований `__import__()` для виклику механізму імпорту. Зверніться до документації бібліотеки `importlib` для отримання додаткової інформації.

## 5.2. пакети

Python має лише один тип об’єкта модуля, і всі модулі належать до цього типу, незалежно від того, чи реалізовано модуль на Python, C чи в чомусь іншому. Щоб допомогти організувати модулі та забезпечити ієрархію імен, Python має концепцію packages.

Ви можете розглядати пакети як каталоги у файловій системі, а модулі як файли в каталогах, але не сприймайте цю аналогію надто буквально, оскільки пакети та модулі не обов’язково походять із файлової системи. Для цілей цієї документації ми будемо використовувати цю зручну аналогію каталогів і файлів. Як і каталоги файлової системи, пакунки організовані ієрархічно, і самі пакунки можуть містити підпакети, а також звичайні модулі.

Важливо мати на увазі, що всі пакети є модулями, але не всі модулі є пакетами. Або інакше кажучи, пакунки - це просто особливий вид модулів. Зокрема, будь-який модуль, який містить атрибут `__path__`, вважається пакетом.

Усі модулі мають назву. Назви підпакетів відокремлюються від назви батьківського пакета крапкою, подібно до стандартного синтаксису доступу до атрибутів Python. Таким чином, у вас може бути пакет під назвою `email`, який, у свою чергу, має підпакет під назвою `email.mime` і модуль у цьому підпакеті під назвою `email.mime.text`.

### 5.2.1. Звичайні пакети

Python визначає два типи пакетів: звичайні пакунки і пакети простору імен. Звичайні пакунки — це традиційні пакунки, які існували в Python 3.2 і раніше. Звичайний пакет зазвичай реалізується як каталог, що містить файл `__init__.py`. Коли імпортується звичайний пакет, цей файл `__init__.py` виконується неявно, а об’єкти, які він визначає, прив’язуються до імен у просторі імен пакета. Файл `__init__.py` може містити той самий код Python, який може містити будь-який інший модуль, і Python додасть деякі додаткові атрибути до модуля під час його імпорту.

Наприклад, наведений нижче макет файлової системи визначає «батьківський» пакет верхнього рівня з трьома підпакетами:

    parent/
        __init__.py
        one/
            __init__.py
        two/
            __init__.py
        three/
            __init__.py

Імпорт `parent.one` неявно виконає `parent/__init__.py` і `parent/one/__init__.py`. Наступні імпорти `parent.two` або `parent.three` виконають `parent/two/__init__.py` та `parent/three/__init__.py` відповідно.

### 5.2.2. Пакети простору імен

Пакет простору імен — це сукупність різноманітних частин, де кожна частина додає підпакет до батьківського пакета. Частини можуть знаходитися в різних місцях файлової системи. Частини також можна знайти в zip-файлах, у мережі чи будь-де ще, де Python шукає під час імпорту. Пакети простору імен можуть або не можуть відповідати безпосередньо об’єктам у файловій системі; вони можуть бути віртуальними модулями, які не мають конкретного представлення.

Пакунки простору імен не використовують звичайний список для свого атрибута `__path__`. Натомість вони використовують настроюваний ітерований тип, який автоматично виконає новий пошук частин пакета під час наступної спроби імпорту в цьому пакеті, якщо шлях до їх батьківського пакета (або `sys.path` для пакета верхнього рівня) зміниться.

У пакетах простору імен немає файлу `parent/__init__.py`. Насправді під час пошуку імпорту може бути знайдено кілька `батьківських` каталогів, де кожен надається окремою частиною. Таким чином, `батьківський/один` може фізично не знаходитися поруч із `батьківським/два`. У цьому випадку Python створюватиме пакет простору імен для `батьківського` пакета верхнього рівня кожного разу, коли він або один із його підпакетів імпортується.

Дивіться також PEP 420 для специфікації пакета простору імен.

## 5.3. Пошук

Щоб розпочати пошук, Python потребує повної назви модуля (або пакета, але для цілей цього обговорення різниця несуттєва), який імпортується. Це ім’я може походити з різних аргументів оператора `import` або з параметрів функцій `importlib.import_module()` або `__import__()`.

Ця назва використовуватиметься на різних етапах пошуку імпорту, і це може бути пунктирний шлях до підмодуля, напр. `foo.bar.baz`. У цьому випадку Python спочатку намагається імпортувати `foo`, потім `foo.bar` і, нарешті, `foo.bar.baz`. Якщо будь-який із проміжних імпортів не вдається, виникає помилка `ModuleNotFoundError`.

### 5.3.1. Кеш модуля

Перше місце, яке перевіряється під час пошуку імпорту, це `sys.modules`. Це відображення служить кеш-пам’яттю всіх модулів, які були раніше імпортовані, включаючи проміжні шляхи. Отже, якщо `foo.bar.baz` був раніше імпортований, `sys.modules` міститиме записи для `foo`, `foo.bar` і `foo.bar.baz`. Кожен ключ матиме значення відповідного об’єкта модуля.

Під час імпорту ім’я модуля шукається в `sys.modules` і, якщо воно присутнє, пов’язане значення означає модуль, який задовольняє імпорт, і процес завершується. Однак, якщо значенням є `None`, тоді виникає `ModuleNotFoundError`. Якщо ім’я модуля відсутнє, Python продовжить пошук модуля.

`sys.modules` доступний для запису. Видалення ключа може не знищити пов’язаний модуль (оскільки інші модулі можуть містити посилання на нього), але це зробить недійсним запис кешу для названого модуля, змусивши Python знову шукати названий модуль під час наступного імпорту. Ключ також може бути призначений як `None`, змушуючи наступний імпорт модуля призводити до `ModuleNotFoundError`.

Але будьте обережні: якби ви зберегли посилання на об’єкт модуля, зробили недійсним запис кешу в `sys.modules`, а потім повторно імпортували названий модуль, два об’єкти модуля не будуть однаковими. Навпаки, `importlib.reload()` повторно використовуватиме той самий об’єкт модуля та просто повторно ініціалізує вміст модуля, повторно запускаючи код модуля.

### 5.3.2. Шукачі та вантажники

Якщо названий модуль не знайдено в `sys.modules`, тоді для пошуку та завантаження модуля викликається протокол імпорту Python. Цей протокол складається з двох концептуальних об’єктів, finders і loaders. Робота шукача полягає в тому, щоб визначити, чи зможе він знайти названий модуль, використовуючи ту стратегію, про яку він знає. Об’єкти, які реалізують обидва ці інтерфейси, називаються імпортерами — вони повертаються самі, коли виявляють, що можуть завантажити запитаний модуль.

Python містить ряд засобів пошуку та імпорту за замовчуванням. Перший знає, як знайти вбудовані модулі, а другий знає, як знайти заморожені модулі. Третій засіб пошуку за умовчанням шукає модулі в шляху імпорту. шлях імпорту — це список розташувань, які можуть іменувати шляхи файлової системи або файли zip. Його також можна розширити для пошуку будь-якого ресурсу, який можна знайти, наприклад, ідентифікованого за URL-адресами.

Механізм імпорту можна розширювати, тому можна додавати нові шукачі, щоб розширити діапазон і обсяг пошуку модулів.

Шукачі фактично не завантажують модулі. Якщо їм вдається знайти названий модуль, вони повертають module spec, інкапсуляцію інформації, пов’язаної з імпортом модуля, яку потім використовує механізм імпорту під час завантаження модуля.

У наступних розділах більш детально описано протоколи для шукачів і завантажувачів, зокрема те, як ви можете створити та зареєструвати нові протоколи для розширення механізму імпорту.

Змінено в версії 3.4: У попередніх версіях Python шукачі повертали loaders безпосередньо, тоді як тепер вони повертають специфікації модулів, які містять завантажувачі. Завантажувачі все ще використовуються під час імпорту, але мають менше обов’язків.

### 5.3.3. Імпортні гачки

Імпортна техніка розрахована на розширення; основним механізмом для цього є перехоплення імпорту. Існує два типи хуків імпорту: мета-хуки та хуки шляху імпорту.

Мета-хуки викликаються на початку обробки імпорту, до того, як відбулася будь-яка інша обробка імпорту, окрім пошуку кешу `sys.modules`. Це дозволяє мета-хукам перевизначати `sys.path` обробку, заморожені модулі або навіть вбудовані модулі. Мета-хуки реєструються шляхом додавання нових об’єктів пошуку до `sys.meta_path`, як описано нижче.

Перехоплювачі шляхів імпорту викликаються як частина обробки `sys.path` (або `package.__path__`) у точці, де зустрічається пов’язаний з ними елемент шляху. Хуки шляхів імпорту реєструються шляхом додавання нових викликів до `sys.path_hooks`, як описано нижче.

### 5.3.4. Меташлях

When the named module is not found in `sys.modules`, Python next searches `sys.meta_path`, which contains a list of meta path finder objects. These finders are queried in order to see if they know how to handle the named module. Meta path finders must implement a method called `find_spec()` which takes three arguments: a name, an import path, and (optionally) a target module. The meta path finder can use any strategy it wants to determine whether it can handle the named module or not.

Якщо засіб пошуку меташляху знає, як обробляти названий модуль, він повертає специфікаційний об’єкт. Якщо він не може обробити названий модуль, він повертає `None`. Якщо обробка `sys.meta_path` досягає кінця свого списку без повернення специфікації, тоді виникає `ModuleNotFoundError`. Будь-які інші викликані винятки просто поширюються, перериваючи процес імпорту.

The `find_spec()` method of meta path finders is called with two or three arguments. The first is the fully qualified name of the module being imported, for example `foo.bar.baz`. The second argument is the path entries to use for the module search. For top-level modules, the second argument is `None`, but for submodules or subpackages, the second argument is the value of the parent package’s `__path__` attribute. If the appropriate `__path__` attribute cannot be accessed, a `ModuleNotFoundError` is raised. The third argument is an existing module object that will be the target of loading later. The import system passes in a target module only during reload.

Мета-шлях може проходити кілька разів для одного запиту на імпорт. Наприклад, якщо припустити, що жоден із задіяних модулів уже не кешовано, імпорт `foo.bar.baz` спочатку виконає імпорт верхнього рівня, викликаючи `mpf.find_spec("foo", None, None)` на шукач кожного меташляху (`mpf`). Після того, як `foo` було імпортовано, `foo.bar` буде імпортовано шляхом проходження меташляху вдруге, викликавши `mpf.find_spec("foo.bar", foo.__path__, None)` . Після того, як `foo.bar` буде імпортовано, остаточний обхід викличе `mpf.find_spec("foo.bar.baz", foo.bar.__path__, None)`.

Деякі засоби пошуку меташляхів підтримують лише імпорт верхнього рівня. Ці імпортери завжди повертатимуть `None`, якщо будь-що інше, ніж `None`, передається як другий аргумент.

За замовчуванням `sys.meta_path` Python має три засоби пошуку меташляху: один, який знає, як імпортувати вбудовані модулі, інший, який знає, як імпортувати заморожені модулі, і інший, який знає, як імпортувати модулі зі шляху імпорту (import path, тобто path based finder).

Змінено в версії 3.4: The `find_spec()` method of meta path finders replaced `find_module()`, which is now deprecated. While it will continue to work without change, the import machinery will try it only if the finder does not implement `find_spec()`.

Змінено в версії 3.10: Use of `find_module()` by the import system now raises `ImportWarning`.

Змінено в версії 3.12: `find_module()` has been removed. Use `find_spec()` instead.

## 5.4. Завантаження

Якщо специфікація модуля буде знайдена, механізм імпорту використовуватиме її (і завантажувач, який вона містить) під час завантаження модуля. Ось приблизно те, що відбувається під час завантаження частини імпорту:

    module = None
    if spec.loader is not None and hasattr(spec.loader, 'create_module'):
        # It is assumed 'exec_module' will also be defined on the loader.
        module = spec.loader.create_module(spec)
    if module is None:
        module = ModuleType(spec.name)
    # The import-related module attributes get set here:
    _init_module_attrs(spec, module)

    if spec.loader is None:
        # unsupported
        raise ImportError
    if spec.origin is None and spec.submodule_search_locations is not None:
        # namespace package
        sys.modules[spec.name] = module
    elif not hasattr(spec.loader, 'exec_module'):
        module = spec.loader.load_module(spec.name)
    else:
        sys.modules[spec.name] = module
        try:
            spec.loader.exec_module(module)
        except BaseException:
            try:
                del sys.modules[spec.name]
            except KeyError:
                pass
            raise
    return sys.modules[spec.name]

Зверніть увагу на такі деталі:

- Якщо в `sys.modules` є існуючий об’єкт модуля з вказаною назвою, імпортування його вже повернуло.

- Модуль існуватиме в `sys.modules` до того, як завантажувач виконає код модуля. Це важливо, оскільки код модуля може (прямо чи опосередковано) імпортувати сам себе; додавання його до `sys.modules` заздалегідь запобігає необмеженій рекурсії в гіршому випадку та багаторазовому завантаженню в найкращому.

- Якщо завантаження не вдасться, модуль із помилкою – і тільки модуль із помилкою – видаляється з `sys.modules`. Будь-який модуль, який уже знаходиться в кеші `sys.modules`, і будь-який модуль, який було успішно завантажено як побічний ефект, повинні залишатися в кеші. Це контрастує з перезавантаженням, коли навіть несправний модуль залишається в `sys.modules`.

- Після створення модуля, але перед його виконанням, механізм імпорту встановлює пов’язані з імпортом атрибути модуля («_init_module_attrs» у наведеному вище прикладі псевдокоду), як підсумовано в пізнішому розділі.

- Виконання модуля є ключовим моментом завантаження, під час якого простір імен модуля заповнюється. Виконання повністю делеговано завантажувачу, який вирішує, що заповнювати і як.

- Модуль, створений під час завантаження та переданий у exec_module(), може не бути тим, що повертається в кінці імпорту [2].

Змінено в версії 3.4: Система імпорту взяла на себе шаблонні обов’язки вантажників. Раніше вони виконувалися методом `importlib.abc.Loader.load_module()`.

### 5.4.1. Вантажники

Завантажувачі модулів забезпечують критичну функцію завантаження: виконання модуля. Механізм імпорту викликає метод `importlib.abc.Loader.exec_module()` з одним аргументом, об’єктом модуля, який потрібно виконати. Будь-яке значення, повернуте з `exec_module()` ігнорується.

Вантажники повинні відповідати таким вимогам:

- Якщо модуль є модулем Python (на відміну від вбудованого модуля або динамічно завантажуваного розширення), завантажувач повинен виконати код модуля в глобальному просторі імен модуля (`module.__dict__`).

- Якщо завантажувач не може виконати модуль, він має викликати `ImportError`, хоча будь-які інші винятки, викликані під час `exec_module()`, будуть поширені.

У багатьох випадках шукач і завантажувач можуть бути одним і тим же об’єктом; у таких випадках метод `find_spec()` просто повертатиме специфікацію із завантажувачем, встановленим на `self`.

Завантажувачі модулів можуть вибрати створення об’єкта модуля під час завантаження, реалізувавши метод `create_module()`. Він приймає один аргумент, специфікацію модуля, і повертає новий об’єкт модуля для використання під час завантаження. `create_module()` не потребує встановлення жодних атрибутів для об’єкта модуля. Якщо метод повертає `None`, механізм імпорту створить новий модуль сам.

Added in version 3.4: Метод завантажувачів `create_module()`.

Змінено в версії 3.4: Метод `load_module()` було замінено на `exec_module()`, а механізм імпорту взяв на себе всі шаблонні обов’язки щодо завантаження.

Для сумісності з існуючими завантажувачами механізм імпорту використовуватиме метод завантажувачів `load_module()`, якщо він існує, а завантажувач також не реалізує `exec_module()`. Однак `load_module()` застаріло, і завантажувачі повинні використовувати `exec_module()` замість нього.

Метод `load_module()` має реалізовувати всі стандартні функції завантаження, описані вище, на додаток до виконання модуля. Застосовуються ті самі обмеження з деякими додатковими уточненнями:

- Якщо в `sys.modules` є існуючий об’єкт модуля з вказаною назвою, завантажувач повинен використати цей існуючий модуль. (Інакше `importlib.reload()` не працюватиме належним чином.) Якщо названий модуль не існує в `sys.modules`, завантажувач має створити новий об’єкт модуля та додати його до `sys.modules`.

- Модуль має існувати в `sys.modules` до того, як завантажувач виконає код модуля, щоб запобігти необмеженій рекурсії або багаторазовому завантаженню.

- Якщо завантаження не вдається, завантажувач повинен видалити будь-які модулі, які він вставив у `sys.modules`, але він повинен видалити лише модуль(и) з помилкою, і лише якщо завантажувач сам завантажив модуль( s) явно.

Змінено в версії 3.5: `DeprecationWarning` виникає, коли `exec_module()` визначено, а `create_module()` ні.

Змінено в версії 3.6: Помилка `ImportError` виникає, коли `exec_module()` визначено, а `create_module()` ні.

Змінено в версії 3.10: Використання `load_module()` призведе до `ImportWarning`.

### 5.4.2. Підмодулі

Коли підмодуль завантажується за допомогою будь-якого механізму (наприклад, `importlib` API, `import` або `import-from` оператори, або вбудований `__import__()`), прив’язка розміщується в простір імен батьківського модуля до об’єкта підмодуля. Наприклад, якщо пакет `spam` має підмодуль `foo`, після імпорту `spam.foo`, `spam` матиме атрибут `foo`, який прив’язаний до підмодуля. Скажімо, у вас є така структура каталогів:

    spam/
        __init__.py
        foo.py

а `spam/__init__.py` містить такий рядок:

    from .foo import Foo

потім виконання наступного ставить прив’язки імен для `foo` і `Foo` в `spam` модуль:

    >>> import spam
    >>> spam.foo
    <module 'spam.foo' from '/tmp/imports/spam/foo.py'>
    >>> spam.Foo
    <class 'spam.foo.Foo'>

Враховуючи знайомі правила прив’язки імен Python, це може здатися дивним, але насправді це фундаментальна функція системи імпорту. Незмінним є те, що якщо у вас є `sys.modules['spam']` і `sys.modules['spam.foo']` (як після імпорту вище), останній повинен відображатися як атрибут `foo` першого.

### 5.4.3. Module specs

Механізм імпорту використовує різноманітну інформацію про кожен модуль під час імпорту, особливо перед завантаженням. Більшість інформації є спільною для всіх модулів. Метою специфікації модуля є інкапсуляція цієї інформації, пов’язаної з імпортом, на основі кожного модуля.

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

The module’s spec is exposed as `module.__spec__`. Setting `__spec__` appropriately applies equally to modules initialized during interpreter startup. The one exception is `__main__`, where `__spec__` is set to None in some cases.

See `ModuleSpec` for details on the contents of the module spec.

Added in version 3.4.

### 5.4.4. __path__ attributes on modules

The `__path__` attribute should be a (possibly empty) sequence of strings enumerating the locations where the package’s submodules will be found. By definition, if a module has a `__path__` attribute, it is a package.

A package’s `__path__` attribute is used during imports of its subpackages. Within the import machinery, it functions much the same as `sys.path`, i.e. providing a list of locations to search for modules during import. However, `__path__` is typically much more constrained than `sys.path`.

The same rules used for `sys.path` also apply to a package’s `__path__`. `sys.path_hooks` (described below) are consulted when traversing a package’s `__path__`.

A package’s `__init__.py` file may set or alter the package’s `__path__` attribute, and this was typically the way namespace packages were implemented prior to PEP 420. With the adoption of PEP 420, namespace packages no longer need to supply `__init__.py` files containing only `__path__` manipulation code; the import machinery automatically sets `__path__` correctly for the namespace package.

### 5.4.5. Репрезентація модуля

За замовчуванням усі модулі мають придатне для використання відображення, однак, залежно від атрибутів, налаштованих вище, і в специфікації модуля ви можете чіткіше керувати відтворенням об’єктів модуля.

Якщо модуль має специфікацію (`__spec__`), механізм імпорту спробує згенерувати з неї repr. Якщо це не вдається або немає специфікації, система імпорту створить відображення за замовчуванням, використовуючи будь-яку інформацію, доступну в модулі. Він намагатиметься використати `module.__name__`, `module.__file__` і `module.__loader__` як вхідні дані в repr, із значеннями за замовчуванням для будь-якої інформації, якої немає.

Ось точні правила, які використовуються:

- Якщо модуль має атрибут `__spec__`, інформація в специфікації використовується для створення repr. Переглядаються атрибути «name», «loader», «origin» і «has_location».

- Якщо модуль має атрибут `__file__`, він використовується як частина repr модуля.

- Якщо модуль не має `__file__`, але має `__loader__`, який не є `None`, тоді repr завантажувача використовується як частина repr модуля.

- В іншому випадку просто використовуйте `__name__` модуля в repr.

Змінено в версії 3.12: Use of `module_repr()`, having been deprecated since Python 3.4, was removed in Python 3.12 and is no longer called during the resolution of a module’s repr.

### 5.4.6. Анулювання кешованого байт-коду

Перед тим, як Python завантажить кешований байт-код із файлу `.pyc`, він перевіряє, чи оновлений кеш із вихідним файлом `.py`. За замовчуванням Python робить це, зберігаючи мітку часу останньої зміни джерела та розмір у файлі кешу під час його запису. Під час виконання система імпорту потім перевіряє кеш-файл, перевіряючи збережені в кеш-файлі метадані з метаданими джерела.

Python також підтримує кеш-файли на основі хешів, які зберігають хеш вмісту вихідного файлу, а не його метаданих. Існує два варіанти хеш-файлів `.pyc`: позначений і не позначений. Для перевірених хеш-файлів `.pyc` Python перевіряє файл кешу, хешуючи вихідний файл і порівнюючи отриманий хеш із хешем у файлі кешу. Якщо перевірений файл кешу на основі хешу виявляється недійсним, Python повторно генерує його та записує новий перевірений файл кешу на основі хешу. Для неперевірених файлів `.pyc` на основі хешу Python просто припускає, що файл кешу дійсний, якщо він існує. Поведінка перевірки файлів `.pyc` на основі хешу може бути замінена прапором `--check-hash-based-pycs`.

Змінено в версії 3.7: Додано файли `.pyc` на основі хешу. Раніше Python підтримував лише недійсність кешів байт-кодів на основі часових позначок.

## 5.5. Пошук на основі шляху

Як згадувалося раніше, Python поставляється з декількома типовими засобами пошуку меташляхів. Один із них, який називається пошук на основі шляху (`PathFinder`), шукає шлях імпорту, який містить список записів шляху. Кожен запис шляху називає розташування для пошуку модулів.

Сам шукач на основі шляху не знає, як нічого імпортувати. Замість цього він проходить окремі записи шляху, пов’язуючи кожен із них із інструментом пошуку записів шляху, який знає, як обробляти цей конкретний шлях.

Стандартний набір засобів пошуку шляхів реалізує всю семантику для пошуку модулів у файловій системі, обробки спеціальних типів файлів, таких як вихідний код Python (файли `.py`), байт-код Python (файли `.pyc`) і спільні бібліотеки (наприклад, файли `.so`). Якщо підтримується модулем `zipimport` у стандартній бібліотеці, засоби пошуку шляхів за замовчуванням також обробляють завантаження всіх цих типів файлів (окрім спільних бібліотек) із файлів zip.

Записи шляху не повинні обмежуватися розташуванням файлової системи. Вони можуть посилатися на URL-адреси, запити до бази даних або будь-яке інше розташування, яке можна вказати як рядок.

Шукач на основі шляху надає додаткові перехоплювачі та протоколи, щоб ви могли розширювати та налаштовувати типи записів шляху для пошуку. Наприклад, якщо ви хочете підтримувати записи шляху як мережеві URL-адреси, ви можете написати хук, який реалізує семантику HTTP для пошуку модулів в Інтернеті. Цей хук (об’єкт виклику) повертав би path entry finder, що підтримує описаний нижче протокол, який потім використовувався для отримання завантажувача для модуля з Інтернету.

Попередження: у цьому та попередньому розділах використовується термін finder, розрізняючи їх за допомогою термінів meta path finder і path entry finder. Ці два типи шукачів дуже схожі, підтримують схожі протоколи та функціонують однаково під час процесу імпорту, але важливо мати на увазі, що вони дещо відрізняються. Зокрема, шукачі меташляху працюють на початку процесу імпорту, як ключ від обходу `sys.meta_path`.

Навпаки, засоби пошуку запису шляху є в певному сенсі деталлю реалізації засобу пошуку шляху, і фактично, якби засіб пошуку шляху було вилучено з `sys.meta_path`, жодна семантика засобу пошуку шляху не буде викликатися.

### 5.5.1. Шукачі входу в шлях

path based finder відповідає за пошук і завантаження модулів і пакетів Python, розташування яких вказано за допомогою рядка path entry. Більшість записів шляхів іменують розташування у файловій системі, але вони не повинні обмежуватися цим.

Як мета-пошук шляху, пошук на основі шляху реалізує `find_spec()` протокол, описаний раніше, однак він надає додаткові перехоплювачі, які можна використовувати для налаштування способу пошуку модулів і завантажується з шляху імпорту.

Три змінні використовуються path based finder, `sys.path`, `sys.path_hooks` і `sys.path_importer_cache`. Також використовуються атрибути `__path__` об’єктів пакета. Це надає додаткові способи налаштування імпортного обладнання.

`sys.path` contains a list of strings providing search locations for modules and packages. It is initialized from the `PYTHONPATH` environment variable and various other installation- and implementation-specific defaults. Entries in `sys.path` can name directories on the file system, zip files, and potentially other «locations» (see the `site` module) that should be searched for modules, such as URLs, or database queries. Only strings should be present on `sys.path`; all other data types are ignored.

пошук на основі шляху є метапошуком шляху, тому механізм імпорту починає пошук шляху імпорту, викликаючи `Коли вказано аргумент ``path`()` для `find_spec()`, це буде список шляхів до рядків, які потрібно пройти - зазвичай атрибут `__path__` пакета для імпорту в цьому пакет. Якщо аргумент `path` має значення `None`, це вказує на імпорт верхнього рівня та `sys.path` використовується.

The path based finder iterates over every entry in the search path, and for each of these, looks for an appropriate path entry finder (`PathEntryFinder`) for the path entry. Because this can be an expensive operation (e.g. there may be `stat()` call overheads for this search), the path based finder maintains a cache mapping path entries to path entry finders. This cache is maintained in `sys.path_importer_cache` (despite the name, this cache actually stores finder objects rather than being limited to importer objects). In this way, the expensive search for a particular path entry location’s path entry finder need only be done once. User code is free to remove cache entries from `sys.path_importer_cache` forcing the path based finder to perform the path entry search again.

Якщо запису шляху немає в кеші, пошук на основі шляху повторює кожен виклик у `sys.path_hooks`. Кожен із перехоплювачів запису шляху у цьому списку викликається з одним аргументом, записом шляху, який потрібно шукати. Цей виклик може повертати пошук запису шляху, який може обробити запис шляху, або може викликати `ImportError`. Помилка `ImportError` використовується шукачем на основі шляху, щоб сигналізувати про те, що хук не може знайти пошук запису шляху для цього запису шляху. Виняток ігнорується, і повторення шляху імпорту продовжується. Хук повинен очікувати об’єкт string або bytes; кодування об’єктів bytes залежить від хука (наприклад, це може бути кодування файлової системи, UTF-8 або щось інше), і якщо хук не може декодувати аргумент, він має викликати `ImportError`.

Якщо `sys.path_hooks` ітерація закінчується без повернення path entry finder, тоді метод пошуку на основі шляху `find_spec()` зберігатиме `None` у `sys.path_importer_cache` (щоб вказати, що немає засобу пошуку для цього запису шляху) і повернути `None`, вказуючи, що цей meta path finder не зміг знайти модуль.

Якщо path entry finder повертається одним із викликів path entry hook на `sys.path_hooks`, тоді використовується наступний протокол, щоб запитати модуль на пошуковик spec, який потім використовується під час завантаження модуля.

The current working directory – denoted by an empty string – is handled slightly differently from other entries on `sys.path`. First, if the current working directory cannot be determined or is found not to exist, no value is stored in `sys.path_importer_cache`. Second, the value for the current working directory is looked up fresh for each module lookup. Third, the path used for `sys.path_importer_cache` and returned by `importlib.machinery.PathFinder.find_spec()` will be the actual current working directory and not the empty string.

### 5.5.2. Протокол пошуку входу шляху

Для того, щоб підтримувати імпорт модулів та ініціалізованих пакетів, а також вносити частини до пакетів простору імен, засоби пошуку запису шляху повинні реалізувати метод `find_spec()`.

`find_spec()` приймає два аргументи: повне ім’я модуля, який імпортується, і (необов’язковий) цільовий модуль. `find_spec()` повертає повністю заповнену специфікацію для модуля. Ця специфікація завжди матиме встановлений «завантажувач» (за одним винятком).

To indicate to the import machinery that the spec represents a namespace portion, the path entry finder sets `submodule_search_locations` to a list containing the portion.

Змінено в версії 3.4: `find_spec()` replaced `find_loader()` and `find_module()`, both of which are now deprecated, but will be used if `find_spec()` is not defined.

Старіші засоби пошуку запису шляху можуть використовувати один із цих двох застарілих методів замість `find_spec()`. Методи досі поважаються заради зворотної сумісності. Однак, якщо `find_spec()` реалізовано в пошуку запису шляху, застарілі методи ігноруються.

`find_loader()` takes one argument, the fully qualified name of the module being imported. `find_loader()` returns a 2-tuple where the first item is the loader and the second item is a namespace portion.

Для зворотної сумісності з іншими реалізаціями протоколу імпорту багато засобів пошуку шляхів також підтримують той самий традиційний метод `find_module()`, який підтримують засоби пошуку меташляхів. Однак методи Find_module() засобу пошуку записів шляху ніколи не викликаються з аргументом `path` (вони мають записати відповідну інформацію про шлях від початкового виклику до перехоплення шляху).

Метод `find_module()` для засобів пошуку записів шляху застарів, оскільки він не дозволяє засобу пошуку записів шляху вносити частини до пакетів простору імен. Якщо і «find_loader()», і «find_module()» існують у пошуку запису шляху, система імпорту завжди викличе «find_loader()», а не «find_module()».

Змінено в версії 3.10: Calls to `find_module()` and `find_loader()` by the import system will raise `ImportWarning`.

Змінено в версії 3.12: `find_module()` and `find_loader()` have been removed.

## 5.6. Заміна стандартної системи імпорту

Найнадійнішим механізмом заміни всієї системи імпорту є видалення вмісту за замовчуванням `sys.meta_path`, повністю замінивши його спеціальним хуком мета-шляху.

Якщо прийнятно лише змінювати поведінку операторів імпорту, не впливаючи на інші API, які отримують доступ до системи імпорту, тоді заміни вбудованої функції `__import__()` може бути достатньо. Цю техніку також можна використовувати на рівні модуля лише для зміни поведінки операторів імпорту в цьому модулі.

Щоб вибірково запобігти імпорту деяких модулів із хука на ранній стадії меташляху (замість повного відключення стандартної системи імпорту), достатньо викликати `ModuleNotFoundError` безпосередньо з `find_spec()` замість повернення `None`. Останнє вказує на те, що пошук меташляху має продовжуватися, тоді як виклик винятку негайно припиняє його.

## 5.7. Відносний імпорт пакетів

Для відносного імпорту використовуються крапки на початку. Одна крапка на початку вказує на відносний імпорт, починаючи з поточного пакета. Дві або більше крапок на початку вказують на відносний імпорт до батьківського(их) пакета(ів) поточного пакета, один рівень на кожну крапку після першого. Наприклад, враховуючи такий макет пакета:

    package/
        __init__.py
        subpackage1/
            __init__.py
            moduleX.py
            moduleY.py
        subpackage2/
            __init__.py
            moduleZ.py
        moduleA.py

У `subpackage1/moduleX.py` або `subpackage1/__init__.py` наступні дійсні відносні імпорти:

    from .moduleY import spam
    from .moduleY import spam as ham
    from . import moduleY
    from ..subpackage1 import moduleY
    from ..subpackage2.moduleZ import eggs
    from ..moduleA import foo

Абсолютний імпорт може використовувати або синтаксис `import <>` або `from <> import <>`, але відносний імпорт може використовувати лише другу форму; причина цього в тому, що:

    import XXX.YYY.ZZZ

має показувати `XXX.YYY.ZZZ` як придатний вираз, але .moduleY не є дійсним виразом.

## 5.8. Особливі міркування для __main__

Модуль `__main__` є окремим випадком щодо системи імпорту Python. Як зазначено в іншому місці, модуль `__main__` безпосередньо ініціалізується під час запуску інтерпретатора, подібно до `sys` і `builtins`. Однак, на відміну від цих двох, він не кваліфікується як вбудований модуль. Це пояснюється тим, що спосіб ініціалізації `__main__` залежить від прапорів та інших параметрів, з якими викликається інтерпретатор.

### 5.8.1. __main__.__spec__

Залежно від того, як `__main__` ініціалізовано, `__main__.__spec__` встановлюється відповідним чином або має значення `None`.

Коли Python запускається з параметром `-m`, `__spec__` встановлюється на специфікацію модуля відповідного модуля або пакета. `__spec__` також заповнюється, коли модуль `__main__` завантажується як частина виконання каталогу, zip-файлу або іншого запису `sys.path`.

У решті випадків `__main__.__spec__` встановлено на `None`, оскільки код, який використовується для заповнення `__main__`, не відповідає безпосередньо імпортованому модулю:

- інтерактивна підказка

- Параметр `-c`

- працює з stdin

- працює безпосередньо з джерела або файлу байт-коду

Зауважте, що `__main__.__spec__` завжди має значення `None` в останньому випадку, навіть якщо технічно файл можна імпортувати безпосередньо як модуль. Використовуйте перемикач `-m`, якщо потрібні дійсні метадані модуля в `__main__`.

Зауважте також, що навіть якщо `__main__` відповідає імпортованому модулю, а `__main__.__spec__` встановлено відповідно, вони все одно вважаються окремими модулями. Це пов’язано з тим, що блоки, які захищаються перевірками `if __name__ == "__main__":`, виконуються лише тоді, коли модуль використовується для заповнення простору імен `__main__`, а не під час звичайного імпорту.

## 5.9. Список літератури

Механізм імпорту значно розвинувся з перших днів Python. Оригінальна специфікація пакетів все ще доступна для читання, хоча деякі деталі змінилися з моменту написання цього документа.

Початкова специфікація для `sys.meta_path` була PEP 302 з подальшим розширенням у PEP 420.

PEP 420 introduced namespace packages for Python 3.3. PEP 420 also introduced the `find_loader()` protocol as an alternative to `find_module()`.

PEP 366 описує додавання атрибута `__package__` для явного відносного імпорту в основні модулі.

PEP 328 ввів абсолютний і явний відносний імпорт і спочатку запропонував `__name__` для семантики PEP 366 згодом вказав би для `__package__`.

PEP 338 визначає модулі виконання як скрипти.

PEP 451 додає інкапсуляцію стану імпорту кожного модуля в специфікаційні об’єкти. Це також перекладає більшість шаблонних обов’язків вантажників назад на імпортну техніку. Ці зміни дозволяють припинити підтримку кількох API у системі імпорту, а також додати нові методи до засобів пошуку та завантаження.

Виноски

[1]

Див. `types.ModuleType`.

[2]

Реалізація importlib уникає безпосереднього використання значення, що повертається. Натомість він отримує об’єкт module, шукаючи ім’я модуля в `sys.modules`. Непрямим наслідком цього є те, що імпортований модуль може замінити себе в `sys.modules`. Це специфічна поведінка реалізації, яка не гарантовано працюватиме в інших реалізаціях Python.