Почему неявный импорт со звездочкой не рекомендуют использовать в коде приложения python
Не могу разобраться в системе импортов. Почему когда я делаю:
то мне доступен os.walk , который находится в os , хотя я явно не импортирую os ? Почему-то со своими package такой финт не прокатывает. За счет чего это происходит?
Я понимаю, что импорт вносит импортируемое в пространство имен модуля. поэтому определив модуль 'myutils.py' c
в другом модуле получается следущая картина
Есть какая то особенность в механизме import os в os.path? Я понимаю, что os.path есть часть os, а не 2 абсолютно отдельных package, но хочу точно понять механизм.
3,450 1 1 золотой знак 14 14 серебряных знаков 23 23 бронзовых знака- найти (загрузить и проинициализировать) модуль
- ввести новые имена в текущем окружении (как операция присваивания = )
This name will be used in various phases of the import search, and it may be the dotted path to a submodule, e.g. foo.bar.baz . In this case, Python first tries to import foo , then foo.bar , and finally foo.bar.baz . If any of the intermediate imports fail, an ImportError is raised.
то есть import a.b.c импортирует модули a , a.b , a.b.c и более вложенный модуль не может быть импортирован успешно, если импорт неудачен для любого модуля выше.
If the module being imported is not a top level module, then the name of the top level package that contains the module is bound in the local namespace as a reference to the top level package. The imported module must be accessed using its full qualified name rather than directly
import a.b.c в Питоне делает доступным a имя в текущем пространстве имён (например, в глобальном пространстве имён модуля, который импортирует a.b.c ) и присваивает атрибут: a.b и атрибут атрибута: a.b.c соответствующим загруженным модулям (аналог: a = sys.modules['a'] ; a.b = sys.modules['a.b'] ; ..).
Хотя вопрос с подводхом, потому что os не является Питон-пакетом ( __path__ атрибут не установлен) и os.path является обычным атрибутом (таким же как os.walk ) с той разницей, что os.path является модулем и os.py содержит хак: sys.modules['os.path'] = path (что разрешает import os.path конструкцию). import os сам по себе уже делает os.path доступным без import os.path .
Перефразируя вопрос, используя более регулярный пример:
Почему когда я делаю:
import os.pathimport html.parser то мне доступенos.walkhtml.escape , который находится вoshtml , хотя я явно не импортируюoshtml ?
import html.parser импортирует как html так и (естественно) html.parser модули и так как html/__init__.py (выполняемый на этапе импорта html ) определяет escape функцию, то html.escape() также доступна как если бы мы просто выполнили import html .
Почему-то со своими package такой финт не прокатывает. За счет чего это происходит?
Я знаю, что использовать import * в python плохо, и я не собираюсь делать это привычкой. Однако недавно я столкнулся с каким-то странным поведением, которое я не понимаю, и подумал, может ли кто-нибудь мне это объяснить.
Допустим, у меня есть три сценария Python. Первый, first_script.py , содержит:
Поведение (печатает this is from the second script ) имеет смысл для меня. Я импортировал first_script.py , но перезаписал переменную в ее пространстве имен, поэтому, когда я вызываю print_message() , я получаю новое содержимое этой переменной.
Тем не менее, у меня также есть third_script.py , включающий:
Эта первая строка понятна, но вторая не имеет смысла для меня. Моя интуиция заключалась в том, что, поскольку я импортировал в основное пространство имен с помощью * в первой строке, у меня есть глобальная переменная называется MESSAGES . Затем во второй строке я перезаписываю MESSAGES . Почему тогда функция (импортированная из первого скрипта) выдает СТАРЫЙ вывод, особенно учитывая вывод second_script.py . Любые идеи?
2 ответа
Прямое присвоение изменяет ссылку на объект, а модификация - нет. Например,
Печатает два разных идентификатора, но
Печатает тот же идентификатор.
В second_script.py присвоение просто изменяет first_script , поэтому и first_script.py , и second_script.py могут определять один и тот же атрибут MESSAGE для first_script . В third_script.py прямое присвоение изменяет ссылку на MESSAGE ; поэтому после присвоения переменная MESSAGE в third_script.py является переменной, отличной от MESSAGE в first_script.py .
Рассмотрим связанный пример:
first_script.py
third_script.py
import module , from module import smth и from module import * могут иметь разные варианты использования.
Проще:
Загружает модуль tools и добавляет ссылку на него в локальное пространство имен (также называемое tools ). После этого вы можете получить доступ к любым ссылкам на инструменты, добавив к ним tools , например, tools.var1
Делает то же самое, но вы используете псевдоним для доступа к ссылкам из модуля (например: sloot.var1 ). Он в основном используется для модулей с хорошо известными псевдонимами, такими как import numpy as np .
Другой способ
Напрямую импортирует некоторые символы из модуля tools в текущем пространстве имен. Это означает, что вы можете использовать только указанные символы, поскольку они не нуждаются в квалификации. Хороший вариант использования - это когда вы можете импортировать символ из разных модулей с одинаковыми функциями. Например
Это обычно используется как трюк переносимости.
Это обычная идиома, но она может не выполнять то, что вы ожидаете, если модуль не документирует это. Фактически, он импортирует все открытые символы из модуля, по умолчанию все символы, которые не имеют начальных _ , которые могут содержать нежелательные вещи. Кроме того, модуль может объявить специальную переменную __all__ , которая, как предполагается, объявляет открытый интерфейс, и в этом случае будут импортированы только символы, содержащиеся в __all__ .
Вы можете использовать (при условии, что mod.py доступен)
Поэтому вы должны использовать from tools import * только в том случае, если документация модуля tools объявляет его безопасным и содержит список фактически импортированных символов.
Порой бывает трудно правильно реализовать import с первого раза, особенно если мы хотим добиться правильной работы на плохо совместимых между собой версиях Python 2 и Python 3. Попытаемся разобраться, что из себя представляют импорты в Python и как написать решение, которое подойдёт под обе версии языка.
Содержание
Ключевые моменты
- Выражения import производят поиск по списку путей в sys.path .
- sys.path всегда включает в себя путь скрипта, запущенного из командной строки, и не зависит от текущей рабочей директории.
- Импортирование пакета по сути равноценно импортированию __init__.py этого пакета.
Основные определения
- Модуль: любой файл *.py . Имя модуля — имя этого файла.
- Встроенный модуль: «модуль», который был написан на Си, скомпилирован и встроен в интерпретатор Python, и потому не имеет файла *.py .
- Пакет: любая папка, которая содержит файл __init__.py . Имя пакета — имя папки.
- С версии Python 3.3 любая папка (даже без __init__.py ) считается пакетом.
Пример структуры директорий
Обратите внимание, что в корневой папке test/ нет файла __init__.py .
Что делает import
При импорте модуля Python выполняет весь код в нём. При импорте пакета Python выполняет код в файле пакета __init__.py , если такой имеется. Все объекты, определённые в модуле или __init__.py , становятся доступны импортирующему.
Встроенные функции Python: какие нужно знать и на какие не стоит тратить времяОсновы import и sys.path
Вот как оператор import производит поиск нужного модуля или пакета согласно документации Python:
- директории, содержащей исходный скрипт (или текущей директории, если файл не указан);
- директории по умолчанию, которая зависит от дистрибутива Python;
- PYTHONPATH (список имён директорий; имеет синтаксис, аналогичный переменной окружения PATH ).
Технически документация не совсем полна. Интерпретатор будет искать не только файл (модуль) spam.py , но и папку (пакет) spam .
Обратите внимание, что Python сначала производит поиск среди встроенных модулей — тех, которые встроены непосредственно в интерпретатор. Список встроенных модулей зависит от дистрибутива Python, а найти этот список можно в sys.builtin_module_names (Python 2 и Python 3). Обычно в дистрибутивах есть модули sys (всегда включён в дистрибутив), math , itertools , time и прочие.
В отличие от встроенных модулей, которые при поиске проверяются первыми, остальные (не встроенные) модули стандартной библиотеки проверяются после директории запущенного скрипта. Это приводит к сбивающему с толку поведению: возможно «заменить» некоторые, но не все модули стандартной библиотеки. Допустим, модуль math является встроенным модулем, а random — нет. Таким образом, import math в start.py импортирует модуль из стандартной библиотеки, а не наш файл math.py из той же директории. В то же время, import random в start.py импортирует наш файл random.py .
17–19 декабря, Онлайн, Беcплатно
Кроме того, импорты в Python регистрозависимы: import Spam и import spam — разные вещи.
Функцию pkgutil.iter_modules() (Python 2 и Python 3) можно использовать, чтобы получить список всех модулей, которые можно импортировать из заданного пути:
Чуть подробнее о sys.path
Чтобы увидеть содержимое sys.path , запустите этот код:
Документация Python описывает sys.path так:
Список строк, указывающих пути для поиска модулей. Инициализируется из переменной окружения PYTHONPATH и директории по умолчанию, которая зависит от дистрибутива Python.
При запуске программы после инициализации первым элементом этого списка, path[0] , будет директория, содержащая скрипт, который был использован для вызова интерпретатора Python. Если директория скрипта недоступна (например, если интерпретатор был вызван в интерактивном режиме или скрипт считывается из стандартного ввода), то path[0] является пустой строкой. Из-за этого Python сначала ищет модули в текущей директории. Обратите внимание, что директория скрипта вставляется перед путями, взятыми из PYTHONPATH .
Источник: Python 2 и Python 3
Документация к интерфейсу командной строки Python добавляет информацию о запуске скриптов из командной строки. В частности, при запуске python <script>.py .
Если имя скрипта ссылается непосредственно на Python-файл, то директория, содержащая этот файл, добавляется в начало sys.path , а файл выполняется как модуль main .
Источник: Python 2 и Python 3
Итак, повторим порядок, согласно которому Python ищет импортируемые модули:
- Модули стандартной библиотеки (например, math , os ).
- Модули или пакеты, указанные в sys.path :
- Если интерпретатор Python запущен в интерактивном режиме:
- sys.path[0] — пустая строка '' . Это значит, что Python будет искать в текущей рабочей директории, из которой вы запустили интерпретатор. В Unix-системах эту директорию можно узнать с помощью команды pwd .
Если мы запускаем скрипт командой python <script>.py :
Обратите внимание, что при запуске скрипта для sys.path важна не директория, в которой вы находитесь, а путь к самому скрипту. Например, если в командной строке мы находимся в test/folder и запускаем команду python ./packA/subA/subA1.py , то sys.path будет включать в себя test/packA/subA/ , но не test/ .
Кроме того, sys.path общий для всех импортируемых модулей. Допустим, мы вызвали python start.py . Пусть start.py импортирует packA.a1 , а a1.py выводит на экран sys.path . В таком случае sys.path будет включать test/ (путь к start.py ), но не test/packA (путь к a1.py ). Это значит, что a1.py может вызвать import other , так как other.py находится в test/ .
Всё о __init__.py
У файла __init__.py есть две функции:
- Превратить папку со скриптами в импортируемый пакет модулей (до Python 3.3).
- Выполнить код инициализации пакета.
Превращение папки со скриптами в импортируемый пакет модулей
Чтобы импортировать модуль (или пакет) из директории, которая находится не в директории нашего скрипта (или не в директории, из которой мы запускаем интерактивный интерпретатор), этот модуль должен быть в пакете.
Как было сказано ранее, любая директория, содержащая файл __init__.py , является пакетом. Например, при работе с Python 2.7 start.py может импортировать пакет packA , но не packB , так как в директории test/packB/ нет файла __init__.py .
Это не относится к Python 3.3 и выше благодаря появлению неявных пакетов пространств имён. Проще говоря, в Python 3.3+ все папки считаются пакетами, поэтому пустые файлы __init__.py больше не нужны.
Допустим, packB — пакет пространства имён, так как в нём нет __init__.py . Если запустить интерактивную оболочку Python 3.6 в директории test/ , то мы увидим следующее:
Выполнение кода инициализации пакета
В момент, когда пакет или один из его модулей импортируется в первый раз, Python выполняет __init__.py в корне пакета, если такой файл существует. Все объекты и функции, определённые в __init__.py , считаются частью пространства имён пакета.
Рассмотрим следующий пример:
Вывод после запуска python start.py :
Примечание Если a1.py вызовет import a2 , и мы запустим python a1.py , то test/packA/__init__.py не будет вызван, несмотря на то, что a2 вроде бы является частью пакета packA . Это связано с тем, что когда Python выполняет скрипт (в данном случае a1.py ), содержащая его папка не считается пакетом.
Использование объектов из импортированного модуля или пакета
Есть 4 разных вида импортов:
- import <пакет>
- import <модуль>
- from <пакет> import <модуль или подпакет или объект>
- from <модуль> import <объект>
Пусть X — имя того, что идёт после import :
- Если X — имя модуля или пакета, то для того, чтобы использовать объекты, определённые в X , придётся писать X.объект .
- Если X — имя переменной, то её можно использовать напрямую.
- Если X — имя функции, то её можно вызвать с помощью X() .
Опционально после любого выражения import X можно добавить as Y . Это переименует X в Y в пределах скрипта. Учтите, что имя X с этого момента становится недействительным. Частым примером такой конструкции является import numpy as np .
Аргументом для import может быть как одно имя, так и их список. Каждое из имён можно переименовать с помощью as . Например, следующее выражение будет действительно в start.py : import packA as pA, packA.a1, packA.subA.sa1 as sa1 .
Пример: нужно в start.py импортировать функцию helloWorld() из sa1.py .
- Решение 1: from packA.subA.sa1 import helloWorld . Мы можем вызвать функцию напрямую по имени: x = helloWorld() .
- Решение 2: from packA.subA import sa1 или то же самое import packA.subA.sa1 as sa1 . Для использования функции нам нужно добавить перед её именем имя модуля: x = sa1.helloWorld() . Иногда такой подход предпочтительнее первого, так как становится ясно, из какого модуля взялась та или иная функция.
- Решение 3: import packA.subA.sa1 . Для использования функции перед её именем нужно добавить полный путь: x = packA.subA.sa1.helloWorld() .
Прим. перев. После переименования с помощью as новое имя нельзя использовать в качестве имени пакета или модуля для последующих импортов. Иными словами, команда вроде следующей недействительна: import packA as pA, pA.a1 .
Используем dir() для исследования содержимого импортированного модуля
После импортирования модуля можно использовать функцию dir() для получения списка доступных в модуле имён. Допустим, мы импортируем sa1 . Если в sa1.py есть функция helloWorld() , то dir(sa1) будет включать helloWorld :
Импортирование пакетов
Импортирование пакета по сути равноценно импортированию его __init__.py . Вот как Python на самом деле видит пакет:
После импорта становятся доступны только те объекты, что определены в __init__.py пакета. Поскольку в packB нет такого файла, от import packB (в Python 3.3.+) будет мало толку, так как никакие объекты из этого пакета не становятся доступны. Последующий вызов модуля packB.b1 приведёт к ошибке, так как он ещё не был импортирован.
Абсолютный и относительный импорт
При абсолютном импорте используется полный путь (от начала корневой папки проекта) к желаемому модулю.
При относительном импорте используется относительный путь (начиная с пути текущего модуля) к желаемому модулю. Есть два типа относительных импортов:
- При явном импорте используется формат from .<модуль/пакет> import X , где символы точки . показывают, на сколько директорий «вверх» нужно подняться. Одна точка . показывает текущую директорию, две точки .. — на одну директорию выше и т. д.
- Неявный относительный импорт пишется так, как если бы текущая директория была частью sys.path . Такой тип импортов поддерживается только в Python 2.
В документации Python об относительных импортах в Python 3 написано следующее:
Единственный приемлемый синтаксис для относительных импортов — from .[модуль] import [имя] . Все импорты, которые начинаются не с точки . , считаются абсолютными.
Источник: What’s New in Python 3.0
В качестве примера допустим, что мы запускаем start.py , который импортирует a1 , который импортирует other , a2 и sa1 . Тогда импорты в a1.py будут выглядеть следующим образом:
Явные относительные импорты:
Неявные относительные импорты (не поддерживаются в Python 3):
Учтите, что в относительных импортах с помощью точек . можно дойти только до директории, содержащей запущенный из командной строки скрипт (не включительно). Таким образом, from .. import other не сработает в a1.py . В результате мы получим ошибку ValueError: attempted relative import beyond top-level package .
Как правило, абсолютные импорты предпочтительнее относительных. Они позволяют избежать путаницы между явными и неявными импортами. Кроме того, любой скрипт с явными относительными импортами нельзя запустить напрямую:
Имейте в виду, что относительные импорты основаны на имени текущего модуля. Так как имя главного модуля всегда "__main__" , модули, которые должны использоваться как главный модуль приложения, должны всегда использовать абсолютные импорты.
Источник: Python 2 и Python 3
Примеры
Пример 1: sys.path известен заранее
Если вы собираетесь вызывать только python start.py или python other.py , то прописать импорты всем модулям не составит труда. В данном случае sys.path всегда будет включать папку test/ . Таким образом, все импорты можно писать относительно этой папки.
Пример: файлу в проекте test нужно импортировать функцию helloWorld() из sa1.py .
Решение: from packA.subA.sa1 import helloWorld (или любой другой эквивалентный синтаксис импорта).
Пример 2: sys.path мог измениться
Зачастую нам требуется как запускать скрипт напрямую из командной строки, так и импортировать его как модуль в другом скрипте. Как вы увидите далее, здесь могут возникнуть проблемы, особенно в Python 3.
Пример: пусть start.py нужно импортировать a2 , которому нужно импортировать sa2 . Предположим, что start.py всегда запускается напрямую, а не импортируется. Также мы хотим иметь возможность запускать a2 напрямую.
Звучит просто, не так ли? Нам всего лишь нужно выполнить два импорта: один в start.py и другой в a2.py .
Проблема: это один из тех случаев, когда sys.path меняется. Когда мы выполняем start.py , sys.path содержит test/ , а при выполнении a2.py sys.path содержит test/packA/ .
С импортом в start.py нет никаких проблем. Так как этот модуль всегда запускается напрямую, мы знаем, что при его выполнении в sys.path всегда будет test/ . Тогда импортировать a2 можно просто с помощью import packA.a2 .
С импортом в a2.py немного сложнее. Когда мы запускаем start.py напрямую, sys.path содержит test/ , поэтому в a2.py импорт будет выглядеть как from packA.subA import sa2 . Однако если запустить a2.py напрямую, то в sys.path уже будет test/packA/ . Теперь импорт вызовет ошибку, так как packA не является папкой внутри test/packA/ .
Вместо этого мы могли бы попробовать from subA import sa2 . Это решает проблему при запуске a2.py напрямую, однако теперь создаёт проблему при запуске start.py . В Python 3 это приведёт к ошибке, потому что subA не находится в sys.path (в Python 2 это не вызовет проблемы из-за поддержки неявных относительных импортов).
Запускаем from packA.subA import sa2 from subA import sa2 start.py Нет проблем В Py2 нет проблем, в Py3 ошибка ( subA не в test/ ) a2.py Ошибка ( packA не в test/packA/ ) Нет проблем Использование относительного импорта from .subA import sa2 будет иметь тот же эффект, что и from packA.subA import sa2 .
Вряд ли для этой проблемы есть чистое решение, поэтому вот несколько обходных путей:
1. Использовать абсолютные импорты относительно директории test/ (т. е. средняя колонка в таблице выше). Это гарантирует, что запуск start.py напрямую всегда сработает. Чтобы запустить a2.py напрямую, запустите его как импортируемый модуль, а не как скрипт:
- В консоли смените директорию на test/ .
- Запустите python -m packA.a2 .
2. Использовать абсолютные импорты относительно директории test/ (средняя колонка в таблице). Это гарантирует, что запуск start.py напрямую всегда сработает. Чтобы запустить a2.py напрямую, можно изменить sys.path в a2.py , чтобы включить test/packA/ перед импортом sa2 .
Примечание Обычно этот метод работает, однако в некоторых случаях переменная __file__ может быть неправильной. В таком случае нужно использовать встроенный пакет inspect . Подробнее в этом ответе на StackOverflow.
3. Использовать только Python 2 и неявные относительные импорты (последняя колонка в таблице).
4. Использовать абсолютные импорты относительно директории test/ и добавить её в переменную среды PYTHONPATH . Это решение не переносимо, поэтому лучше не использовать его. О том, как добавить директорию в PYTHONPATH , читайте в этом ответе.
Пример 3: sys.path мог измениться (вариант 2)
А вот ещё одна проблема посложнее. Допустим, модуль a2.py никогда не надо запускать напрямую, но он импортируется start.py и a1.py , которые запускаются напрямую.
В этом случае первое решение из примера выше не сработает. Тем не менее, всё ещё можно использовать остальные решения.
Пример 4: импорт из родительской директории
Если мы не изменяем PYTHONPATH и стараемся не изменять sys.path программно, то сталкиваемся со следующим основным ограничением импортов в Python: при запуске скрипта напрямую невозможно импортировать что-либо из его родительской директории.
Например, если бы нам пришлось запустить python sa1.py , то этот модуль не смог бы ничего импортировать из a1.py без вмешательства в PYTHONPATH или sys.path .
На первый взгляд может показаться, что относительные импорты (например from .. import a1 ) помогут решить эту проблему. Однако запускаемый скрипт (в данном случае sa1.py ) считается «модулем верхнего уровня». Попытка импортировать что-либо из директории над этим скриптом приведёт к ошибке ValueError: attempted relative import beyond top-level package .
Для решения этой проблемы лучше её не создавать и избегать написания скриптов, которые импортируют из родительской директории. Если этого нельзя избежать, то предпочтительным обходным путём является изменение sys.path .
Python 2 vs Python 3
Мы разобрали основные отличия импортов в Python 2 и Python 3. Они ещё раз изложены здесь наряду с менее важными отличиями:
Как вы, возможно знаете, код на Python хранится в модулях (modules), которые могут быть объединены в пакеты (packages). Это руководство призвано подробно рассказать именно о пакетах, однако совсем не упомянуть модули нельзя, поэтому я немного расскажу и о них. Многое из того, что применимо к модулям, справедливо и для пакетов, особенно если принять во внимание тот факт, что каждый, как правило, ведёт себя как модуль.
Кратко о модулях
Модуль в Python — это файл с кодом. Во время же исполнения модуль представлен соответствующим объектом, атрибутами которого являются:
- Объявления, присутствующие в файле.
- Объекты, импортированные в этот модуль откуда-либо.
При этом определения и импортированные сущности ничем друг от друга не отличаются: и то, и другое — это всего лишь именованные ссылки на некоторые объекты первого класса (такие, которые могут быть переданы из одного участка кода в другой как обычные значения).
Такое единообразие удобно, например, при рефакторинге: мы можем разделить один разросшийся модуль на несколько, а потом импортировать вынесенные определения в оригинальный модуль. При этом с точки зрения внешнего наблюдателя переработанный модуль будет иметь те же атрибуты, которые имел до внесения изменений, а значит у пользователей модуля ничего в коде не сломается.
Модули и видимость содержимого
В Python нет настоящего сокрытия атрибутов объектов, поэтому и атрибуты объекта модуля так или иначе всегда доступны после импорта последнего. Однако существует ряд соглашений, которые влияют на процесс импортирования и поведение инструментов, работающих с кодом.
Так атрибуты, имя которых начинается с одиночного подчёркивания, считаются как бы помеченными "для внутреннего использования", и обычно не отображаются в IDE при обращению к объекту "через точку". И linter обычно предупреждает об использовании таких атрибутов, мол, "небезопасно!". "Опасность" состоит в том, что автор кода имеет полное право изменять состав таких атрибутов без уведомления пользователей кода. Поэтому программист, использовавший в своём коде приватные части чужого кода рискует в какой-то момент получить код, который перестанет работать при обновлении сторонней библиотеки.
Итак, мы можем определять публичные атрибуты модуля, приватные атрибуты (так называют упомянутые выше атрибуты "для внутреннего пользования"). И данное разделение касается не только определений, содержащихся в самом модуле, но и импортируемых сущностей. Ведь все импортированные объекты становятся атрибутами и того модуля, в который они импортированы.
Есть и третья группа атрибутов — атрибуты, добавляемые в область видимости при импортировании всего содержимого модуля ("со звёздочкой", from module import * ). Если ничего явно не указывать, то при таком импортировании в текущую область видимости добавятся все публичные атрибуты модуля. Помимо данного умолчания существует и возможность явно указать, что конкретно будет экспортировано при импорте со звёздочкой. Для управления названным методом импорта существует атрибут __all__ , в который можно положить список (а ещё лучше — кортеж) строк с именами, которые будут экспортироваться.
Живой пример видимости атрибутов модулей.
Рассмотрим пример, демонстрирующий всё вышеописанное. Пусть у нас будет два файла:
Рассмотрим сначала обычный импорт import module . Если импортировать модуль таким образом, то IDE, REPL и остальные инструменты "увидят" у модуля следующие атрибуты:
- FISH , MEAT т.к. имена констант — публичные,
- CAT , т.к. константа импортирована под публичным именем.
А эти атрибуты не будут видны:
- _DOG , т.к. при импортировании константа переименована в приватной манере,
- _GOAT , т.к. импортирована по своему приватному имени (тут линтер может и поругать за обращение к приватному атрибуту модуля!),
- _CARROT , ибо приватная константа.
Импорт import other_module я не рассматриваю как тривиальный случай.
Теперь рассмотрим импорт всего содержимого module:
После импортирования в текущей области видимости мы получим ровно два новых имени: FISH и _CARROT — именно они перечислены в атрибуте __all__ . Заметьте, что в данном случае при массовом импорте добавится даже приватный атрибут, потому что он явно указан!
Последствия импорта from other_module import * тоже очевидны и я их не рассматриваю.
Наконец-то, пакеты!
Пакет в Python — директория с обязательным модулем __init__.py . Остальное содержимое опционально и может включать в себя и модули, и другие пакеты.
Импортирование пакетов
Пакет с единственным модулем __init__.py при импорте ведёт себя как обычный модуль. Содержимое инициализирующего модуля определяет атрибуты объекта пакета.
Прочие модули пакета и вложенные пакеты не импортируются автоматически вместе с пакетом-родителем, но могут быть импортированы отдельно с указанием полного имени. Важный момент: при импортировании вложенного модуля всегда сначала импортируются модули инициализации всех родительских пакетов (если оные ещё ни разу не импортировались, но об этом я расскажу ниже).
Рассмотрим, к примеру, следующую структуру директорий и файлов:
Когда мы импортируем модуль submodule.py , то фактически происходит следующее (именно в таком порядке):
- загружается и выполняется модуль package/__init__.py ,
- загружается и выполняется package/subpackage/__init__.py ,
- наконец, импортируется package/subpackage/submodule.py .
При импорте package.module предварительно загружается только package/__init__.py .
Так что же, если мы загрузим парочку вложенных модулей, то для каждого будет выполняться загрузка всех __init__.py по дороге? Не будет! Подсистема интерпретатора, отвечающая за загрузку модулей, кэширует уже загруженные пакеты и модули. Каждый конкретный модуль загружается ровно один раз, в том числе и инициализирующие модули __init__.py (короткие имена модулей хоть и одинаковы, но полные имена всегда разные). Все последующие импортирования модуля не приводят к его загрузке, только лишь нужные атрибуты копируются в соответствующие области видимости.
Пакеты и __all__
В целом атрибут __all__ в модуле инициализации пакета ведёт себя так же, как и в случае с обычным модулем. Но если при импорте пакета "со звёздочкой" среди перечисленных имён встретится имя вложенного модуля, а сам модуль не окажется импортирован ранее в этом же __init__.py , то этот модуль импортируется неявно! Очередной пример это продемонстрирует.
Вот структура пакета:
Файл же package/__init__.py содержит следующее (и только это!):
А импортируем мы from package import * . В области видимости у нас окажутся объекты модулей a и b под своими именами (без полного пути, то есть без package. ). При этом сами модули в коде нигде явно не импортируются! Такая вот "автомагия".
Указанный автоматизм достаточно ограничен: не работает "вглубь", например — не импортирует "через звёздочку" указанные модули и подпакеты. Если же вам вдруг такого захочется, вы всегда сможете на соответствующих уровнях в __init__.py сделать from x import * и получить в корневом пакете плоскую область видимости со всем нужным содержимым. Но такое нужно довольно редко, потому что "не помогает" ни IDE, ни ручному поиску по коду. Впрочем, знать о фиче и иметь её в виду — не вредно, как мне кажется.
Изучайте Python на Хекслете Первые курсы в профессии Python-программист доступны бесплатно сразу после регистрации. Начните сегодня, учитесь в комфортном для вас темпе.
Пакеты, модули и точки входа
У пакетов роль атрибута выполняет специальный файл __main__.py . Когда мы запустим пакет через python path/to/package или python -m package , интерпретатор будет искать и выполнять именно этот файл.
Более того, модули __main__ нельзя импортировать обычным способом, поэтому можно не бояться случайного импорта и писать команды прямо на верхнем уровне: всё равно странно в модуле с именем __main__ проверять, что его имя равно __main__ (хе-хе!).
А ещё модуль __main__.py удобен тем, что его можно класть в корень вашего проекта, после чего запускать проект можно будет с помощью команды python . ! Лаконично, не правда ли?
PEP 420, или неявные пространства имён
Раз уж развёл ликбез, расскажу и про эту штуку.
Долгое время в Python пакеты были обязаны иметь файл __init__.py — наличие этого файла позволяло отличить пакет от обычной директории с модулями (с которыми Python работать не мог). Но с версии Python3.3 вступил в силу PEP 420, позволяющий создавать пространства имён "на вырост".
Теперь вы можете создавать пакет без __init__.py , и такой пакет сможет существовать полноценно, разве что при импорте содержимого не будет производиться инициализация. Но, конечно же, данное изменение делалось не с целью сэкономить на файлах. Подобные пакеты могут встречаться в путях поиска пакетов (о поиске пакетов я ниже расскажу) более одного раза: все встреченные структуры с общим корневым именем при загрузке схлопнутся в одно пространство имён.
Тут стоит отметить, что с полноценными пакетами подобное не срабатывало ранее и не будет работать в будущем. Если среди путей пакет с модулем инициализации находится в первый раз, все последующие пакеты с тем же именем будут проигнорированы. Это защищает вас от смешивания сторонних пакетов с системными. И даже просто от ошибок именования: назвав пакет так же, как называется встроенный пакет или модуль, вы получите ошибку — ваши определения не будут импортироваться.
Пакеты — пространства имён (Namespace Packages, NP) — а именно так называются пакеты без инициализации — не могут объединяться с полноценными пакетами, поэтому добавить что-то в системный пакет вам также не удастся. И тут всё защищено!
Какая же польза от неявных пространств имён? А вы представьте себя авторами, скажем, игрового движка. Вы хотите весь код держать в общем пространстве имён engine , но при этом не желаете, чтобы весь код поставлялся одним дистрибутивом (не каждому же пользователю нужны все-все компоненты движка). С NP вы можете в нескольких дистрибутивах использовать общее корневое имя engine , но разные подпакеты и подмодули. А на выходе вы получите возможность делать импорты вида
Важно: помните, если встретятся обычный пакет и NP с одинаковым именем, то победит обычный пакет! А NP, сколько бы их не было, не будут загружены!
Циклические импорты
Если вдруг вы захотите в один модуль импортировать другой, а другой захочет, пусть даже и не напрямую, импортировать первый, то вы получите ImportError . Потому что у вас случится циклический импорт. Про оный нужно просто знать и стараться архитектурить код так, чтобы циклов не случалось.
Если же приспичивает, и импортировать что-то "ну очень нужно", то можно попробовать обойтись локальным импортом:
Да, это костыль. Но иногда полезный. В идеале — до ближайшего большого рефакторинга. Поэтому настраивайте linter на ловлю локальных импортов и стремитесь убирать такие костыли хоть когда-нибудь!
Поиск пакетов и модулей
Пайтон ищет модули и пакеты в директориях, во время исполнения перечисленных в списке sys.path — по порядку от первого пути к последнему.
В этом списке пути до стандартных библиотек обычно расположены раньше, чем директории со сторонними пакетами, чтобы нельзя было случайно заменить стандартный пакет сторонним (помним: кто первый, того и тапки — среди нескольких с одинаковыми именами загружается первый попавшийся пакет).
Обычно пути трогать не нужно, всё вполне нормально "работает само". Но если очень хочется, то путей у вас несколько:
- Использовать переменную окружения PYTHONPATH (значение — строка с путями, разделёнными символом : ),
- Во время исполнения изменить sys.path .
Первый способ — простой и понятный. Не сложнее добавления пути до исполняемых файлов в PATH (даже синтаксис тот же).
Второй способ — сложный и требующий внимательности. Дело в том, что sys.path нужно изменять максимально рано — где-нибудь в точке входа. Если не торопиться менять sys.path , то что-то уже может успеть загрузиться до того, как вы перестроите пути для поиска пакетов. А ведь эта загрузка может произойти в другом потоке исполнения! Отлаживать проблемы с очерёдностью загрузки модулей сложно. Лучше просто их не создавать.
Кстати, когда вы используете виртуальные окружения, sys.path будет содержать пути до локальных копий стандартных библиотек. Именно это позволяет виртуальному окружению быть самодостаточным (работать на любой машине с подходящей ОС — даже без установленного в систему Python!).
Что не было раскрыто?
Я специально не стал рассказывать про
- создание модулей и пакетов на лету (без использования файлов исходников);
- загрузку модулей не с диска, а из других источников;
- расширение подсистемы импортирования с целью загрузки в виде объектов-модулей чего-то, не являющегося кодом вовсе (XML, CSV, JSON).
Темы эти насколько интересны, настолько и велики. На наше счастье, самим разбираться в такой тонкой и сложной машинерии приходится редко. Мы просто пользуемся готовыми магическими артефактами, а зачаровывают их другие :) Если же вы захотите научиться магии, документация вам в руки.
Читайте также:
- Если интерпретатор Python запущен в интерактивном режиме: