9.1. Теория¶
Модули и пакеты являются неотъемлемой частью модульного программирования - организации программы как совокупности небольших независимых блоков, структура и поведение которых подчиняются определенным правилам.
Разработка программы как совокупности модулей позволяет:
упростить задачи проектирования программы и распределения процесса разработки между группами разработчиков;
предоставить возможность обновления (замены) модуля, без необходимости изменения остальной системы;
упростить тестирование программы;
упростить обнаружение ошибок.
Программный код часто разбивается на несколько файлов, каждый из которых используется отдельно от остальных. Одним из методов написания модульных программ является объектно-ориентированное программирование.
9.1.1. Основные понятия¶
Модуль (англ. Module) - специальное средство языка программирования, позволяющее объединить вместе данные и функции и использовать их как одну функционально-законченную единицу (например, математический модуль, содержащий тригонометрические и прочие функции, константы \(\pi\), \(\epsilon\) и т.д.).
Пакеты (англ. Package) являются еще более крупной единицей и представляют собой набор взаимосвязанных модулей, предназначенных для решения задач определенного класса некоторой предметной области (например, пакет для решения систем уравнений, который может включать математический модуль, модуль со специальными типами данных и т.д.).
9.1.2. Модули и пакеты в Python¶
Модуль - отдельный файл с кодом на Python, содержащий функции и данные:
имеет расширение
*.py
(имя файла является именем модуля);может быть импортирован (подключен) (директива
import ...
);может быть многократно использован.
Пакеты в Python - это способ структуризации модулей. Пакет представляет собой папку, в которой содержатся модули и другие пакеты и обязательный файл __init.py__
, отвечающий за инициализацию пакета.
Так, например, пакет xml имеет следующую структуру:
C:\USERS\YURI\APPDATA\LOCAL\PROGRAMS\PYTHON\PYTHON35\LIB\xml
│ __init__.py Файл инициализации пакета xml
│
├───dom Вложенный пакет xml.dom
│ domreg.py
│ expatbuilder.py
│ minicompat.py
│ minidom.py
│ NodeFilter.py
│ pulldom.py
│ xmlbuilder.py
│ __init__.py
│
├───etree Вложенный пакет xml.etree
│ cElementTree.py
│ ElementInclude.py
│ ElementPath.py
│ ElementTree.py
│ __init__.py
│
├───parsers Вложенный пакет xml.parsers
│ expat.py
│ __init__.py
│
└───sax Вложенный пакет xml.sax
expatreader.py
handler.py
saxutils.py
xmlreader.py
_exceptions.py
__init__.py
где каждый модуль (или вложенный пакет) отвечает за свою часть реализации работы с XML-форматом, однако рассматривается как единое целое в виде пакета.
Одна из основных целей использования как модулей, так и пакетов - реализация модели пространства имен, позволяющей логически группировать и в то же время изолировать различные идентификаторы. Например, при наличии глобальной переменной author
в модуле A
и B
не произойдет конфликта, т.к. они находятся в разном пространстве имен: A.author
и B.author
соответственно.
9.1.2.1. Классификация¶
Все модули/пакеты в Python можно разделить на 4 категории:
Встроенные (англ. Built-in).
Модули, встроенные в язык и предоставляющие базовые возможности языка (написаны на языке Си).
К встроенным относятся как модули общего назначения (например,
math
илиrandom
), так и плаиформозависимые модули (например, модульwinreg
, предназначенный для работы с реестром ОС Windows, устанавливается только на соответствующей ОС).Список установленных встроенных модулей можно посмотреть следующим образом:
>>> import sys >>> >>> print(sys.builtin_module_names) # Встроенные модули ('_ast', '_bisect', '_codecs', '_codecs_cn', '_codecs_hk', '_codecs_iso2022', '_codecs_jp', '_codecs_kr', '_codecs_tw', '_collections', '_csv', '_datetime', '_functools', '_heapq', '_imp', '_io', '_json', '_locale', '_lsprof', '_md5', '_multibytecodec', '_opcode', '_operator', '_pickle', '_random', '_sha1', '_sha256', '_sha512', '_signal', '_sre', '_stat', '_string', '_struct', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', '_winapi', 'array', 'atexit', 'audioop', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'math', 'mmap', 'msvcrt', 'nt', 'parser', 'sys', 'time', 'winreg', 'xxsubtype', 'zipimport', 'zlib')
Стандартная библиотека (англ. Standard Library).
Сторонние (англ. 3rd Party).
Модули и пакеты, которые не входят в дистрибутив Python, и могут быть установлены из каталога пакетов Python (англ. PyPI - the Python Package Index, более 90.000 пакетов) с помощью утилиты
pip
:C:\Users\yuri>pip install vk Collecting vk Downloading vk-2.0.2.tar.gz Requirement already satisfied (use --upgrade to upgrade): requests<3.0,>=2.8 in c:\users\yuri\appdata\local\programs\python\python35\lib\site-packages (from vk) Installing collected packages: vk Running setup.py install for vk ... done Successfully installed vk-2.0.2
При установке пакета автоматически устанавливаются зависимые пакеты.
Пользовательские (собственные).
Модули и пакеты, создаваемые разработчиком.
Примечание
Создание собственных пакетов не рассматривается в рамках настоящего курса.
В собственной программе рекомендуется выполнять импорт именно в таком порядке: от встроенных до собственных модулей/пакетов.
9.1.2.2. Подключение и использование¶
Для использования модуля или пакета в коде необходимо его предварительно подключить (импортировать).
- import¶
Импорт модуля или пакета выполняется единожды инструкцией import
, располагаемой, как правило, в начале файла.
Выполнить подключение модуля можно несколькими способами:
# Способ №1
# В данном способе обратиться к члену модуля можно с указанием модуля,
# например, module_1.object_1
# Импортирует модуль 'module_1'
import module_1
# Импортирует модули 'module_1', 'module_2',..., 'module_n'
import module_1, module_2, ..., module_n
# Импортирует модуль 'module_1' под псевдонимом 'preferred_name'
import module_1 as preferred_name
# Способ №2
# В данном способе обратиться к члену модуля можно без указания модуля,
# например, object_1
# Импортирует 'object' под псевдонимом 'preferred_name' из модуля 'module_1'
from module_1 import object as preferred_name
# Импортирует объекты 'object_1', ..., 'object_n' из модуля 'module_1'
from module_1 import object_1, object_2, ..., object_n
# Импортирует все объекты из модуля 'module_1'
from module_1 import *
Пакет подключается аналогичным образом. Кроме того, имеется возможность импорта отдельных модулей из пакета, если нет необходимости использовать весь пакет:
# Импортирует пакет 'package_1'
import package_1
# Импортирует модуль 'module_1' из пакета 'package_1'
import package_1.module_1
Различные варианты подключения модуля и пакета приведены в Листингах 9.1.1 (а-г).
import math
# Использование любого элемента модуля math возможно через
# предварительное указание его имени: math.<...>
#
# Это - рекомендуемый способ импортирования модуля
print("sin(30) = {:.1f}".format(math.sin(math.radians(30)))) # sin(30) = 0.5
from math import sin
import math
# Доступ к функции sin() возможен без указания модуля, а
# к функции radians() только с указанием
#
# Данный способ рекомендуется использовать:
# - для повышения читаемости кода;
# - если вероятность конфликта имен с другими участками кода мала.
print("sin(30) = {:.1f}".format(sin(math.radians(30)))) # sin(30) = 0.5
from math import *
# Любую функцию из модуля math можно указывать без ссылки на модуль
#
# Данный способ, хотя и экономит место, НЕ рекомендуется в виду
# высокой вероятности конфликта имен (импортированные таким образом
# члены модуля могут совпасть по имени с другими объектами) и неоднозначности
# поиска идентификатора (откуда импортирован sin?)
print("sin(30) = {:.1f}".format(sin(radians(30)))) # sin(30) = 0.5
import json # импорт пакета целиком
import os.path # импорт модуля os.path из пакета os
# Разделитель пути для текущей ОС
print(os.path.sep) # "\"
Посмотреть содержимое модуля или пакета и справку по нему возможно с помощью функций dir()
и help()
:
>>> import math
>>>
>>> dir(math)
['__doc__', '__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', 'trunc']
>>>
>>> help(math)
Help on built-in module math:
NAME
math
DESCRIPTION
This module is always available. It provides access to the
mathematical functions defined by the C standard.
FUNCTIONS
...
9.1.2.3. Область поиска¶
При импорте модуля или пакета Python выполняет его поиск в следующем порядке:
Встроенный модуль?
Файл
module.py
/module.pyc
, или пакетpackage
есть в списке путей переменнойsys.path
?sys.path
- список, при инициализации включающий:рабочую директорию скрипта (основного модуля);
переменную окружения
PYTHONPATH
и пути инсталляции Python.
Если модуль не удается найти, возбуждается исключение ModuleNotFoundError
. При ошибке загрузки существующего модуля - ImportError
.
Примечание
Для проверки содержимого sys.path
можно выполнить следующий код:
>>> import sys
>>>
>>> print(sys.path)
['', 'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\python35.zip',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\DLLs',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\lib',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\lib\\site-packages',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\lib\\site-packages\\win32',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\lib\\site-packages\\win32\\lib',
'C:\\Users\\yuri\\AppData\\Local\\Programs\\Python\\Python35\\lib\\site-packages\\Pythonwin']
9.1.3. Особенности модулей в Python¶
9.1.3.1. Специальные атрибуты¶
Каждый модуль имеет специальные и дополнительные атрибуты.
Специальные атрибуты содержат системную информацию о модуле (путь запуска, имя модуля и др.) и доступны всегда. Некоторые из них:
Дополнительные (необязательные) атрибуты могут содержать справочную информацию об авторе, версии модуля и т.д. и имеют следующие обозначения:
__author__
,__copyright__
,__credits__
,__license__
,__version__
,__maintainer__
,__email__
,__status__
.
9.1.3.2. Кэширование («компиляция») модулей¶
Python считается интерпретируемым языком, однако имеет гибридную составляющую: при запуске программы происходит компиляция в промежуточный байт-код (бинарный файл с расширением *.pyc
) всех импортированных модулей, после чего код выполняется виртуальной машиной (Рисунок 9.1.1).
Файл *.pyc
создается заново только в случае, если исходный код был изменен (проверяется дата изменения), иначе используется уже найденная «скомпилированная» версия. При этом компилируются только импортируемые модули (так, текущий запускаемый модуль скомпилирован не будет).
На Рисунке 9.1.2 приведена общая схема загрузки модуля.
Особенности *.pyc
-файлов:
имеют бинарный формат;
загружаются быстрее, чем исходные
*.py
-файлы; при этом скорость выполнения одинакова в обоих случаях;создаются в папке
__pycache__
и имеют имяимя_модуля.cpython-35.pyc
(для реализации CPython), где35
- версия Python;«привязаны» к версии интерпретатора (обязательно в пределах основной версии - 2 или 3) и платформе компиляции.
Примечание
«Скомпилированные» файлы могут также предоставляться вместо исходных, например, когда нужно скрыть код реализации.
В то же время, применяется это не часто, т.к. в отличие от языков программирования, компилируемых непосредственно в машинный код (например, Си), для языков, использующих байт-код (Java, C#, Python и др.), высока вероятность полной декомпиляции (воссоздания исходного кода) скомпилированной программы.
9.1.3.3. Собственные модули¶
Python предоставляет возможность создания собственных модулей.
Примечание
PEP8.
Соглашение рекомендует использовать:
змеиный_регистр (англ. snake_case) для наименования модулей:
my_module
;
Для создания собственных модулей нет особенных правил - любой файл с расширением *.py
является модулем. Пример создания и подключения собственного модуля приведен в Листинге 9.1.2 (а)-(б).
""" Модуль для работы с числами Фибоначчи.
- список чисел Фибоначчи, не превышающих 'n';
- проверка на вхождение в ряд Фибоначчи;
- ...
"""
def list_le_than(n):
"""Вернуть список чисел Фибоначчи, не превышающих 'n'."""
assert isinstance(n, int) and n > 0
a, b = 1, 1
result = [a]
while b <= n:
result.append(b)
a, b = b, a + b
return result
def is_in_row(n):
"""Вернуть True, если 'n' - число Фибоначчи."""
return n in list_le_than(n)
import fibonacci
num = 20
print("Числа до {}: {}".format(num, fibonacci.list_le_than(num))) # [1, 1, 2, 3, 5, 8, 13]
print("{} входит: {}".format(num, fibonacci.is_in_row(num))) # False
Примечание
Основной модуль программы, как правило, удобно называть main.py
.
9.1.3.4. Использование модуля: запуск или импорт¶
Любой модуль в Python может быть:
запущен автономно (как скрипт, например, в командной строке или через IDE);
импортирован (через
import
).
При написании собственного модуля необходимо предполагать, будет он запускаться автономно или использоваться для импорта. При этом следует избегать обособленного кода, который выполнялся бы в обоих вариантах (Листинг 9.1.3 (а)-(б)).
""" Модуль для работы с числами Фибоначчи.
- список чисел Фибоначчи, не превышающих 'n';
- проверка на вхождение в ряд Фибоначчи;
- ...
"""
def list_le_than(n):
"""Вернуть список чисел Фибоначчи, не превышающих 'n'."""
assert isinstance(n, int) and n > 0
a, b = 1, 1
result = [a]
while b <= n:
result.append(b)
a, b = b, a + b
return result
def is_in_row(n):
"""Вернуть True, если 'n' - число Фибоначчи."""
return n in list_le_than(n)
print(list_le_than(10)) # Этот код выполнится, как при запуске, так и при импорте
import fibonacci # Здесь будет "неожиданный" вывод на экран: [1, 1, 2, 3, 5, 8]
num = 20
print("Числа до {}: {}".format(num, fibonacci.list_le_than(num))) # [1, 1, 2, 3, 5, 8, 13]
print("{} входит: {}".format(num, fibonacci.is_in_row(num))) # False
Чтобы выполнить различный код в зависимости от того, запущен модуль или импортирован, достаточно использовать специальный идентификатор __name__
, который содержит:
имя модуля, если он был импортирован (например,
"fibonacci"
или"math"
);специальное наименование
"__main__"
, если модуль был запущен автономно (Листинг 9.1.4 (а)-(б)).
""" Модуль для работы с числами Фибоначчи.
- список чисел Фибоначчи, не превышающих 'n';
- проверка на вхождение в ряд Фибоначчи;
- ...
"""
def list_le_than(n):
"""Вернуть список чисел Фибоначчи, не превышающих 'n'."""
assert isinstance(n, int) and n > 0
a, b = 1, 1
result = [a]
while b <= n:
result.append(b)
a, b = b, a + b
return result
def is_in_row(n):
"""Вернуть True, если 'n' - число Фибоначчи."""
return n in list_le_than(n)
if __name__ == "__main__":
print("Данный код выполняется т.к. модуль был запущен автономно (как скрипт) и не будет выполнен при импорте.")
print("Тест функций:")
print("- list_le_than(10) =", list_le_than(10))
print("- is_in_row(10) =", is_in_row(10))
C:\>python fibonacci.py
Данный код выполняется т.к. модуль был запущен автономно (как скрипт) и не будет выполнен при импорте.
Тест функций:
- list_le_than(10) = [1, 1, 2, 3, 5, 8]
- is_in_row(10) = False
C:\>python
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import fibonacci
>>> fibonacci.list_le_than(20) # Нет вывода как выше
[1, 1, 2, 3, 5, 8, 13]
Как правило, проверка __name__
используется в основном файле программы, а вспомогательные модули избавляются от кода вне функций и/или классов (Листинг 9.1.5 (а)-(б)).
""" Модуль для работы с числами Фибоначчи.
- список чисел Фибоначчи, не превышающих 'n';
- проверка на вхождение в ряд Фибоначчи;
- ...
"""
def list_le_than(n):
"""Вернуть список чисел Фибоначчи, не превышающих 'n'."""
assert isinstance(n, int) and n > 0
a, b = 1, 1
result = [a]
while b <= n:
result.append(b)
a, b = b, a + b
return result
def is_in_row(n):
"""Вернуть True, если 'n' - число Фибоначчи."""
return n in list_le_than(n)
import fibonacci
if __name__ == "__main__":
num = 20
print("Числа до {}: {}".format(num, fibonacci.list_le_than(num))) # [1, 1, 2, 3, 5, 8, 13]
print("{} входит: {}".format(num, fibonacci.is_in_row(num))) # False
else:
# else добавлять не нужно, если никакие специальные действия
# в случае неавтономного запуска не планируется
print("Данный модуль не предназначен для импорта.")