Перевод статьи The Definitive Guide to Python import Statements.

Я почти никогда не мог с первого раза написать правильно такие инструкции Python как import. Поведение этой инструкции не одинаково в различных версиях Python 2.7 и Python 3.6 (эти две версии рассматриваются здесь) и нет единого способа сделать так, что бы инструкция импорта import всегда гарантированно работала так как нам нужно. Эта статья — погружение в решение проблем с использованием инструкции import.

Общее / Ключевые моменты

  • инструкция import осуществляет поиск модулей в списке путей, содержащемся в переменной sys.path;
  • sys.path всегда включает путь к скрипту, запущенному в настоящий момент времени в командной строке и однозначно задает рабочую директорию для текущего сеанса командной строки;
  • концептуально импорт пакета происходит, как импорт файла пакета __init__.py.

Основные определения

  • модуль: любой файл с расширением *.py. Именем модуля является имя файла.
  • встроенный модуль: модуль (написанный на С ), который интегрирован непосредственно в интерпретатор Python и следовательно не имеет отдельного файла с расширением *.py.
  • пакет: любая папка содержащая файл с именем __init\__.py. Имя пакета совпадает с именем папки. В Python 3.3 и выше любая папка (даже без файла __init\__.py) считается пакетом.
  • объект: в Python почти всё является объектом: функции, классы, переменные и т.д.

Пример структуры директории

test/                      # корневая папка
    packA/                 # пакет packA
        subA/              # субпакет subA
            __init__.py
            sa1.py
            sa2.py
        __init__.py
        a1.py
        a2.py
    packB/                 # пакет packB (неявное пространство имен пакета)
        b1.py
        b2.py
    math.py
    random.py
    other.py
    start.py

Обратите внимание, что мы не размещаем __init\__.py в корневой папке test/.

Что такое import ?

Когда вы импортируете модуль, Python запускает код из файла модуля. Когда вы импортируете пакет, Python сначала запускает код из файла пакета __init__.py, если такой файл существует. После этого все объекты, определенные в файле модуля или __init__.py пакета, становятся доступны.

Основы использования оператора import и переменной sys.path

В соответствии с документацией Python, оператор import ищет корректный модуль или пакет для импорта в соответствии со следующими правилами.

Когда импортируется модуль с определенным именем, например spam, интерпретатор сначала ищет встроенный модуль с этим именем.
Если он не найден, он ищет файл с именем spam.py в списке каталогов, заданных в переменной sys.path. sys.path инициализируется следующими значениями путей:

  • директория, содержащая запущенный на исполнение скрипт (или текущий каталог, когда не указан файл).
  • переменная окружения PYTHONPATH (список имен каталогов с тем же синтаксисом, что и PATH переменная оболочки shell).
  • значение по умолчанию, заданное при установке Python.

После запуска программы, написанные на Python, могут изменять значение переменной sys.path. Каталог, содержащий выполняемый скрипт, помещается в начало списка путей для поиска при импорте, впереди значения, содержащего путь к стандартной библиотеке.

Технически документация Python является неполной. На самом деле интерпретатор ищет не только файл (то есть, модуль) с именем spam.py, также он будет искать папку (то есть, пакет) с именем spam.
Обратите внимание, что интерпретатор Python сначала ищет список встроенных модулей, то есть модулей, которые интегрированы непосредственно в интерпретатор Python. Состав списка встроенных модулей зависит от типа установки и может быть найден в файлах sys.builtin_module_names (для Python версии 2 и 3). Обычно встроенные модули, которые входят с состав начальной установки, включают в себя sys (всегда устанавливается), math, itertools и time и другие.

В отличие от встроенных модулей (модулей стандартной библиотеки), пути к которым помещаются первыми в списке для поиска при импорте остальные модули в стандартной библиотеке Python появляются после пути каталога текущего скрипта. Это приводит к запутанному поведению: становится возможно «заменить» некоторые, но не все модули в стандартной библиотеке Python.
Например, на моем компьютере (Windows 10, Python 3.6) модуль math является встроенным модулем, тогда как модуль random нет. Таким образом, import math в start.py будет импортировать модуль math из стандартной библиотеки, а не мой собственный файл math.py, находящийся в тот же каталоге.
А оператор import random в start.py будет импортировать мой файл random.py, а не модуль random из стандартной библиотеки.

Кроме того, оператор import чувствителен к регистру символов в названии импортируемого файла. import Spam — это не то же самое, что import spam.

Функция pkgutil.iter_modules (Python 2 и 3) может использоваться для получения списка всех импортируемых модулей по заданному пути:

import pkgutil
# установите значение переменной search_path равным None, чтобы увидеть все модули, импортируемые из sys.path
search_path = '.' 
all_modules = [x[1] for x in pkgutil.iter_modules(path=search_path)]
print(all_modules)

