Это пример того, как надо начинать писать статью

«Бизнес-информатика - это круто!»

Это пример того, как надо начинать писать статью

Содержание

Здесь вы познакомитесь с модулями (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) и импортируйте их с уже используемым pyfrjvsv синтаксисом:

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>

You can import modules with these statements as well:

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()

You can technically import the package as well:

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

But this is of little avail. Though this is, strictly speaking, a syntactically correct Python statement, it doesn’t do much of anything useful. In particular, it does not place any of the modules in pkg into the local namespace:

>>> 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'

To actually import the modules or their contents, you need to use one of the forms shown above.

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

If a file named __init__.py is present in a package directory, it is invoked when the package or a module in the package is imported. This can be used for execution of Инициализация пакета code, such as initialization of package-level data.

For example, consider the following __init__.py file:

__init__.py

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

Let’s add this file to the pkg directory from the above example:

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

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

Now when the package is imported, the global list A is initialized:

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

A module in the package can access the global variable by importing it in turn:

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 can also be used to effect automatic importing of modules from a package. For example, earlier you saw that the statement import pkg only places the name pkg in the caller’s local symbol table and doesn’t import any modules. But if __init__.py in the pkg directory contains the following:

__init__.py

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

then when you execute import pkg, modules mod1 and mod2 are imported automatically:

>>> 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__']

Hmph. Not much. You might have expected (assuming you had any expectations at all) that Python would dive down into the package directory, find all the modules it could, and import them all. But as you can see, by default that is not what happens.

Instead, Python follows this convention: if the __init__.py file in the package directory contains a list named __all__, it is taken to be a list of modules that should be imported when the statement from <package_name> import * is encountered.

For the present example, suppose you create an __init__.py in the pkg directory like this:

pkg/__init__.py

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

Now from pkg import * imports all four modules:

>>> 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'>

Using import * still isn’t considered terrific form, any more for packages than for modules. But this facility at least gives the creator of the package some control over what happens when import * is specified. (In fact, it provides the capability to disallow it entirely, simply by declining to define __all__ at all. As you have seen, the default behavior for packages is to import nothing.)

By the way, __all__ can be defined in a module as well and serves the same purpose: to control what is imported with import *. For example, modify mod1.py as follows:

pkg/mod1.py

__all__ = ['foo']

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

class Foo:
    pass

Now an import * statement from pkg.mod1 will only import what is contained in __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() (the function) is now defined in the local namespace, but Foo (the class) is not, because the latter is not in __all__.

In summary, __all__ is used by both packages and modules to control what is imported when import * is specified. But the default behavior differs:

  • For a package, when __all__ is not defined, import * does not import anything.
  • For a module, when __all__ is not defined, import * imports everything (except—you guessed it—names starting with an underscore).

Subpackages

Packages can contain nested subpackages to arbitrary depth. For example, let’s make one more modification to the example package directory as follows:

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

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

The four modules (mod1.py, mod2.py, mod3.py and mod4.py) are defined as previously. But now, instead of being lumped together into the pkg directory, they are split out into two subpackage directories, sub_pkg1 and sub_pkg2.

Importing still works the same as shown previously. Syntax is similar, but additional dot notation is used to separate package name from subpackage name:

>>> 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()

In addition, a module in one subpackage can reference objects in a sibling subpackage (in the event that the sibling contains some functionality that you need). For example, suppose you want to import and execute function foo() (defined in module mod1) from within module mod3. You can either use an absolute import:

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()

Or you can use a relative import, where .. refers to the package one level up. From within mod3.py, which is in subpackage sub_pkg2,

  • .. evaluates to the parent package (pkg), and
  • ..sub_pkg1 evaluates to subpackage sub_pkg1 of the parent package.

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

Tags: ,

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *