Модули

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

In [1]:
import math
math,type(math)
Out[1]:
(<module 'math' from '/usr/lib64/python3.6/lib-dynload/math.cpython-36m-x86_64-linux-gnu.so'>,
 module)

Модуль имеет своё пространство имён. Оператор import math вводит объект типа модуль math в текущее пространство имён. Имена, определённые в модуле, при этом в текущем пространстве имён не появляются - их нужно использовать как math.что_то. Функция dir возвращает список имён в модуле (как и в классе или объекте).

In [2]:
dir(math)
Out[2]:
['__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'acos',
 'acosh',
 'asin',
 'asinh',
 'atan',
 'atan2',
 'atanh',
 'ceil',
 'copysign',
 'cos',
 'cosh',
 'degrees',
 'e',
 'erf',
 'erfc',
 'exp',
 'expm1',
 'fabs',
 'factorial',
 'floor',
 'fmod',
 'frexp',
 'fsum',
 'gamma',
 'gcd',
 'hypot',
 'inf',
 'isclose',
 'isfinite',
 'isinf',
 'isnan',
 'ldexp',
 'lgamma',
 'log',
 'log10',
 'log1p',
 'log2',
 'modf',
 'nan',
 'pi',
 'pow',
 'radians',
 'sin',
 'sinh',
 'sqrt',
 'tan',
 'tanh',
 'tau',
 'trunc']
In [3]:
math.__doc__
Out[3]:
'This module is always available.  It provides access to the\nmathematical functions defined by the C standard.'
In [4]:
math.pi,math.exp
Out[4]:
(3.141592653589793, <function math.exp>)
In [5]:
math.exp(math.pi)
Out[5]:
23.140692632779267

Встроенные функции, классы и т.д. языка питон живут в модуле builtins.

In [6]:
import builtins
dir(builtins)
Out[6]:
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'DeprecationWarning',
 'EOFError',
 'Ellipsis',
 'EnvironmentError',
 'Exception',
 'False',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'FutureWarning',
 'GeneratorExit',
 'IOError',
 'ImportError',
 'ImportWarning',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'KeyboardInterrupt',
 'LookupError',
 'MemoryError',
 'ModuleNotFoundError',
 'NameError',
 'None',
 'NotADirectoryError',
 'NotImplemented',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PendingDeprecationWarning',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'ResourceWarning',
 'RuntimeError',
 'RuntimeWarning',
 'StopAsyncIteration',
 'StopIteration',
 'SyntaxError',
 'SyntaxWarning',
 'SystemError',
 'SystemExit',
 'TabError',
 'TimeoutError',
 'True',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'UnicodeWarning',
 'UserWarning',
 'ValueError',
 'Warning',
 'ZeroDivisionError',
 '__IPYTHON__',
 '__build_class__',
 '__debug__',
 '__doc__',
 '__import__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'abs',
 'all',
 'any',
 'ascii',
 'bin',
 'bool',
 'bytearray',
 'bytes',
 'callable',
 'chr',
 'classmethod',
 'compile',
 'complex',
 'copyright',
 'credits',
 'delattr',
 'dict',
 'dir',
 'divmod',
 'dreload',
 'enumerate',
 'eval',
 'exec',
 'filter',
 'float',
 'format',
 'frozenset',
 'get_ipython',
 'getattr',
 'globals',
 'hasattr',
 'hash',
 'help',
 'hex',
 'id',
 'input',
 'int',
 'isinstance',
 'issubclass',
 'iter',
 'len',
 'license',
 'list',
 'locals',
 'map',
 'max',
 'memoryview',
 'min',
 'next',
 'object',
 'oct',
 'open',
 'ord',
 'pow',
 'print',
 'property',
 'range',
 'repr',
 'reversed',
 'round',
 'set',
 'setattr',
 'slice',
 'sorted',
 'staticmethod',
 'str',
 'sum',
 'super',
 'tuple',
 'type',
 'vars',
 'zip']

Если Вам лень полностью писать имя модуля перед каждым использованием функции из него, можно использовать as и задать ему краткое имя.

In [7]:
import random as r
r
Out[7]:
<module 'random' from '/usr/lib64/python3.6/random.py'>
In [8]:
dir(r)
Out[8]:
['BPF',
 'LOG4',
 'NV_MAGICCONST',
 'RECIP_BPF',
 'Random',
 'SG_MAGICCONST',
 'SystemRandom',
 'TWOPI',
 '_BuiltinMethodType',
 '_MethodType',
 '_Sequence',
 '_Set',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_acos',
 '_bisect',
 '_ceil',
 '_cos',
 '_e',
 '_exp',
 '_inst',
 '_itertools',
 '_log',
 '_pi',
 '_random',
 '_sha512',
 '_sin',
 '_sqrt',
 '_test',
 '_test_generator',
 '_urandom',
 '_warn',
 'betavariate',
 'choice',
 'choices',
 'expovariate',
 'gammavariate',
 'gauss',
 'getrandbits',
 'getstate',
 'lognormvariate',
 'normalvariate',
 'paretovariate',
 'randint',
 'random',
 'randrange',
 'sample',
 'seed',
 'setstate',
 'shuffle',
 'triangular',
 'uniform',
 'vonmisesvariate',
 'weibullvariate']
In [9]:
[r.random() for i in range(10)]
Out[9]:
[0.09637772172557402,
 0.89551664168298,
 0.9426823013506337,
 0.4094328234976712,
 0.7232184697800689,
 0.8586383884595843,
 0.2778551445357017,
 0.4321903116808209,
 0.30183017632492515,
 0.3057854562362986]

Такая форма оператора import вводит перечисленные имена (функции, переменные, классы) из модуля в текущее пространство имён. Мне она нравится - использовать импортированные таким образом объекты удобно, не надо писать перед каждым имя модуля.

In [10]:
from sys import path

Переменная path - это список имён директорий, в которых оператор import ищет модули. В начале в него входит '' - директория, в которой находится текущая программа (или текущая директория в случае интерактивной сессии); директории, перечисленные в переменной окружения PYTHONPATH (если такая переменная есть); и стандартные директории для данной версии питона. Но это обычный список, его можно менять стандартными языковыми средствами. Например, ревнители безопасности считают, что опасно включать текущую директорию в path - если пользователю в его директорию кто-нибудь подсунет зловредную версию math.py, а программа пользователя выполнит import math, то этот модуль выполнится, и может, скажем, удалить все файлы этого пользователя. Такие ревнители могут сделать path=path[1:].

In [11]:
path
Out[11]:
['',
 '/usr/lib64/python36.zip',
 '/usr/lib64/python3.6',
 '/usr/lib64/python3.6/lib-dynload',
 '/usr/lib64/python3.6/site-packages',
 '/usr/lib64/python3.6/site-packages/IPython/extensions',
 '/home/grozin/.ipython']
In [12]:
path.append('/home/grozin/python')
path
Out[12]:
['',
 '/usr/lib64/python36.zip',
 '/usr/lib64/python3.6',
 '/usr/lib64/python3.6/lib-dynload',
 '/usr/lib64/python3.6/site-packages',
 '/usr/lib64/python3.6/site-packages/IPython/extensions',
 '/home/grozin/.ipython',
 '/home/grozin/python']

Если Вам лень писать каждый раз длинное имя функции из модуля, можно дать ему короткий псевдоним.

In [13]:
from math import factorial as f
In [14]:
f(100)
Out[14]:
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Для самых ленивых есть оператор from ... import *, который импортирует в текущее пространство имён все имена, определённые в модуле. Обычно это плохая идея - Вы засоряете текущее пространство имён, и даже не знаете, чем. Такую форму импорта разумно использовать, когда Вы импортируете свой модуль, про который Вы всё знаете. Ну или в интерактивной сессии, когда Вы хотите попробовать всякие функции из какого-нибудь модуля. Но не в программе, которая пишется всерьёз и надолго.

Например, в текущей директории есть файл fac.py. Мы работаем в ipython, который предоставляет всякие удобства для интерактивной работы. Например, можно выполнить shell команду, если в начале строки поставить ! (только не пробуйте этого делать в обычном интерпретаторе питон). Так что легко распечатать этот файл. В нём определена одна функция fac.

In [15]:
!cat fac.py
#!/usr/bin/env python3
'В этом модуле определена функция fac'

def fac(n):
    'calculate factorial of n'
    assert type(n) is int and n >= 0
    r = 1
    for i in range(2, n + 1):
        r *= i
    return r

if __name__ == '__main__':
    from sys import argv, exit
    if len(argv) != 2:
        print('usage: ./fac.py n')
        exit(1)
    print(fac(int(argv[1])))
In [16]:
from fac import *
fac(10)
Out[16]:
3628800

Файл fac.py показывает типичное устройство любого файла на питоне. Первая строка позволяет запустить такой файл, если у него установлен бит, позволяющий исполнять его текущему пользователю. Почему не просто #!/usr/bin/python3 ? Потому что на некоторых машинах питон может быть в /usr/local/bin или ещё где-то; стандартная unix-утилита env (предположительно) всегда живёт в /usr/bin. Она позволяет установить какие-нибудь переменные окружения, а затем, если есть аргумент - имя программы, запускает эту программу в этом модифицированном окружении; если такого аргумента нет, просто печатает это окружение. Так что, вызвав просто env, Вы получите список всех текущих переменных окружения с их значениями. В данном случае вызывается /usr/bin/env python3, то есть никакие изменения окружения не произвадятся, и env вызывает python3, расположенный где угодно в $PATH. Почему python3? python может быть симлинком либо на python2, либо на python3; в свою очередь, python3 может быть симлинком, скажем, на python3.6. Если наша программа предназначена для питона 3, то в первой строке лучше указывать python3, иначе на некоторых машинах могут возникнуть неприятные сюрпризы.