Источники:
Как получить список встроенных модулей в python ?

Подробнее о sys.path

Чтобы узнать, что находится в переменной sys.path, запустите в командной строке следующие команды или скрипт содержащий инструкции:

import sys
print(sys.path)

Документация Python для sys.path описывает это следующим образом.

Список строк, определяющий пути поиска для модулей, инициализируется из переменной среды PYTHONPATH, а также зависит от значений по умолчанию при установке.
В ходе инициализации при запуске программы Python, первым элементом этого списка path[0], будет являться каталог, содержащий скрипт, который использовался при вызове интерпретатора Python.
Если каталог содержащий файл сценария недоступен (например, если интерпретатор работает в интерактивном режиме или если скрипт считывается в потоке стандартного ввода), path[0] — будет задан пустой строкой, которая направляет Python на поиск модулей в текущем каталоге.
Обратите внимание, что путь к каталогу, содержащему файл сценария будет вставлен перед путями полученными из переменной PYTHONPATH.
Источник: Python 2 и 3

Документация для интерфейса командной строки Python добавляет следующее о запуске сценариев из командной строки. В частности, при запуске командыpython script.py, происходит следующее.

Если имя сценария ссылается непосредственно на файл с расширением py, то каталог, содержащий этот файл, добавляется в начало sys.path и файл выполняется в качестве основного модуля.
Источник: Python 2 и 3

Давайте рассмотрим порядок, в соответствии с которым интерпретатор Python ищет модули для импорта:

  1. Модули в стандартной библиотеке Python (например, math, os).
  2. Модули или пакеты в каталоге, заданном в sys.path:
    1. Если интерпретатор Python запускается в интерактивном режиме:
      • sys.path[0] — пустая строка ‘ ‘. Это служит указанием для интерпретатора Python, что в качестве текущего рабочего каталога необходимо использовать каталог из которого был запущен интерпретатор, т. е. результат возвращаемый утилитой pwd в операционных системах Unix.
    2. Если мы запускаем сценарий из командной строки с использованием команды вида python script.py:
      • sys.path[0] — записывается как путь к script.py.
  3. Каталоги в переменной окружения PYTHONPATH
  4. Значения из переменной sys.path, заданные по умолчанию.

Обратите внимание, что при запуске скрипта Python в sys.path не учитывается, каков ваш текущий «рабочий каталог». Учитывается только о путь к сценарию. Например, если интерпретатор запущен из папки test/ и вы запускаете команду 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 выполняет 2 функции.

  1. Преобразование папки со скриптами в импортируемый пакет модулей (до Python 3.3).
  2. Запуск кода инициализации пакета.

Преобразование папки сценариев в импортируемый пакет модулей

Как уже было сказано выше, любой каталог, содержащий файл с именем __init__.py представляет собой пакет Python. Этот файл так же может быть пустым. Например, при запуске скрипта start.py в Python 2.7 можно импортировать пакет packA, но не packB, так как в каталоге test/packB/ нет файла __init__.py.

Это НЕ применимо к Python 3.3 и выше, благодаря принятию неявных пространств имен пакетов. В принципе, Python 3.3+ рассматривает все папки как пакеты, поэтому пустые файлы __init__.py больше не нужны и могут быть опущены.

Например, packB представляет собой пространство имен пакета, поскольку в папке нет файла __init__.py. Если мы запустим интерактивный интерпретатор Python версии 3.6 в каталоге test/, то получим следующий результат:

>>> import packB
>>> packB
<module 'packB' (namespace)>

Источники:
1. What is init.py for?
2. PEP 420: Implicit Namespace Packages.

Запуск кода инициализации пакета

В первый раз, когда вы импортируете пакет или один из его модулей, Python будет выполнять файл __init__.py в корневой папке пакета, если этот файл существует. Все объекты и функции, определенные в __init__.py, считаются частью пространства имен пакета.

Рассмотрим следующий пример.

Листинг файла test/packA/a1.py:

def a1_func():
    print("running a1_func()")

Листинг файла test/packA/__init__.py:

## этот импорт делает a1_func доступной напрямую из packA.a1_func
from packA.a1 import a1_func

def packA_func():
    print("running packA_func()")

Листинг файла test/start.py:

import packA  # "import packA.a1" будет работать также

packA.packA_func()
packA.a1_func()
packA.a1.a1_func()

Команда python start.py выведет следующее:

running packA_func()
running a1_func() 
running a1_func()

Примечание: если в файле a1.py вызывается import a2, и вы запускаете в командной строке команду python a1.py, то test/packA/__ init__.py НЕ будет вызван, хотя на первый взгляд кажется, что a2 является частью пакета packA. Так происходит потому, что Python запускает скрипт (в нашем случае a1.py), но содержащая его папка не является пакетом.

