Здесь вы познакомитесь с модулями (modules) и пакетами (packages) Python, с двумя механизмами модульного программирования (modular programming).

Содержание

Модульное программирование есть процесс разбиения большой и громоздкой задачи на отдельные, более маленькие, управляемые подзадачи и модули. Все это по научному называется декомпозицией. Далее отдельные модули могут быть скомпонованы вместе, как строительные блоки, для создания более крупного приложения, решающего вашу задачу.

У такого, модульного подхода при проектировании кода больших приложений есть сразу несколько преимуществ:

  • Простота: Вместо того, чтобы думать о всей проблеме в целом, обычно, в модуле фокусируются на решении одной, относительно небольшой, части программы. Работая над одним модулем, сужается область размышлений, что делает разработку проще и менее подверженной ошибкам.
  • Модифицируемость: Обычно, модули имеют логические границы между различными задачами проблемы в целом. Если в модулях свести к минимуму взаимозависимости, то снижается вероятность того, что модификации одного модуля окажут влияние на другие части программы. Возможно, вы даже сможете вносить изменения в модуль, не зная ничего о приложении, для которого он написан. Таким образом, над одним приложением может работать большая группа программистов, что есть совместная разработка.
  • Повторное использование кода: Функциональность, определенная в одном модуле, может быть легко использована повторно (через соответствующий интерфейс) другими приложениями, что избавляет от необходимости дублирования.
  • Область действия: Обычно, в модуле определяется отдельное пространство имен, что помогает избежать коллизий между идентификаторами в разных областях программы. (Один из тезисов Дзен Python гласит Пространства имён — отличная штука! Будем делать их больше!)

Функции, модули и пакеты есть все конструкции в Python, которые способствуют модульному программированию на Python.

Модули в Python: обзор    ↑

На самом деле в Python есть три способа определения модуля:

  1. Модуль может быть написан на самом Python.
  2. Модуль может быть написан на C и динамически подгружен во время исполнения, как модуль re (regular expression).
  3. Модуль, встроенный в интерпретатор, как инструмент itertools.

Во всех трёх случаях доступ к модулю предоставляется одинаково — с помощью оператора import.

Здесь основное внимание будет уделено модулям, написанным на Python. Крутая штука в модулях, написанных на Python, заключается в том, что их чрезвычайно просто сделать. Все, что нужно, так это просто создать файл, который содержит допустимый код Python и дать ему имя с расширением .py. Это всё! Никакого специального синтаксиса или танцев с бубном не требуется.

Например, у вас есть файл mod.py со следующим кодом:

s = "Если товарищ Наполеон говорит, то это должно быть правильно."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

В mod.py определены следующие объекты:

  • s (строка)
  • a (список)
  • foo() (функция)
  • Foo (класс)

Предполагая, что файл mod.py расположен в правильном месте, о котором расскажем позже, доступ к этим объектам модуля можно получить импортируя модуль следующим образом:

>>> import mod
>>> print(mod.s)
Если товарищ Наполеон говорит, то это должно быть правильно.
>>> mod.a
[100, 200, 300]
>>> mod.foo(['quux', 'corge', 'grault'])
arg = ['quux', 'corge', 'grault']
>>> x = mod.Foo()
>>> x
<mod.Foo object at 0x03C181F0>

Правильное место для модуля    ↑

Продолжая разговор о вышеприведённом примере, посмотрим, что делает Python, выполняя оператор:

import mod

Когда интерпретатор выполняет оператор import, то он ищет файл mod.py в следующих каталогах в порядке приоритетности:

  • Текущий каталог, т.е. тот каталог, из которого был запущен наш скрипт с оператором import, если он запущен в интерактивном режиме.
  • В списке каталогов, определенном в установленной переменной окружения PYTHONPATH. (Формат PYTHONPATH зависит от операционной системы, но всегда похож на переменную окружения ОС PATH.)
  • В списоке каталогов, определённых и настроенных во время установки Python.

В результате, в переменной окружения sys.path модуля sys содержится список каталогов для поиска импортируемого модуля:

>>> import sys
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages']

Примечание: Значение в sys.path зависит от установки и на вашем компьютере, наверняка, будет отличаться от того, что написано выше.

Таким образом, чтобы убедиться, что ваш модуль найден, вам необходимо выполнить одно из следующих действий:

  • Разместить файл mod.py в текущем каталоге, если вы работаете в интерактивном режиме.
  • Изменить переменную окружения PYTHONPATH таким образом, что бы она содержала название каталога, где расположен файл mod.py
    • или: Разместить файл mod.py в одном из каталогов уже описанном в переменной PYTHONPATH.
  • Разместить файл модуля mod.py в одном из каталогов, которые определены при установке интерпретатора в вашей операционной системе.

На самом деле есть еще одна дополнительная возможность — можно поместить файл модуля в любой каталог по своему выбору, а затем изменить sys.path во время выполнения, чтобы он содержал этот каталог. Например, можно поместить mod.py в каталог C:\Users\john и затем выполнить следующие операторы:

>>> sys.path.append(r'C:\Users\john')
>>> sys.path
['', 'C:\\Users\\john\\Documents\\Python\\doc', 'C:\\Python36\\Lib\\idlelib',
'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib',
'C:\\Python36', 'C:\\Python36\\lib\\site-packages', 'C:\\Users\\john']
>>> import mod

После того, как модуль импортирован, можно определить его местоположение с помощью атрибута __file__ модуля:

>>> import mod
>>> mod.__file__
'C:\\Users\\john\\mod.py'

>>> import re
>>> re.__file__
'C:\\Python36\\lib\\re.py'

Часть каталога __file__ должна быть одной из каталогов в sys.path.

Оператор import    ↑

Содержимое модуля становится доступным вызывающему объекту после выполнения оператора import, который может быть записан в нескольких форматах. Их мы сейчас и обсудим.

import <module_name>    ↑

Простейшая форма уже показана выше:

import <module_name>

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

Оператор import <module_name> только размещает <module_name> в таблице определений вызвавшего объекта. Объекты остаются в собственной таблице определений модуля.

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

После выполнения оператора import mod размещается в локальной таблице определений. Таким образом, mod становится видимым в локальном контексте вызвавшего его кода:

>>> import mod
>>> mod
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>

Но s и foo остаются в собственной таблице определений модуля и не доступны в локальном контексте вызвавшего скрипта:

>>> s
NameError: name 's' is not defined
>>> foo('quux')
NameError: name 'foo' is not defined

Для доступа в локальном контексте имена объектов, определенных в модуле, должны иметь префикс mod:

>>> mod.s
'Когда говорит товарищ Наполеон, то это должно быть правильно.'
>>> mod.foo('quux')
arg = quux

В одном операторе import можно указать несколько модулей, разделенных запятыми:

import <module_name>[, <module_name> ...]

from <module_name> import <name(s)>    ↑

Альтернативный формат записи оператора import позволяет импортировать отдельные объекты из модуля непосредственно в таблицу определений вызывающего скрипта:

from <module_name> import <name(s)>

После выполнения приведенного выше оператора в среде вызывающего скрипта на <name(s)> можно ссылаться без префикса <module_name> :

>>> from mod import s, foo
>>> s
'Когда говорит товарищ Наполеон, то это должно быть правильно.'
>>> foo('quux')
arg = quux

>>> from mod import Foo
>>> x = Foo()
>>> x
<mod.Foo object at 0x02E3AD50>

Поскольку эта форма импорта помещает имена объектов непосредственно в таблицу определений вызывающего скрипта, то все объекты с таким же именем будут перезаписаны:

>>> a = ['foo', 'bar', 'baz']
>>> a
['foo', 'bar', 'baz']

>>> from mod import a
>>> a
[100, 200, 300]

Можно даже сделать import всего из модуля одним махом, без разбора:

from <module_name> import *

Это поместит имена всех объектов из<module_name> в локальную таблицу определений, за сключением тех, которые начинаются с символа подчеркивания (_).

Например:

>>> from mod import *
>>> s
'Когда говорит товарищ Наполеон, то это должно быть правильно.'
>>> a
[100, 200, 300]
>>> foo
<function foo at 0x03B449C0>
>>> Foo
<class 'mod.Foo'>

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

from <module_name> import <name> as <alt_name>    ↑

Кроме того, можно сделать import отдельных объекты, вводя их в локальную таблицу определений с альтернативными именами:

from <module_name> import <name> as <alt_name>[, <name> as <alt_name> …]

Это позволяет размещать имена непосредственно в локальной таблице определений, избегая конфликтов с ранее существующими именами:

>>> s = 'foo'
>>> a = ['foo', 'bar', 'baz']

>>> from mod import s as string, a as alist
>>> s
'foo'
>>> string
'Когда говорит товарищ Наполеон, то это должно быть правильно.'
>>> a
['foo', 'bar', 'baz']
>>> alist
[100, 200, 300]

import <module_name> as <alt_name>    ↑

Вы также можете импортировать весь модуль под другим именем:

import <module_name> as <alt_name>
>>> import mod as my_module
>>> my_module.a
[100, 200, 300]
>>> my_module.foo('qux')
arg = qux

Содержимое модуля может быть импортировано из функции. В этом случае import не происходит до тех пор, пока функция не будет вызвана:

>>> def bar():
...     from mod import foo
...     foo('corge')
...

>>> bar()
arg = corge

Однако, синтаксис Python 3 не допускает оператора import * внутри описания функции:

>>> def bar():
...     from mod import *
...
SyntaxError: import * only allowed at module level

Наконец, оператор try с выражением except ImportError позволяет избежать рекорректного import:

>>> try:
...     # Non-existent module
...     import baz
... except ImportError:
...     print('Module not found')
...

Module not found
>>> try:
...     # Existing module, but non-existent object
...     from mod import baz
... except ImportError:
...     print('Object not found in module')
...

Object not found in module

Функция dir()    ↑

Встроенная функция dir() возвращает список всех имен, определенных в пространстве. Если при вызове отсутствуют аргументы, то она создает алфавитно упорядоченный список имен в текущей локальной таблице определений:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> qux = [1, 2, 3, 4, 5]
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux']

>>> class Bar():
...     pass
...
>>> x = Bar()
>>> dir()
['Bar', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'qux', 'x']

Обратите внимание, что при первом вызове dir() перечисляются несколько имен, которые определяются автоматически и уже при запуске интерпретатора находятся в пространстве имен. По мере определения новых имен (quux, Bar, x) они появляются при последующих вызовах dir().

Это бывает полезно для понимания того, что именно было добавлено в пространство имен с помощью инструкции import:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> import mod
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod']
>>> mod.s
'Когда говорит товарищ Наполеон, то это должно быть правильно.'
>>> mod.foo([1, 2, 3])
arg = [1, 2, 3]

>>> from mod import a, Foo
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod']
>>> a
[100, 200, 300]
>>> x = Foo()
>>> x
<mod.Foo object at 0x002EAD50>

>>> from mod import s as string
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'mod', 'string', 'x']
>>> string
'Когда говорит товарищ Наполеон, то это должно быть правильно.'

При задании в качестве аргумента имени модуля, dir() перечислит имена, определенные в этом модуле:

