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. Меташлях

Якщо вказаний модуль не знайдено в sys.modules, Python далі шукає sys.meta_path, який містить список об’єктів пошуку мета-шляху. Ці шукачі запитуються, щоб побачити, чи знають вони, як працювати з названим модулем. Шукачі меташляхів повинні реалізувати метод під назвою find_spec(), який приймає три аргументи: ім’я, шлях імпорту та (необов’язково) цільовий модуль. Шукач меташляху може використовувати будь-яку стратегію, щоб визначити, чи може він обробляти названий модуль чи ні.

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

Метод find_spec() засобів пошуку меташляху викликається з двома або трьома аргументами. Перше — це повна назва модуля, який імпортується, наприклад foo.bar.baz. Другим аргументом є записи шляху, які слід використовувати для пошуку модулів. Для модулів верхнього рівня другим аргументом є None, але для підмодулів або підпакетів другим аргументом є значення атрибута __path__ батьківського пакета. Якщо неможливо отримати доступ до відповідного атрибута __path__, виникає ModuleNotFoundError. Третій аргумент — це існуючий об’єкт модуля, який буде метою завантаження пізніше. Система імпорту передає цільовий модуль лише під час перезавантаження.

Мета-шлях може проходити кілька разів для одного запиту на імпорт. Наприклад, якщо припустити, що жоден із задіяних модулів уже не кешовано, імпорт 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. Специфікація модуля

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

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

Специфікація модуля представлена як атрибут __spec__ в об’єкті модуля. Дивіться ModuleSpec, щоб дізнатися більше про вміст специфікації модуля.

Added in version 3.4.

5.4.5. module.__path__

За визначенням, якщо модуль має атрибут __path__, це пакет.

Атрибут __path__ пакета використовується під час імпорту його підпакетів. У механізмі імпорту він функціонує майже так само, як sys.path, тобто надає список місць для пошуку модулів під час імпорту. Однак __path__ зазвичай набагато більш обмежений, ніж sys.path.

__path__ має бути повторюваним рядком, але він може бути порожнім. Ті самі правила, що використовуються для sys.path, також застосовуються до __path__ пакета, а sys.path_hooks (описані нижче) звертаються під час проходження __path__ пакета.

Файл __init__.py пакета може встановлювати або змінювати атрибут __path__ пакета, і це був типовий спосіб реалізації пакетів простору імен до PEP 420. Із застосуванням PEP 420 пакетам простору імен більше не потрібно надавати файли __init__.py, що містять лише __path__ код маніпуляції; механізм імпорту автоматично встановлює __path__ правильно для пакета простору імен.

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

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

Якщо модуль має специфікацію (__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.7. Анулювання кешованого байт-коду

Перед тим, як 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, який потім використовується під час завантаження модуля.

Поточний робочий каталог, позначений порожнім рядком, обробляється дещо інакше, ніж інші записи в sys.path. По-перше, якщо поточний робочий каталог не існує, жодне значення не зберігається в sys.path_importer_cache. По-друге, значення для поточного робочого каталогу оновлюється для кожного пошуку модуля. По-третє, шлях, який використовується для sys.path_importer_cache і повертається importlib.machinery.PathFinder.find_spec(), буде фактичним поточним робочим каталогом, а не порожнім рядком.

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 у системі імпорту, а також додати нові методи до засобів пошуку та завантаження.

Виноски