Использование объектов из импортированного модуля или пакета

Существует четыре различных вида синтаксиса для записи операторов импорта.

  1. import <package>
  2. import <module>
  3. from <package> import <module or subpackage or object>
  4. from <module> import <object>

Пусть X любое имя после ключевого слова import.

  • если X — это имя модуля или пакета, то для использования объектов, определенных в X, вам нужно написать X.object.
  • если X является именем переменной, его можно использовать непосредственно напрямую.
  • если X является именем функции, то его можно вызвать с помощью инструкции X().

Необязательно, но после любого оператора import X может быть добавлена инструкция вида as Y, например, 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

затем мы можем вызывать функцию непосредственно по имени:

a = helloWorld()

Решение №2:

# следующие две строки кода эквивалентны
from packA.subA import sa1
import packA.subA.sa1 as sa1

затем мы должны будем использовать в качестве префикса к имени функции имя модуля.

x = sa1.helloWorld()

Иногда это решение предпочтительнее Решения №1 для того чтобы сделать явным вызов функции helloWorld из модуля sa1.

Решение №3:

import packA.subA.sa1

Далее необходимо использовать полный путь:

x = packA.subA.sa1.helloWorld()

Использование dir() для проверки содержимого импортированного модуля

После импорта модуля, используйте функцию dir() для того чтобы получить список доступных имен модуля. Например, предположим, что вы импортируете sa1. Если в sa1.py определена функция helloWorld(), то инструкция dir(sa1) в числе прочих выведет имя helloWorld.

>>> from packA.subA import sa1
>>> dir(sa1)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'helloWorld']

Импорт пакетов

Импорт пакета концептуально эквивалентен импорту файла __init__.py из папки пакета в качестве модуля. И действительно это так. Вот как Python рассматривает импортируемый пакет:

>>> import packA
>>> packA
<module 'packA' from 'packA\__init__.py'>

Скриптам, импортирующим пакеты, доступны лишь те объекты, которые были объявлены в файле пакета __init__.py. Например, так как каталог packB не содержит файла __init__.py, то вызов инструкции import packB в Python 3.3+ будет мало полезен, поскольку никакие объекты при импорте пакета packB не будут доступны. Последующий вызов packB.b1 завершится неудачей, поскольку объект не будет импортирован.

Импорт с использованием абсолютных и относительных путей

Импорт по абсолютному пути использует полный путь (начинающийся с корневой папки проекта) до требуемого для импорта модуля .

Импорт по относительному пути использует относительный путь (начинающийся с пути к текущему модулю) до требуемого для импорта модуля. Существует два вида импорта с использованием относительного пути:

  • явный импорт задается в следующей форме from .<module/package> import X, где <module/package> имеет префикс в виде последовательности имен каталогов, разделенных точками ., которые указывают, на сколько каталогов необходимо переместиться вверх или вниз. При этом одна точка . соответствует текущему каталогу, а две точки .. предписывают переместиться на одну папку вверх и т.д.
  • неявный импорт записывается так, как будто текущий каталог является частью содержимого переменной sys.path. Неявный относительный импорт поддерживается только Python 2 и не поддерживается Python 3.

В документации Python говорится о том, как Python 3+ обрабатывает импорт по относительному пути:

Единственным верным синтаксисом для импорта по относительному пути является формат from .[module] import name. Все формы инструкции импорта не начинающиеся с точки . интерпретируются как импорт по абсолютному пути.
Источник: What’s New in Python 3.0.

Например, предположим, что мы запускаем на исполнение скрипт start.py, который импортирует a1, который, в свою очередь, импортирует other, a2 и sa1. Инструкции import в файле a1.py будут выглядеть следующим образом:

# импорт по абсолютному пути
import other
import packA.a2
import packA.subA.sa1

# явный импорт по относительному пути
import other
from . import a2
from .subA import sa1

# неявный импорт по относительному пути (не поддерживается в Python 3)
import other
import a2
import subA.sa1

Импорт по относительному пути может выполняться только для модулей в пакете; не допускается использовать эту возможность для ссылки на модули, которые находятся в другом каталоге файловой системы. Обратите внимание, что при импорте по относительному пути, точки .. помогут перемещаться при поиске файла для импорта только выше каталога (но не включая его), содержащего скрипт, запускаемый из командной строки. Таким образом, from .. import other не будет работать в a1.py. Это приводит к ошибке ValueError: attempted relative import beyond top-level package (попытка импорта по относительному пути за пределы пакета верхнего уровня).

