
Модули и пакеты
Работа с модулями. Объединение отдельных модулей в пакет. Пример использования модуля random
Штана Альберт Игоревич
Модули и пакеты
Для реальных приложений на рынке разработчики пишут сотни и тысячи строчек кода. Есть даже проекты, в которых несколько миллионов строчек. Подобный масштабный проект невозможно сохранить в одном файле, команда запутается в своем же коде, не сможет поддерживать и обновлять его. Поэтому файл с кодом нужно разбивать на части. Этот принцип применяется во многих языках программирования. В Python с этой целью используются модули.
В Python любой файл с кодом называется модулем — между этими терминами нет разницы. Чтобы всем Python-разработчикам было удобно читать готовый код, принято называть файлы в стиле snake_case: то есть с маленькой буквы и с разделением слов символом подчеркивания. Это легко запомнить: snake_case переводится как «змеиный регистр» и поэтому идеально сочетается с языком Python.
Как использовать модули
Работать с кодом в тысячи строчек намного проще, если он разбит на несколько модулей.
В таком случае обычно работают с главным файлом, а отдельные функции помещают в разные модули.
Затем модули импортируют в main.py с помощью ключевого слова import.
В итоге команда разработчиков получает код, который легко читать и обслуживать.
Чтобы попрактиковаться, попробуем создать свой модуль.
Для этого мы создадим файл с названием greeting.py.
Затем внутри этого файла определим функцию say_hi() и переменную name:
# file: greeting.py
def say_hi(): # определяем функцию
print('Hi!')
name = 'Alexander' # определяем переменную
Модуль-приветствие готов: он умеет печатать строку Hi! и обрабатывать переменную name.
Но от него нет никакой пользы, пока он не встроен в работу всей остальной программы.
Чтобы воспользоваться нашим модулем, нужно импортировать его в главный модуль main.py.
Для этого в Python есть три способа:
- Импорт модуля целиком;
- Импорт отдельных определений из модуля;
- Импорт всего содержимого модуля сразу;
Сначала рассмотрим только первый и второй способы.
Импорт модуля целиком
Это самый простой способ: используем ключевое слово import вместе с названием файла без расширения .py.
Перейдем в главный файл main.py и импортируем туда наш модуль greeting.py:
# file: main.py
import greeting
Импорт прошел успешно — у нас появился доступ к модулю-приветствию прямо из главного файла. Теперь к содержимому модуля можно обращаться «через точку». Так можно вызвать функцию модуля или отдельную переменную:
# вызываем функцию модуля
greeting.say_hi() # => Hi!
# выводим на экран отдельную переменную
print(greeting.name) # => Alexander
Это самый распространенный способ, потому что такой код легко читать. Любой разработчик с первого взгляда может определить, что используемая переменная или вызываемая функция — это часть какого-то конкретного модуля.
Импорт отдельных определений
Иногда первый способ не подходит — например, из длинного и сложного модуля вам нужна всего пара функций или переменных.
Тогда поможет второй способ импорта: нужно написать ключевое слово from с названием модуля без расширения .py.
Затем в той же строчке указываем ключевое слово import с названиями определений, которые хотим использовать.
Так это выглядит в коде:
# file: main.py
from greeting import say_hi, name # импортируем отдельные компоненты модуля
print(name) # используем импортированную переменную
say_hi() # вызываем импортированную функцию
Также при импорте можно указать новое имя для импортируемого компонента с помощью ключевого слова as:
# file: main.py
from greeting import say_hi as hello, name as first_name
print(first_name)
hello()
Во время импорта всего модуля можно обращаться к нему «через точку». Вот как выглядит такая запись: module.name. Для такой формы записи существует и более официальный термин — квалифицированное имя. Соответственно, импорт модуля целиком официально называется квалифицированным импортом. Стоит отметить, что в Python все строчки с import принято располагать в самом начале кода модуля. Такой набор строчек часто называют блоком импортов, хотя синтаксически этот блок никак не выделен — это обычные строчки одна за другой. Эта группировка повышает читаемость кода: так разработчикам проще искать ошибки в коде и исправлять их.
Импорт всего содержимого модуля
Рассмотрим третий вариант — импорт всего содержимого модуля. Так он выглядит в коде:
from some_module import *
from another_module import *
Здесь из модулей some_module и another_module неявно импортируются все определения. Такой способ импорта дает доступ к десяткам переменных, констант и функций, причем можно не указывать названия содержимого из всего модуля. Эта особенность отличает импорт содержимого от импорта модуля целиком — и она же может усложнять чтение кода. Посмотрим на примере. Представим программиста, который читает чужой код с импортом содержимого. В какой-то момент он встречает название незнакомой переменной и хочет узнать, откуда она взялась. Недостаточно просто внимательно посмотреть на блок импортов или сделать поиск по коду — названия всех импортированных определений скрываются за *. Именно поэтому в большинстве руководств по написанию кода на Python повторяется один и тот же совет: «Как можно реже пользуйтесь импортом всего содержимого». Тем не менее в реальном коде такие импорты встречаются, поэтому было важно упомянуть этот вариант.
Сочетание способов импорта
Чтобы углубить знания, рассмотрим еще один способ — сочетание импорта целиком и отдельными определениями в рамках одного и того же модуля.
Рассмотрим пример. В модуле computation.py определим функцию и переменные:
# file: computation.py
PI = 3.1415926
E = 2.7182818
def pi_times(x):
return x * PI
А в модуле main.py воспользуемся двумя способами импорта из модуля computation.py:
# file: main.py
import computation
from computation import PI, E
from computation import pi_times
print(PI)
print(computation.E)
print(pi_times(2))
print(computation.pi_times(E))
Из примеров выше видно, что можно:
- Использовать оба способа импорта одновременно;
- Импортировать отдельные определения в несколько заходов;
- Получать доступ «через точку» к определениям, которые уже импортированы по имени;
Пакеты
Обычно код в больших проектах делят на модули — отдельные файлы, в которых хранится код на Python. Но иногда и этого разделения недостаточно. Часто модули хочется сгруппировать «по смыслу» или сформировать отдельную группу модулей, чтобы использовать их в других проектах. В таких случаях помогают пакеты — группы модулей. В особенно больших проектах используются еще и подпакеты внутри пакетов, но на этой теме мы пока не будем останавливаться. Ниже разберемся, как создавать пакеты и добавлять в них модули. Также посмотрим, какими способами можно импортировать пакеты в проект.
Создание пакета
С точки зрения структуры пакет — это каталог (директория) с файлами модулей, имеющий имя в формате snake_case и содержащий специальный модуль с именем «__init__.py». Именно наличие этого специального файла подсказывает интерпретатору Python, что каталог следует воспринимать как пакет. Рассмотрим пример самого простого пакета. Создадим пакет из каталога package и модуля __init__.py внутри этого каталога:
package/
└── __init__.py
В файл __init__.py добавим следующий код:
# file __init__.py
NAME = 'super_package'
Теперь у нас есть небольшой, но уже полноценный пакет. Его можно импортировать так же, как модуль:
import package
print(package.NAME)
Обратите внимание, что отдельно импортировать файл __init__.py не нужно.
При первом обращении к пакету Python самостоятельно импортирует модуль __init__.py.
Это происходит автоматически, потому что каталог без init-файла не будет считаться пакетом.
Добавление модуля в пакет
С простым пакетом все ясно — его можно использовать как модуль. Теперь перейдем к группировке. Добавим в пакет еще два модуля:
package/
├── constants.py
├── functions.py
└── __init__.py
Чтобы было понятнее, рассмотрим содержимое модуля constants.py:
# file constants.py
PERSON = 'Alex'
И содержимое модуля functions.py:
# file functions.py
def greet(who):
print('Hello, ' + who + '!')
Теперь в пакете есть не только __init__.py, но и еще два модуля — теперь их можно импортировать.
Как импортировать пакеты
Про модули упоминались два распространенных варианта импорта:
- Квалифицированный импорт (также его называют «импорт модуля целиком»);
- Импорт отдельных определений.
Применим оба способа импорта — но теперь уже не к модулям, а к пакетам. Квалифицированный импорт помогает писать понятный код. Прочитав строчку вызова функции, другой разработчик сразу поймет, откуда пришли сама функция и ее аргумент. В этом примере квалифицированный импорт выглядит так:
import package.functions
import package.constants
package.functions.greet(package.constants.PERSON) # => Hello, Alex!
Импорт отдельных определений удобнее в работе, потому что вам не придется каждый раз прописывать имя пакета и модуля. С другой стороны, другим разработчикам будет сложнее читать код. Чтобы узнать, откуда пришли функция и константа, им придется смотреть в блок импортов. Попробуем импортировать отдельные определения, то есть саму функцию и аргумент:
from package.functions import greet
from package.constants import PERSON
greet(PERSON) # => Hello, Alex!
Кроме того, импорты в Python бывают двух видов:
- Абсолютные;
- Относительные.
Для понимания пакетов критически важно обсудить подробности этих двух видов импортов.
Абсолютный импорт
В абсолютном импорте нужно прописывать полный путь до модуля, включающий все пакеты и подпакеты. Полные пути гарантируют простоту чтения и однозначность — так всем будет понятно, что и откуда импортируется. Чтобы вам было удобнее читать код, во всех примерах выше использовался абсолютный импорт.
Относительный импорт
Относительные импорты выглядят так:
from . import module
from .module import function
from .subpackage.module import CONSTANT
В относительном импорте используется точка, которая означает импорт модуля из текущей директории.
Например, мы работаем с файлом main.py и хотим импортировать .module.
При этом мы знаем, что main.py и .module хранятся в одной и той же директории.
Тогда можно не прописывать абсолютный путь к .module, а воспользоваться . import.
По этой точке Python автоматически определит, что и откуда мы хотим импортировать.
Относительный импорт помогает писать быстрее, но слишком сильно запутывает код, что негативно сказывается на читаемости. Именно поэтому в сообществе Python-разработчиков есть распространенный совет для новичков: старайтесь пользоваться абсолютным импортом, даже в самых простых и очевидных случаях.
Модуль random
Python знаменит тем, что в нем доступно большое количество стандартных библиотек — так называют модули и пакеты, в которых уже реализованы тысячи разных функций. Разработчику полезно разбираться в этих стандартных библиотеках, ведь это знание позволяет экономить время и силы. Предлагаю для начала познакомимся с одной из стандартных библиотек — модулем random.
При разработке программ иногда возникает потребность сгенерировать случайное число. Для этого в Python можно использовать модуль random. Он предоставляет множество функций, но пока остановимся на двух:
randint— сгенерировать целое число в заданном диапазоне;choice— выбрать случайный элемент из заданного набора.
Генерация случайных чисел
Чтобы сгенерировать случайное число, нужно импортировать функцию randint из модуля random:
from random import randint
Для примера попробуем сгенерировать число от 1 до 100:
random_number = randint(1, 100)
Обратите внимание, что обе границы диапазона включены — значит, randint может выдать любое значение в диапазоне, в том числе 1 и 100.
Перейдем к более сложному примеру:
string = 'abcde'
random_index = randint(0, len(string) - 1)
char = string[random_index]
Здесь программа должна выбрать случайный символ из строки string. При этом:
- Строка в переменной string имеет длину 5;
- Индекс последнего элемента в строке равен 4;
- Символы строки индексируются с нуля.
Если мы попробуем сгенерировать число через randint(0, 5), то в какой-то момент получим значение 5.
Тогда программа выдаст ошибку IndexError: она не сможет выдать пятый символ из четырех.
Как это предотвратить? Нужно не просто задать верхнюю границу диапазона, а вычислить ее — то есть вычесть единицу из длины строки.
Именно так и сделано в примере кода выше.
Выбор случайного элемента
Выше мы рассмотрели пример, в котором выбирается случайный символ строки.
Эта задача возникает достаточно часто, поэтому в модуле random существует функция choice.
Если использовать эту функцию, выбор случайного символа из строки будет выглядеть так:
from random import choice
string = 'abcde'
char = choice(string)
Используя choice, не нужно думать о границах диапазона — функция сама определяет, как правильно выбирать элементы.
При этом важно, чтобы строка для выбора не была пустой.
Иначе мы получим ошибку IndexError: Cannot choose from an empty sequence т.е. «Нельзя выбрать из пустой строки».
В основе модуля random лежит генератор псевдослучайных чисел — на самом деле, число выбирается не случайно, а на основе сложных математических вычислений.
Из-за этого random не принято использовать в сферах, где нужна повышенная безопасность.
В криптографии, шифровании и других подобных сферах используют модуль secrets, в основе которого менее предсказуемый механизм случайной генерации.
Подробнее о модуле random почитайте по ссылке: Документация по модулю random.
Статья про генерацию случайных чисел.
Попробуйте сами запустить код в окне ниже с интерпретатором Python и повторите примеры из статьи чтобы самим увидеть и понять как всё это работает. Для этого в ячейке с кодом нажмите клавиши на клавиатуре Shift+Enter или запустите код через кнопку Run по значку ▶.