HTML Diff
1 added 1 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Статья рассказывает об устройстве пакетов и модулей языка Python и раскрывает некоторые тонкости, о которых следует знать при работе с пакетами, моделями и их импортом.</p>
1 <p>Статья рассказывает об устройстве пакетов и модулей языка Python и раскрывает некоторые тонкости, о которых следует знать при работе с пакетами, моделями и их импортом.</p>
2 <h2>Содержание</h2>
2 <h2>Содержание</h2>
3 <ul><li><a>О чём пойдёт речь</a></li>
3 <ul><li><a>О чём пойдёт речь</a></li>
4 <li><a>Кратко о модулях</a></li>
4 <li><a>Кратко о модулях</a></li>
5 <li><a>Модули и видимость содержимого</a></li>
5 <li><a>Модули и видимость содержимого</a></li>
6 <li><a>Наконец-то, пакеты!</a></li>
6 <li><a>Наконец-то, пакеты!</a></li>
7 <li><a>Пакеты, модули и точки входа</a></li>
7 <li><a>Пакеты, модули и точки входа</a></li>
8 <li><a>PEP 420, или неявные пространства имён</a></li>
8 <li><a>PEP 420, или неявные пространства имён</a></li>
9 <li><a>Циклические импорты</a></li>
9 <li><a>Циклические импорты</a></li>
10 <li><a>Поиск пакетов и модулей</a></li>
10 <li><a>Поиск пакетов и модулей</a></li>
11 <li><a>Что не было раскрыто?</a></li>
11 <li><a>Что не было раскрыто?</a></li>
12 </ul><h2>О чём пойдёт речь</h2>
12 </ul><h2>О чём пойдёт речь</h2>
13 <p>Как вы, возможно знаете, код на Python хранится в<em>модулях (modules)</em>, которые могут быть объединены в<em>пакеты (packages)</em>. Это руководство призвано подробно рассказать именно о пакетах, однако совсем не упомянуть модули нельзя, поэтому я немного расскажу и о них. Многое из того, что применимо к модулям, справедливо и для пакетов, особенно если принять во внимание тот факт, что каждый, как правило, ведёт себя как модуль.</p>
13 <p>Как вы, возможно знаете, код на Python хранится в<em>модулях (modules)</em>, которые могут быть объединены в<em>пакеты (packages)</em>. Это руководство призвано подробно рассказать именно о пакетах, однако совсем не упомянуть модули нельзя, поэтому я немного расскажу и о них. Многое из того, что применимо к модулям, справедливо и для пакетов, особенно если принять во внимание тот факт, что каждый, как правило, ведёт себя как модуль.</p>
14 <h2>Кратко о модулях</h2>
14 <h2>Кратко о модулях</h2>
15 <p>Модуль в Python - это файл с кодом. Во время же исполнения модуль представлен соответствующим объектом, атрибутами которого являются:</p>
15 <p>Модуль в Python - это файл с кодом. Во время же исполнения модуль представлен соответствующим объектом, атрибутами которого являются:</p>
16 <ol><li>Объявления, присутствующие в файле.</li>
16 <ol><li>Объявления, присутствующие в файле.</li>
17 <li>Объекты, импортированные в этот модуль откуда-либо.</li>
17 <li>Объекты, импортированные в этот модуль откуда-либо.</li>
18 </ol><p>При этом определения и импортированные сущности ничем друг от друга не отличаются: и то, и другое - это всего лишь именованные ссылки на некоторые объекты первого класса (такие, которые могут быть переданы из одного участка кода в другой как обычные значения).</p>
18 </ol><p>При этом определения и импортированные сущности ничем друг от друга не отличаются: и то, и другое - это всего лишь именованные ссылки на некоторые объекты первого класса (такие, которые могут быть переданы из одного участка кода в другой как обычные значения).</p>
19 <p>Такое единообразие удобно, например, при рефакторинге: мы можем разделить один разросшийся модуль на несколько, а потом импортировать вынесенные определения в оригинальный модуль. При этом с точки зрения внешнего наблюдателя переработанный модуль будет иметь те же атрибуты, которые имел до внесения изменений, а значит у пользователей модуля ничего в коде не сломается.</p>
19 <p>Такое единообразие удобно, например, при рефакторинге: мы можем разделить один разросшийся модуль на несколько, а потом импортировать вынесенные определения в оригинальный модуль. При этом с точки зрения внешнего наблюдателя переработанный модуль будет иметь те же атрибуты, которые имел до внесения изменений, а значит у пользователей модуля ничего в коде не сломается.</p>
20 <h2>Модули и видимость содержимого</h2>
20 <h2>Модули и видимость содержимого</h2>
21 <p>В Python нет настоящего сокрытия атрибутов объектов, поэтому и атрибуты объекта модуля так или иначе всегда доступны после импорта последнего. Однако существует ряд соглашений, которые влияют на процесс импортирования и поведение инструментов, работающих с кодом.</p>
21 <p>В Python нет настоящего сокрытия атрибутов объектов, поэтому и атрибуты объекта модуля так или иначе всегда доступны после импорта последнего. Однако существует ряд соглашений, которые влияют на процесс импортирования и поведение инструментов, работающих с кодом.</p>
22 <p>Так атрибуты, имя которых начинается с одиночного подчёркивания, считаются как бы помеченными "для внутреннего использования", и обычно не отображаются в IDE при обращению к объекту "через точку". И linter обычно предупреждает об использовании таких атрибутов, мол, "небезопасно!". "Опасность" состоит в том, что автор кода имеет полное право<strong>изменять состав таких атрибутов без уведомления пользователей</strong>кода. Поэтому программист, использовавший в своём коде приватные части чужого кода рискует в какой-то момент получить код, который перестанет работать при обновлении сторонней библиотеки.</p>
22 <p>Так атрибуты, имя которых начинается с одиночного подчёркивания, считаются как бы помеченными "для внутреннего использования", и обычно не отображаются в IDE при обращению к объекту "через точку". И linter обычно предупреждает об использовании таких атрибутов, мол, "небезопасно!". "Опасность" состоит в том, что автор кода имеет полное право<strong>изменять состав таких атрибутов без уведомления пользователей</strong>кода. Поэтому программист, использовавший в своём коде приватные части чужого кода рискует в какой-то момент получить код, который перестанет работать при обновлении сторонней библиотеки.</p>
23 <p>Итак, мы можем определять<em>публичные атрибуты</em>модуля,<em>приватные атрибуты</em>(так называют упомянутые выше атрибуты "для внутреннего пользования"). И данное разделение касается не только определений, содержащихся в самом модуле, но и импортируемых сущностей. Ведь все<strong>импортированные объекты становятся атрибутами и того модуля, в который они импортированы</strong>.</p>
23 <p>Итак, мы можем определять<em>публичные атрибуты</em>модуля,<em>приватные атрибуты</em>(так называют упомянутые выше атрибуты "для внутреннего пользования"). И данное разделение касается не только определений, содержащихся в самом модуле, но и импортируемых сущностей. Ведь все<strong>импортированные объекты становятся атрибутами и того модуля, в который они импортированы</strong>.</p>
24 <p>Есть и третья группа атрибутов - атрибуты, добавляемые в область видимости при импортировании всего содержимого модуля ("со звёздочкой", from module import *). Если ничего явно не указывать, то при таком импортировании в текущую область видимости добавятся<strong>все публичные атрибуты модуля</strong>. Помимо данного умолчания существует и возможность явно указать, что конкретно будет экспортировано при импорте со звёздочкой. Для управления названным методом импорта существует атрибут __all__, в который можно положить список (а ещё лучше - кортеж)<strong>строк с именами, которые будут экспортироваться</strong>.</p>
24 <p>Есть и третья группа атрибутов - атрибуты, добавляемые в область видимости при импортировании всего содержимого модуля ("со звёздочкой", from module import *). Если ничего явно не указывать, то при таком импортировании в текущую область видимости добавятся<strong>все публичные атрибуты модуля</strong>. Помимо данного умолчания существует и возможность явно указать, что конкретно будет экспортировано при импорте со звёздочкой. Для управления названным методом импорта существует атрибут __all__, в который можно положить список (а ещё лучше - кортеж)<strong>строк с именами, которые будут экспортироваться</strong>.</p>
25 <h3>Живой пример видимости атрибутов модулей.</h3>
25 <h3>Живой пример видимости атрибутов модулей.</h3>
26 <p>Рассмотрим пример, демонстрирующий всё вышеописанное. Пусть у нас будет два файла:</p>
26 <p>Рассмотрим пример, демонстрирующий всё вышеописанное. Пусть у нас будет два файла:</p>
27 <p>Рассмотрим сначала обычный импорт import module. Если импортировать модуль таким образом, то IDE, REPL и остальные инструменты "увидят" у модуля следующие атрибуты:</p>
27 <p>Рассмотрим сначала обычный импорт import module. Если импортировать модуль таким образом, то IDE, REPL и остальные инструменты "увидят" у модуля следующие атрибуты:</p>
28 <ul><li>FISH, MEAT т.к. имена констант - публичные,</li>
28 <ul><li>FISH, MEAT т.к. имена констант - публичные,</li>
29 <li>CAT, т.к. константа импортирована под публичным именем.</li>
29 <li>CAT, т.к. константа импортирована под публичным именем.</li>
30 </ul><p>А эти атрибуты<strong>не</strong>будут видны:</p>
30 </ul><p>А эти атрибуты<strong>не</strong>будут видны:</p>
31 <ul><li>_DOG, т.к. при импортировании константа переименована в приватной манере,</li>
31 <ul><li>_DOG, т.к. при импортировании константа переименована в приватной манере,</li>
32 <li>_GOAT, т.к. импортирована по своему приватному имени (тут линтер может и поругать за обращение к приватному атрибуту модуля!),</li>
32 <li>_GOAT, т.к. импортирована по своему приватному имени (тут линтер может и поругать за обращение к приватному атрибуту модуля!),</li>
33 <li>_CARROT, ибо приватная константа.</li>
33 <li>_CARROT, ибо приватная константа.</li>
34 </ul><blockquote><p>Импорт import other_module я не рассматриваю как тривиальный случай.</p>
34 </ul><blockquote><p>Импорт import other_module я не рассматриваю как тривиальный случай.</p>
35 </blockquote><p>Теперь рассмотрим импорт<strong>всего содержимого module</strong>:</p>
35 </blockquote><p>Теперь рассмотрим импорт<strong>всего содержимого module</strong>:</p>
36 <p>После импортирования<strong>в текущей области видимости</strong>мы получим<strong>одно новое имя</strong>: _CARROT - именно оно перечислено в атрибуте __all__. Заметьте, что в данном случае при массовом импорте добавится даже приватный атрибут, потому что он явно указан!</p>
36 <p>После импортирования<strong>в текущей области видимости</strong>мы получим<strong>одно новое имя</strong>: _CARROT - именно оно перечислено в атрибуте __all__. Заметьте, что в данном случае при массовом импорте добавится даже приватный атрибут, потому что он явно указан!</p>
37 <blockquote><p>Последствия импорта from other_module import * тоже очевидны и я их не рассматриваю.</p>
37 <blockquote><p>Последствия импорта from other_module import * тоже очевидны и я их не рассматриваю.</p>
38 </blockquote><h2>Наконец-то, пакеты!</h2>
38 </blockquote><h2>Наконец-то, пакеты!</h2>
39 <p>Пакет в Python - директория с обязательным модулем __init__.py. Остальное содержимое опционально и может включать в себя и модули, и другие пакеты.</p>
39 <p>Пакет в Python - директория с обязательным модулем __init__.py. Остальное содержимое опционально и может включать в себя и модули, и другие пакеты.</p>
40 <h3>Импортирование пакетов</h3>
40 <h3>Импортирование пакетов</h3>
41 <p>Пакет с единственным модулем __init__.py при импорте ведёт себя как обычный модуль. Содержимое<em>инициализирующего модуля</em>определяет атрибуты объекта пакета.</p>
41 <p>Пакет с единственным модулем __init__.py при импорте ведёт себя как обычный модуль. Содержимое<em>инициализирующего модуля</em>определяет атрибуты объекта пакета.</p>
42 <p>Прочие модули пакета и вложенные пакеты не импортируются автоматически вместе с пакетом-родителем, но могут быть импортированы отдельно с указанием<strong>полного имени</strong>. Важный момент: при импортировании вложенного модуля всегда сначала<strong>импортируются модули инициализации всех родительских пакетов</strong>(если они ещё ни разу не импортировались, но об этом я расскажу ниже).</p>
42 <p>Прочие модули пакета и вложенные пакеты не импортируются автоматически вместе с пакетом-родителем, но могут быть импортированы отдельно с указанием<strong>полного имени</strong>. Важный момент: при импортировании вложенного модуля всегда сначала<strong>импортируются модули инициализации всех родительских пакетов</strong>(если они ещё ни разу не импортировались, но об этом я расскажу ниже).</p>
43 <p>Рассмотрим, к примеру, следующую структуру директорий и файлов:</p>
43 <p>Рассмотрим, к примеру, следующую структуру директорий и файлов:</p>
44 <p>Когда мы импортируем модуль submodule.py, то фактически происходит следующее (именно в таком порядке):</p>
44 <p>Когда мы импортируем модуль submodule.py, то фактически происходит следующее (именно в таком порядке):</p>
45 <ol><li>загружается и выполняется модуль package/__init__.py,</li>
45 <ol><li>загружается и выполняется модуль package/__init__.py,</li>
46 <li>загружается и выполняется package/subpackage/__init__.py,</li>
46 <li>загружается и выполняется package/subpackage/__init__.py,</li>
47 <li>наконец, импортируется package/subpackage/submodule.py.</li>
47 <li>наконец, импортируется package/subpackage/submodule.py.</li>
48 </ol><blockquote><p>При импорте package.module предварительно загружается только package/__init__.py.</p>
48 </ol><blockquote><p>При импорте package.module предварительно загружается только package/__init__.py.</p>
49 </blockquote><p>Так что же, если мы загрузим парочку вложенных модулей, то для каждого будет выполняться загрузка всех __init__.py по дороге? Не будет! Подсистема интерпретатора, отвечающая за загрузку модулей, кэширует уже загруженные пакеты и модули.<strong>Каждый конкретный модуль загружается ровно один раз</strong>, в том числе и инициализирующие модули __init__.py (короткие имена модулей хоть и одинаковы, но полные имена всегда разные). Все последующие импортирования модуля не приводят к его загрузке, только лишь нужные атрибуты копируются в соответствующие области видимости.</p>
49 </blockquote><p>Так что же, если мы загрузим парочку вложенных модулей, то для каждого будет выполняться загрузка всех __init__.py по дороге? Не будет! Подсистема интерпретатора, отвечающая за загрузку модулей, кэширует уже загруженные пакеты и модули.<strong>Каждый конкретный модуль загружается ровно один раз</strong>, в том числе и инициализирующие модули __init__.py (короткие имена модулей хоть и одинаковы, но полные имена всегда разные). Все последующие импортирования модуля не приводят к его загрузке, только лишь нужные атрибуты копируются в соответствующие области видимости.</p>
50 <h3>Пакеты и __all__</h3>
50 <h3>Пакеты и __all__</h3>
51 - <p>В целом атрибут __all__ в модуле инициализации пакета ведёт себя так же, как и в случае с обычным модулем. Н если при импорте пакета "со звёздочкой" среди перечисленных имён встретится имя вложенного модуля, а сам модуль не окажется импортирован ранее в этом же __init__.py, то этот<strong>модуль импортируется неявно</strong>! Очередной пример это продемонстрирует.</p>
51 + <p>В целом атрибут __all__ в модуле инициализации пакета ведёт себя так же, как и в случае с обычным модулем. Но если при импорте пакета "со звёздочкой" среди перечисленных имён встретится имя вложенного модуля, а сам модуль не окажется импортирован ранее в этом же __init__.py, то этот<strong>модуль импортируется неявно</strong>! Очередной пример это продемонстрирует.</p>
52 <p>Вот структура пакета:</p>
52 <p>Вот структура пакета:</p>
53 <p>Файл же package/__init__.py содержит следующее (и только это!):</p>
53 <p>Файл же package/__init__.py содержит следующее (и только это!):</p>
54 <p>А импортируем мы from package import *. В области видимости у нас окажутся объекты модулей a и b под своими именами (без полного пути, то есть без package.). При этом сами модули в коде нигде явно не импортируются! Такая вот "автомагия".</p>
54 <p>А импортируем мы from package import *. В области видимости у нас окажутся объекты модулей a и b под своими именами (без полного пути, то есть без package.). При этом сами модули в коде нигде явно не импортируются! Такая вот "автомагия".</p>
55 <p>Указанный автоматизм достаточно ограничен: не работает "вглубь", например - не импортирует "через звёздочку" указанные модули и подпакеты. Если же вам вдруг такого захочется, вы всегда сможете на соответствующих уровнях в __init__.py сделать from x import * и получить в корневом пакете плоскую область видимости со всем нужным содержимым. Но такое нужно довольно редко, потому что "не помогает" ни IDE, ни ручному поиску по коду. Впрочем, знать о фиче и иметь её в виду - не вредно, как мне кажется.</p>
55 <p>Указанный автоматизм достаточно ограничен: не работает "вглубь", например - не импортирует "через звёздочку" указанные модули и подпакеты. Если же вам вдруг такого захочется, вы всегда сможете на соответствующих уровнях в __init__.py сделать from x import * и получить в корневом пакете плоскую область видимости со всем нужным содержимым. Но такое нужно довольно редко, потому что "не помогает" ни IDE, ни ручному поиску по коду. Впрочем, знать о фиче и иметь её в виду - не вредно, как мне кажется.</p>
56 <h2>Пакеты, модули и точки входа</h2>
56 <h2>Пакеты, модули и точки входа</h2>
57 <p>С модулем __init__.py разобрались. Настала очередь модуля __main__.py. Этот модуль позволяет сделать пакет исполняемым посредством вызова python -m …. Те из вас, кому знакомо оформление точки входа в модулях, могут догадаться, откуда ноги растут у магического выражения __name__ == '__main__' - да, отсюда! Для остальных напоминаю: чтобы модуль сделать "исполняемым, но не при импорте", в конец модуля дописывается конструкция</p>
57 <p>С модулем __init__.py разобрались. Настала очередь модуля __main__.py. Этот модуль позволяет сделать пакет исполняемым посредством вызова python -m …. Те из вас, кому знакомо оформление точки входа в модулях, могут догадаться, откуда ноги растут у магического выражения __name__ == '__main__' - да, отсюда! Для остальных напоминаю: чтобы модуль сделать "исполняемым, но не при импорте", в конец модуля дописывается конструкция</p>
58 <p>У модуля, который скармливается интерпретатору напрямую (python file.py) или в роли претендента на запуск (python -m module), атрибут __name__ будет содержать то самое магическое '__main__'. А в остальное время атрибут содержит полное имя модуля. С помощью условия, показанного выше, модуль может решить, что делать при запуске.</p>
58 <p>У модуля, который скармливается интерпретатору напрямую (python file.py) или в роли претендента на запуск (python -m module), атрибут __name__ будет содержать то самое магическое '__main__'. А в остальное время атрибут содержит полное имя модуля. С помощью условия, показанного выше, модуль может решить, что делать при запуске.</p>
59 <p>У пакетов роль атрибута выполняет специальный файл __main__.py. Когда мы запустим пакет через python path/to/package или python -m package, интерпретатор будет искать и выполнять именно этот файл.</p>
59 <p>У пакетов роль атрибута выполняет специальный файл __main__.py. Когда мы запустим пакет через python path/to/package или python -m package, интерпретатор будет искать и выполнять именно этот файл.</p>
60 <p>Более того, модули __main__ нельзя импортировать обычным способом, поэтому можно не бояться случайного импорта и писать команды прямо на верхнем уровне: всё равно странно в модуле с именем __main__ проверять, что его имя равно __main__ (хе-хе!).</p>
60 <p>Более того, модули __main__ нельзя импортировать обычным способом, поэтому можно не бояться случайного импорта и писать команды прямо на верхнем уровне: всё равно странно в модуле с именем __main__ проверять, что его имя равно __main__ (хе-хе!).</p>
61 <p>А ещё модуль __main__.py удобен тем, что его можно класть в корень вашего проекта, после чего запускать проект можно будет с помощью команды python .! Лаконично, не правда ли?</p>
61 <p>А ещё модуль __main__.py удобен тем, что его можно класть в корень вашего проекта, после чего запускать проект можно будет с помощью команды python .! Лаконично, не правда ли?</p>
62 <h2>PEP 420, или неявные пространства имён</h2>
62 <h2>PEP 420, или неявные пространства имён</h2>
63 <p>Раз уж развёл ликбез, расскажу и про эту штуку.</p>
63 <p>Раз уж развёл ликбез, расскажу и про эту штуку.</p>
64 <p>Долгое время в Python пакеты были обязаны иметь файл __init__.py - наличие этого файла позволяло отличить пакет от обычной директории с модулями (с которыми Python работать не мог). Но с версии Python3.3 вступил в силу<a>PEP 420</a>, позволяющий создавать пространства имён "на вырост".</p>
64 <p>Долгое время в Python пакеты были обязаны иметь файл __init__.py - наличие этого файла позволяло отличить пакет от обычной директории с модулями (с которыми Python работать не мог). Но с версии Python3.3 вступил в силу<a>PEP 420</a>, позволяющий создавать пространства имён "на вырост".</p>
65 <p>Теперь вы можете создавать пакет без __init__.py, и такой пакет сможет существовать полноценно, разве что при импорте содержимого не будет производиться инициализация. Но, конечно же, данное изменение делалось не с целью сэкономить на файлах. Подобные пакеты могут встречаться в путях поиска пакетов (о поиске пакетов я ниже расскажу) более одного раза: все встреченные структуры с общим корневым именем при загрузке схлопнутся в одно пространство имён.</p>
65 <p>Теперь вы можете создавать пакет без __init__.py, и такой пакет сможет существовать полноценно, разве что при импорте содержимого не будет производиться инициализация. Но, конечно же, данное изменение делалось не с целью сэкономить на файлах. Подобные пакеты могут встречаться в путях поиска пакетов (о поиске пакетов я ниже расскажу) более одного раза: все встреченные структуры с общим корневым именем при загрузке схлопнутся в одно пространство имён.</p>
66 <p>Тут стоит отметить, что с полноценными пакетами подобное не срабатывало ранее и не будет работать в будущем. Если среди путей пакет с модулем инициализации находится в первый раз, все последующие пакеты с тем же именем будут проигнорированы. Это защищает вас от смешивания сторонних пакетов с системными. И даже просто от ошибок именования: назвав пакет так же, как называется встроенный пакет или модуль, вы получите ошибку - ваши определения не будут импортироваться.</p>
66 <p>Тут стоит отметить, что с полноценными пакетами подобное не срабатывало ранее и не будет работать в будущем. Если среди путей пакет с модулем инициализации находится в первый раз, все последующие пакеты с тем же именем будут проигнорированы. Это защищает вас от смешивания сторонних пакетов с системными. И даже просто от ошибок именования: назвав пакет так же, как называется встроенный пакет или модуль, вы получите ошибку - ваши определения не будут импортироваться.</p>
67 <p><em>Пакеты - пространства имён (Namespace Packages, NP)</em>- а именно так называются пакеты без инициализации - не могут объединяться с полноценными пакетами, поэтому добавить что-то в системный пакет вам также не удастся. И тут всё защищено!</p>
67 <p><em>Пакеты - пространства имён (Namespace Packages, NP)</em>- а именно так называются пакеты без инициализации - не могут объединяться с полноценными пакетами, поэтому добавить что-то в системный пакет вам также не удастся. И тут всё защищено!</p>
68 <p>Какая же польза от неявных пространств имён? А вы представьте себя авторами, скажем, игрового движка. Вы хотите весь код держать в общем пространстве имён engine, но при этом не желаете, чтобы весь код поставлялся одним дистрибутивом (не каждому же пользователю нужны все-все компоненты движка). С NP вы можете в нескольких дистрибутивах использовать<strong>общее корневое имя</strong>engine, но разные подпакеты и подмодули. А на выходе вы получите возможность делать импорты вида</p>
68 <p>Какая же польза от неявных пространств имён? А вы представьте себя авторами, скажем, игрового движка. Вы хотите весь код держать в общем пространстве имён engine, но при этом не желаете, чтобы весь код поставлялся одним дистрибутивом (не каждому же пользователю нужны все-все компоненты движка). С NP вы можете в нескольких дистрибутивах использовать<strong>общее корневое имя</strong>engine, но разные подпакеты и подмодули. А на выходе вы получите возможность делать импорты вида</p>
69 <p><strong>Важно</strong>: помните, если встретятся обычный пакет и NP с одинаковым именем, то<strong>победит</strong>обычный пакет! А NP, сколько бы их не было, не будут загружены!</p>
69 <p><strong>Важно</strong>: помните, если встретятся обычный пакет и NP с одинаковым именем, то<strong>победит</strong>обычный пакет! А NP, сколько бы их не было, не будут загружены!</p>
70 <h2>Циклические импорты</h2>
70 <h2>Циклические импорты</h2>
71 <p>Если вдруг вы захотите в один модуль импортировать другой, а другой захочет, пусть даже и не напрямую, импортировать первый, то вы получите ImportError. Потому что у вас случится циклический импорт. Про него нужно просто знать и стараться архитектурить код так, чтобы циклов не случалось.</p>
71 <p>Если вдруг вы захотите в один модуль импортировать другой, а другой захочет, пусть даже и не напрямую, импортировать первый, то вы получите ImportError. Потому что у вас случится циклический импорт. Про него нужно просто знать и стараться архитектурить код так, чтобы циклов не случалось.</p>
72 <p>Если же приспичивает, и импортировать что-то "ну очень нужно", то можно попробовать обойтись локальным импортом:</p>
72 <p>Если же приспичивает, и импортировать что-то "ну очень нужно", то можно попробовать обойтись локальным импортом:</p>
73 <p>Да, это костыль. Но иногда полезный. В идеале - до ближайшего большого рефакторинга. Поэтому настраивайте linter на ловлю локальных импортов и стремитесь убирать такие костыли хоть когда-нибудь!</p>
73 <p>Да, это костыль. Но иногда полезный. В идеале - до ближайшего большого рефакторинга. Поэтому настраивайте linter на ловлю локальных импортов и стремитесь убирать такие костыли хоть когда-нибудь!</p>
74 <h2>Поиск пакетов и модулей</h2>
74 <h2>Поиск пакетов и модулей</h2>
75 <p>Пайтон ищет модули и пакеты в директориях, во время исполнения перечисленных в списке sys.path - по порядку от первого пути к последнему.</p>
75 <p>Пайтон ищет модули и пакеты в директориях, во время исполнения перечисленных в списке sys.path - по порядку от первого пути к последнему.</p>
76 <p>В этом списке пути до стандартных библиотек обычно расположены раньше, чем директории со сторонними пакетами, чтобы нельзя было случайно заменить стандартный пакет сторонним (помним: кто первый, того и тапки - среди нескольких с одинаковыми именами загружается первый попавшийся пакет).</p>
76 <p>В этом списке пути до стандартных библиотек обычно расположены раньше, чем директории со сторонними пакетами, чтобы нельзя было случайно заменить стандартный пакет сторонним (помним: кто первый, того и тапки - среди нескольких с одинаковыми именами загружается первый попавшийся пакет).</p>
77 <p>В списке путей (обычно в начале) присутствует и путь '', означающий текущую директорию. Это, в свою очередь, означает, что модули и пакет в текущем проекте имеют больший приоритет.</p>
77 <p>В списке путей (обычно в начале) присутствует и путь '', означающий текущую директорию. Это, в свою очередь, означает, что модули и пакет в текущем проекте имеют больший приоритет.</p>
78 <p>Обычно пути трогать не нужно, всё вполне нормально "работает само". Но если очень хочется, то путей у вас несколько:</p>
78 <p>Обычно пути трогать не нужно, всё вполне нормально "работает само". Но если очень хочется, то путей у вас несколько:</p>
79 <ol><li>Использовать переменную окружения PYTHONPATH (значение - строка с путями, разделёнными символом :),</li>
79 <ol><li>Использовать переменную окружения PYTHONPATH (значение - строка с путями, разделёнными символом :),</li>
80 <li>Во время исполнения изменить sys.path.</li>
80 <li>Во время исполнения изменить sys.path.</li>
81 </ol><p>Первый способ - простой и понятный. Не сложнее добавления пути до исполняемых файлов в PATH (даже синтаксис тот же).</p>
81 </ol><p>Первый способ - простой и понятный. Не сложнее добавления пути до исполняемых файлов в PATH (даже синтаксис тот же).</p>
82 <p>Второй способ - сложный и требующий внимательности. Дело в том, что sys.path нужно изменять максимально рано - где-нибудь в точке входа. Если не торопиться менять sys.path, то что-то уже может успеть загрузиться до того, как вы перестроите пути для поиска пакетов. А ведь эта загрузка может произойти в другом потоке исполнения! Отлаживать проблемы с очерёдностью загрузки модулей сложно. Лучше просто их не создавать.</p>
82 <p>Второй способ - сложный и требующий внимательности. Дело в том, что sys.path нужно изменять максимально рано - где-нибудь в точке входа. Если не торопиться менять sys.path, то что-то уже может успеть загрузиться до того, как вы перестроите пути для поиска пакетов. А ведь эта загрузка может произойти в другом потоке исполнения! Отлаживать проблемы с очерёдностью загрузки модулей сложно. Лучше просто их не создавать.</p>
83 <p>Кстати, когда вы используете виртуальные окружения, sys.path будет содержать пути до локальных копий стандартных библиотек. Именно это позволяет виртуальному окружению быть самодостаточным (работать на любой машине с подходящей ОС - даже без установленного в систему Python!).</p>
83 <p>Кстати, когда вы используете виртуальные окружения, sys.path будет содержать пути до локальных копий стандартных библиотек. Именно это позволяет виртуальному окружению быть самодостаточным (работать на любой машине с подходящей ОС - даже без установленного в систему Python!).</p>
84 <h2>Что не было раскрыто?</h2>
84 <h2>Что не было раскрыто?</h2>
85 <p>Я специально не стал рассказывать про</p>
85 <p>Я специально не стал рассказывать про</p>
86 <ul><li>создание модулей и пакетов на лету (без использования файлов исходников);</li>
86 <ul><li>создание модулей и пакетов на лету (без использования файлов исходников);</li>
87 <li>загрузку модулей не с диска, а из других источников;</li>
87 <li>загрузку модулей не с диска, а из других источников;</li>
88 <li>расширение подсистемы импортирования с целью загрузки в виде объектов-модулей чего-то, не являющегося кодом вовсе (XML, CSV, JSON).</li>
88 <li>расширение подсистемы импортирования с целью загрузки в виде объектов-модулей чего-то, не являющегося кодом вовсе (XML, CSV, JSON).</li>
89 </ul><p>Темы эти насколько интересны, настолько и велики. На наше счастье, самим разбираться в такой тонкой и сложной машинерии приходится редко. Мы просто пользуемся готовыми магическими артефактами, а зачаровывают их другие :) Если же вы захотите научиться магии, документация вам в руки.</p>
89 </ul><p>Темы эти насколько интересны, настолько и велики. На наше счастье, самим разбираться в такой тонкой и сложной машинерии приходится редко. Мы просто пользуемся готовыми магическими артефактами, а зачаровывают их другие :) Если же вы захотите научиться магии, документация вам в руки.</p>