В целом импорт по абсолютному пути предпочтительнее чем по относительному. Их использование позволяет избежать путаницы между явным или неявным импортом. Кроме того, любой сценарий, который использует явный импорт по относительному пути, не может быть запущен напрямую:

Обратите внимание, что импорт по относительному пути основан на имени текущего модуля. Поскольку имя основного модуля всегда «main», то модули, предназначенные для использования в качестве основного модуля приложения Python, должны всегда использовать импорт по абсолютному пути.
Источники: Python 2 и 3.

Источники:
1. How to accomplish relative import in python
2. Changes in import statement python3

Примеры

Пример 1: содержание sys.path заранее известно

Если вы запускаете скрипты командой python start.py или python other.py, то вам будет очень просто настроить импорт всех модулей. В этом случае переменная sys.path будет всегда включать директорию test/ в списке путей поиска при импорте. Поэтому все операторы импорта могут быть записаны относительно папки test/.

Например: сценарий в папке test должен импортировать функцию helloWorld() из файла sa1.py.
Решение: from packA.subA.sa1 import helloWorld

Пример 2: содержание sys.path может изменяться

Часто мы хотим гибко использовать сценарии Python независимо от того выполняются ли они в командной строке или импортированы как модуль в другой скрипт. Как будет показано ниже, здесь мы столкнемся с проблемами, а в особенности в коде написанном на Python 3.

Пример: Предположим, что в сценарии start.py необходимо импортировать a2.py, который в свою очередь должен импортировать sa2.py. Предположим, что start.py всегда запускается непосредственно напрямую, и никогда не импортируется. Мы также хотим иметь возможность запускать a2.py самостоятельно. Кажется, все достаточно просто. В конце концов, нам нужно всего лишь два оператора импорта: первый в start.py и второй в a2.py.

Проблема: Это случай, когда sys.path будет изменяться. Когда мы запускаем start.py, переменная sys.path содержит директорию test/. Когда мы запустим файл a2.py, то sys.path будет содержать test/ packA/.

Инструкция import в start.py проста для понимания. Зная заранее, что start.py будет всегда запускаться напрямую и никогда не будет импортирован, мы полагаем, что при его запуске путь test/ всегда будет находиться в переменной sys.path. Тогда инструкция для импорта a2.py будет следующая import packA.a2.

Инструкция import в 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, благодаря поддержке неявного импорта с относительными путями.

Таким образом, по использованию инструкции import в файле a2.py имеем:

Инструкцияfrom packA.subA import sa2from 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.py будет импортирован.
    import os, sys
    sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
    # now this works, even when a2.py is run directly
    from packA.subA import sa2

    ПРИМЕЧАНИЕ. Обычно этот метод работает. Однако в некоторых установках Python переменная __file__ может быть некорректной. В этом случае вам нужно будет использовать встроенный в стандартную библиотеку Python пакет inspect. Ознакомьтесь с вопросом на StackOverflow для правильного использования приведенных выше инструкций.

  3. Используйте только Python 2 если используете неявный импорт по относительному пути (т.е. третий столбец в приведенной выше таблице).
  4. Используйте импорт по абсолютному пути, в корне директории test/, и добавьте test/ в переменную среды PYTHONPATH.

Пример 3: содержание sys.path может изменяться (вариант 2)

Более сложная проблема заключается в следующем. Предположим, что a2.py никогда не нужно запускать напрямую, но он импортируется такими файлами, как start.py и a1.py, которые будут запускаться напрямую из консоли.

В этом случае Решение №1 не будет работать. Но другие будут.

Пример 4: Импорт из родительской директории

Если мы не собираемся модифицировать переменные PYTHONPATH и sys.path в коде, то в этом случае основным ограничением импорта является следующее: При непосредственном запуске скрипта через консоль невозможно импортировать что-либо из его родительского каталога.

Например, если вы хотите запустить следующую команду python sa1.py, то невозможно в файле 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 рассматривают инструкции import, были описаны выше. Они снова рассматриваются ниже, наряду с некоторыми другими менее важными отличиями.

  1. Python 2 поддерживает неявный импорт по относительному пути, а Python 3 — нет.
  2. Python 2 требует, чтобы файлы __init__.py находились внутри папки, чтобы папка считалась пакетом и была импортирована. В отличие от него для Python 3.3 и выше, благодаря поддержке неявного пространства имен пакетов, все папки являются пакетами независимо от наличия в них файла __init__.py.
  3. В Python 2 можно написать from <module> import внутри функции. В Python 3 синтаксис from <module> import разрешен только на уровне модуля, но не внутри функций.

Источники:
1. Изменения в инструкциях import в Python 3
2. Модули в Python 2 (официальная документация)
3. Модули в Python 2 (официальная документация)
4. Что нового в Python 3

Оставить комментарий