>>> import mod
>>> dir(mod)
['Foo', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__', 'a', 'foo', 's']
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']
>>> from mod import *
>>> dir()
['Foo', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'a', 'foo', 's']

Выполнение модуля как скрипта    ↑

Любой файл с расширением .py, содержащий модуль, по существу является скриптом Python и нет никаких причин, по которым он не может быть выполнен самостоятельно.

Здесь снова есть mod.py как это было определено выше:
mod.py

s = "Когда говорит товарищ Наполеон, то это должно быть правильно."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

Этот скрипт можно запустить:

C:\Users\john\Documents>python mod.py
C:\Users\john\Documents>

Там нет ошибок и, видимо, это сработало, но, конечно, не очень интересно. Так, как здесь написано, только определяет объекты, но ничего не делает с ними и не генерирует никакого вывода.

Давайте изменим вышеупомянутый модуль Python, чтобы он генерировал некоторый вывод при запуске в виде скрипта:

mod.py

s = "Когда говорит товарищ Наполеон, то это должно быть правильно."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

print(s)
print(a)
foo('quux')
x = Foo()
print(x)

Теперь должно быть немного интереснее:

C:\Users\john\Documents>python mod.py
Когда говорит товарищ Наполеон, то это должно быть правильно.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x02F101D0>

К сожалению, теперь он также генерирует вывод при импорте в виде модуля:

>>> import mod
Когда говорит товарищ Наполеон, то это должно быть правильно.
[100, 200, 300]
arg = quux
<mod.Foo object at 0x0169AD50>

Это, вероятно, не то, что вы хотите. Обычно модуль генерирует выходные данные при импорте.

Было бы неплохо, если бы можно было различать, когда файл загружается как модуль и когда он запускается как отдельный скрипт?

Просите, и дано вам будет.

Когда файл .py импортируется как модуль, Python устанавливает специальную переменную dunder в значение имени модуля __name__. Однако, если файл запускается как отдельный скрипт, то __name__ (творчески) устанавливается в строку '__main__'. Используя этот факт, можно определить, что происходит во время выполнения, и соответственно изменить поведение:

mod.py

s = "Когда говорит товарищ Наполеон, то это должно быть правильно."
a = [100, 200, 300]

def foo(arg):
    print(f'arg = {arg}')

class Foo:
    pass

if (__name__ == '__main__'):
    print('Executing as standalone script')
    print(s)
    print(a)
    foo('quux')
    x = Foo()
    print(x)

Теперь, если вы запускаете его, как скрипт, то получите результат:

C:\Users\john\Documents>python mod.py
Executing as standalone script
Когда говорит товарищ Наполеон, то это должно быть правильно.
[100, 200, 300]
arg = quux
<__main__.Foo object at 0x03450690>

Но если вы импортируете его, как модуль, то:

>>> import mod
>>> mod.foo('grault')
arg = grault

Модули часто разрабатываются с возможностью запуска в качестве отдельного сценария для тестирования функциональности, содержащейся в модуле. Это называется модульное тестирование. Например, вы создали модуль fact.py, где вычисляете factorial следующим образом:

fact.py

def fact(n):
    return 1 if n == 1 else n * fact(n-1)

if (__name__ == '__main__'):
    import sys
    if len(sys.argv) > 1:
        print(fact(int(sys.argv[1])))

Файл может рассматриваться как модуль, а функция fact() импортируется:

>>> from fact import fact
>>> fact(6)
720

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

C:\Users\john\Documents>python fact.py 6
720

Перезагрузка модуля    ↑

Из соображений эффективности модуль загружается только один раз за сеанс интерпретатора. Это хорошо для определений функций и классов, которые обычно составляют основную часть содержимого модуля. Но модуль также может содержать исполняемые операторы, обычно для инициализации. Помните, что эти операторы будут выполняться только при первом импорте модуля.

Рассмотрим следующий файл mod.py:

mod.py

a = [100, 200, 300]
print('a =', a)
>>> import mod
a = [100, 200, 300]
>>> import mod
>>> import mod

>>> mod.a
[100, 200, 300]

Оператор print() не выполняется при последующем импорте. (В этом отношении ни один из них не является оператором присваивания, но, как показывает окончательное отображение значения mod.a, это не имеет значения. Как только присвоение выполнено, оно придерживается.)

Если вы вносите изменения в модуль и вам необходимо его перезагрузить, вам нужно либо перезапустить интерпретатор, либо использовать функцию с именем reload() из модуля importlib:

>>> import mod
a = [100, 200, 300]

>>> import mod

>>> import importlib
>>> importlib.reload(mod)
a = [100, 200, 300]
<module 'mod' from 'C:\\Users\\john\\Documents\\Python\\doc\\mod.py'>

Пакеты Python    ↑

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

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

Создать пакет довольно просто, поскольку он использует внутреннюю иерархическую структуру файлов операционной системы. Рассмотрим следующую договоренность:

Пакет
Пакет

Здесь есть каталог с именем pkg, который содержит два модуля: mod1.py и mod2.py. Содержимое модулей:

mod1.py

def foo():
    print('[mod1] foo()')

class Foo:
    pass

mod2.py

def bar():
    print('[mod2] bar()')

class Bar:
    pass

Учитывая эту структуру, если каталог pkg находится в месте, где его можно найти (в одном из каталогов, содержащихся в sys.path), вы можете обратиться к двум модули с точечной нотацией (pkg.mod1, pkg.mod2) и импортируйте их с уже использованном синтаксисом:

import <module_name>[, <module_name> ...]
>>> import pkg.mod1, pkg.mod2
>>> pkg.mod1.foo()
[mod1] foo()
>>> x = pkg.mod2.Bar()
>>> x
<pkg.mod2.Bar object at 0x033F7290>
from <module_name> import <name(s)>
>>> from pkg.mod1 import foo
>>> foo()
[mod1] foo()
from <module_name> import <name> as <alt_name>
>>> from pkg.mod2 import Bar as Qux
>>> x = Qux()
>>> x
<pkg.mod2.Bar object at 0x036DFFD0>

Вы также можете импортировать модули с этими операторами:

from <package_name> import <modules_name>[, <module_name> ...]
from <package_name> import <module_name> as <alt_name>
>>> from pkg import mod1
>>> mod1.foo()
[mod1] foo()

>>> from pkg import mod2 as quux
>>> quux.bar()
[mod2] bar()

Технически вы также можете импортировать пакет:

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

Но это мало что дает. Хотя, строго говоря, это синтаксически правильный оператор Python, он не делает ничего полезного. В частности, он не помещает ни один из модулей в локальное пространство имен pkg:

>>> pkg.mod1
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    pkg.mod1
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod1.foo()
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    pkg.mod1.foo()
AttributeError: module 'pkg' has no attribute 'mod1'
>>> pkg.mod2.Bar()
Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    pkg.mod2.Bar()
AttributeError: module 'pkg' has no attribute 'mod2'

Чтобы фактически импортировать модули или их содержимое, вам нужно использовать одну из форм, показанных выше.

Инициализация пакета    ↑

Если файл с именем __init__.py присутствует в каталоге пакета, он вызывается при импорте пакета или модуля в пакете. Так, что можно использовать его для выполнения кода инициализации пакета, например, для инициализации данных уровня пакета.

Например, следующий файл __init__.py:
__init__.py

print(f'Invoking __init__.py for {__name__}')
A = ['quux', 'corge', 'grault']

Давайте добавим этот файл в каталог pkg из приведенного выше примера:

Архитектура пакета
Архитектура пакета

Теперь, когда пакет импортирован, глобальный список A инициализируется:

>>> import pkg
Invoking __init__.py for pkg
>>> pkg.A
['quux', 'corge', 'grault']

Модуль в пакете может обращаться к глобальным переменным, импортируя их по очереди:

mod1.py

def foo():
    from pkg import A
    print('[mod1] foo() / A = ', A)

class Foo:
    pass
>>> from pkg import mod1
Invoking __init__.py for pkg
>>> mod1.foo()
[mod1] foo() / A =  ['quux', 'corge', 'grault']

__init__.py также можно использовать для автоматического импорта модулей из пакета. Например, ранее вы видели, что оператор import pkg помещает только имя pkg в таблицу локальных определений вызвавшего кода и не импортирует какие-либо модули. Но если __init__.py в каталоге pkg содержит следующее:

__init__.py

print(f'Invoking __init__.py for {__name__}')
import pkg.mod1, pkg.mod2

затем при выполнении import pkg модули mod1 и mod2 импортируются автоматически:

>>> import pkg
Invoking __init__.py for pkg
>>> pkg.mod1.foo()
[mod1] foo()
>>> pkg.mod2.bar()
[mod2] bar()

Примечание:

Большая часть документации Python гласит, что файл __init__.py должен присутствовать в каталоге пакета при его создании. Когда-то это было правдой. Раньше было так, что само присутствие __init__.py означало, что для Python пакет определен. Файл может содержать код инициализации или даже быть пустым, но он должен быть.

Начиная с Python 3.3, были представлены неявные пакеты пространства имен. Они позволяют создавать пакеты без какого-либо файла __init__.py. Конечно, он может быть, если требуется инициализация пакета. Но теперь это не обязательно.

Импорт * из пакета    ↑

Для дальнейшего обсуждения расширим ранее определенный пакет и теперь он содержать некоторые дополнительные модули:

Иллюстрация иерархической структуры файлов пакета
Иллюстрация иерархической структуры файлов пакета

В каталоге pkg теперь расположены 4 файла:

mod1.py

def foo():
    print('[mod1] foo()')

class Foo:
    pass

mod2.py

def bar():
    print('[mod2] bar()')

class Bar:
    pass

mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass

mod4.py

def qux():
    print('[mod4] qux()')

class Qux:
    pass

(Необычно, не так ли?)

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

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg.mod3 import *

>>> dir()
['Baz', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'baz']
>>> baz()
[mod3] baz()
>>> Baz
<class 'pkg.mod3.Baz'>

Аналогичное утверждение для пакета таково:

from <package_name> import *

Что это значит?

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

Хммм. Немного. Вы могли ожидать (предполагая, что у вас были какие-то ожидания), что Python погрузится в каталог пакетов, найдет все модули и импортирует их все. Но, как вы видите, по умолчанию это не то, что происходит.

Вместо этого Python следует этому соглашению: если файл __init__.py в каталоге package содержит список с именем __all__, который принимается за список модулей, которые должны быть импортированы, когда встречается оператор from <package_name> import *.

В данном примере Предположим, что вы создаете __init__.py в каталоге pkg вот так:

pkg/__init__.py

__all__ = [
        'mod1',
        'mod2',
        'mod3',
        'mod4'
        ]

Теперь from pkg import * импортирует все четыре модуля:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'mod1', 'mod2', 'mod3', 'mod4']
>>> mod2.bar()
[mod2] bar()
>>> mod4.Qux
<class 'pkg.mod4.Qux'>

Использование import * по-прежнему не считается потрясающей формой, пригодной больше для пакетов, чем для модулей. Но эта возможность, по крайней мере, дает создателю пакета некоторый контроль над тем, что происходит, когда указан import *. (На самом деле, он предоставляет возможность полностью его запретить, просто отказываясь вообще определять __all__. Как вы видели, поведение пакетов по умолчанию — ничего не импортировать.)

Кстати, __all__ также можно определить в модуле и служит для той же цели: контролировать то, что импортируется с помощью import *. Например, измените mod1.py следующим образом:

pkg/mod1.py

__all__ = ['foo']

def foo():
    print('[mod1] foo()')

class Foo:
    pass

Теперь оператор import * из pkg.mod1 будет импортировать только то, что содержится в __all__:

>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

>>> from pkg.mod1 import *
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'foo']

>>> foo()
[mod1] foo()
>>> Foo
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    Foo
NameError: name 'Foo' is not defined

Функция foo() теперь определена в локальном пространстве имён, однако, класс Foo нет так, как его описание отсутствует в __all__.

Таким образом, __all__ используется как пакетами, так и модулями для управления тем, что импортируется оператором import *. Но поведение по умолчанию отличается:

  • Для пакетов, когда __all__ не определено, import * не импортирует ничего./li>
  • Для модулей, когда __all__ не определено, import * импортируется всё, кроме имён, как вы уже догадались, начиающихся с подчёруивания.