Дальше следует док-строка модуля. Потом определения всех функций, классов и т.д. Заключительная часть файла выполняется, если он запущен как программа, а не импортируется куда-то. В этой части обычно пишут какие-нибудь простые тесты определённых в файле функций. В данном случае используется sys.argv - список строк-аргументов командной строки. argv[0] - это имя программы, нас интересует переданный ей параметр, argv[1].

In [17]:
import fac
fac.__doc__
Out[17]:
'В этом модуле определена функция fac'
In [18]:
help(fac)
Help on module fac:

NAME
    fac - В этом модуле определена функция fac

FUNCTIONS
    fac(n)
        calculate factorial of n

FILE
    /home/grozin/python/book/fac.py


Функция dir без аргумента возвращает список имён в текущем пространстве имён. Многие имена в этом списке определены ipython-ом; в сессии с обычным интерпретатором питон их бы не было.

In [19]:
dir()
Out[19]:
['In',
 'Out',
 '_',
 '_1',
 '_11',
 '_12',
 '_14',
 '_16',
 '_17',
 '_2',
 '_3',
 '_4',
 '_5',
 '_6',
 '_7',
 '_8',
 '_9',
 '__',
 '___',
 '__builtin__',
 '__builtins__',
 '__doc__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_dh',
 '_exit_code',
 '_i',
 '_i1',
 '_i10',
 '_i11',
 '_i12',
 '_i13',
 '_i14',
 '_i15',
 '_i16',
 '_i17',
 '_i18',
 '_i19',
 '_i2',
 '_i3',
 '_i4',
 '_i5',
 '_i6',
 '_i7',
 '_i8',
 '_i9',
 '_ih',
 '_ii',
 '_iii',
 '_oh',
 '_sh',
 'builtins',
 'exit',
 'f',
 'fac',
 'get_ipython',
 'math',
 'path',
 'quit',
 'r']

В локальном пространстве имён этой функции два имени.

In [20]:
def f(x):
    y=0
    print(dir())
In [21]:
f(0)
['x', 'y']

В каждом модуле есть строковая переменная __name__, она содержит имя модуля. Главная программа (или интерактивная сессия) тоже является модулем, его имя __main__. Этим и объясняется вид оператора if, который стоит в конце файла fac.py.

In [22]:
__name__
Out[22]:
'__main__'
In [23]:
r.__name__
Out[23]:
'random'

Модули не обязательно должны размещаться непосредственно в какой-нибудь директории из sys.path; они могут находиться в поддиректории. Например, в текущей директории (включённой в path) есть поддиректория d1, в ней поддиректория d2.

In [24]:
!ls d1
__pycache__  d2  m1.py
In [25]:
!ls d1/d2
__pycache__  m2.py

Мы можем импортировать модули m1 и m2 так.

In [26]:
import d1.m1
d1.m1.f1()
Out[26]:
1
In [27]:
import d1.d2.m2
d1.d2.m2.f2()
Out[27]:
2

Такое поддерево директорий с модулями можно превратить в пакет, который с точки зрения пользователя выглядит как единый модуль. Для этого нужно добавить файл __init__.py. Вот другое поддерево с теми же файлами m1.py и m2.py.

In [28]:
!ls p1
__init__.py  __pycache__  m1.py  p2
In [29]:
!ls p1/p2
__pycache__  m2.py

Только добавлен файл __init__.py.

In [30]:
!cat p1/__init__.py
'Пакет, экспортирующий f1 из модуля m1 и f2 из модуля m2'
from p1.m1 import f1
from p1.p2.m2 import f2

Теперь мы можем импортировать этот пакет.

In [31]:
import p1

Питон находит в sys.path директорию p1, содержащую __init__.py, и интерпретирует её как пакет. При импорте выполняется этот файл __init__.py, инициализирующий пакет. Все функции, переменные и т.д., определённые в этом файле (непосредственно или через импорт), становятся символами этого пакета. __init__.py может включать не все функции из модулей этого дерева директорий (и даже не все модули); символы, не определённые в __init__.py, недоступны после импорта пакета (конечно, пользователь всегда может импортировать любой модуль напрямую и получить доступ ко всем его символам).

In [32]:
p1.__doc__
Out[32]:
'Пакет, экспортирующий f1 из модуля m1 и f2 из модуля m2'
In [33]:
p1.f1(),p1.f2()
Out[33]:
(1, 2)