Подпакеты    ↑

Пакеты могут содержать вложенные подпакеты и глубина вложенности не ограничена. Например, изменим каталог package следующим образом:

Иллюстрация иерархии подпакетов
Иллюстрация иерархии подпакетов

Четыре модуля (mod1.py, mod2.py, mod3.py и mod4.py) определены как ранее. Но теперь вместо объединения в каталог pkg они разделены на два каталога подпакетов: sub_pkg1 и sub_pkg2.

Импорт по-прежнему работает так же, как показано ранее. Синтаксис аналогичен, но дополнительная точечная нотация используется для отделения имени пакета от имени подпакета:

>>> import pkg.sub_pkg1.mod1
>>> pkg.sub_pkg1.mod1.foo()
[mod1] foo()

>>> from pkg.sub_pkg1 import mod2
>>> mod2.bar()
[mod2] bar()

>>> from pkg.sub_pkg2.mod3 import baz
>>> baz()
[mod3] baz()

>>> from pkg.sub_pkg2.mod4 import qux as grault
>>> grault()
[mod4] qux()

Кроме того, модуль в одном подпакете может ссылаться на объекты в дочернем подпакете (в том случае, если один из братьев содержит некоторые необходимые вам функции). Например, предположим, что вы хотите импортировать и выполнить функцию foo() (определенную в модуле mod1) из модуля mod3. Вы можете использовать абсолютный импорт:

pkg/sub__pkg2/mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass

from pkg.sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
[mod1] foo()
>>> mod3.foo()
[mod1] foo()

Илиможно использовать относительный импорт, когда .. относится к пакету на уровень вые. Из mod3.py, который находится в подпакете sub_pkg2,

  • .. находит родительский пакет (pkg) и
  • ..sub_pkg1 переходит в подпакет sub_pkg1 родительского пакета.

pkg/sub__pkg2/mod3.py

def baz():
    print('[mod3] baz()')

class Baz:
    pass

from .. import sub_pkg1
print(sub_pkg1)

from ..sub_pkg1.mod1 import foo
foo()
>>> from pkg.sub_pkg2 import mod3
<module 'pkg.sub_pkg1' (namespace)>
[mod1] foo()

Заключение    ↑

В этом уроке вы узнали:

  • Как создать модуль Python.
  • Места, где интерпретатор Python ищет модуль.
  • Как получить доступ к объектам, определенном в модуле, используя оператор import.
  • Как создать модуль, который будет выполняться как отдельный скрипт
  • Как организовать модули в пакеты (packages) и подпакеты (subpackages).
  • Как управлять инициализацией пакета.

Надеемся, что теперь вы лучше понимаете, как получить доступ к функциям, доступным во многих сторонних и встроенных модулях для Python.

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

Если вы хотите узнать больше, обратитесь к следующей документации на Python.org:

Счастливого пайтонинга!

С использованием материалов Python Modules and Packages – An Introduction

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

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

Ваш адрес email не будет опубликован.