Модули и пакеты
Что такое модули, как их импортировать в программу, а также как создавать собственные модули, было описано в одном из уроков курса "Python. Введение в программирование". Там модули рассматривались с точки зрения обособления функций, которые потом можно было бы импортировать в разные программы. На самом деле модули содержат не столько функции, сколько классы с их методами.
В этом уроке шагнем дальше и рассмотрим, как несколько модулей-файлов могут быть объединены в пакет. Также выясним, что модули могут исполняться как самостоятельные программы.
Пакеты модулей
В программировании связанные модули принято объединять в пакеты. Пакет представляет собой каталог с файлами-модулями. Кроме того, внутри пакета могут быть вложенные каталоги, а уже в них – файлы.
Допустим, мы пишем пакет модулей для вычисления площадей и периметров фигур. Пакет будет состоять из двух модулей. В одном будут описаны классы двумерных фигур, в другом – трехмерных.
Каталог-пакет назовем geometry. Один модуль – planimetry.py, другой – stereometry.py. Пакет следует разместить в одном из каталогов, содержащихся в списке sys.path
. Первым его элементом является домашний каталог, обозначаемый как пустая строка. Таким образом, пакет проще разместить в том же каталоге, где будет основной скрипт.
Если не планируется писать скрипт, а достаточно протестировать пакет в интерактивном режиме, то в Linux будет проще разместить его в домашнем каталоге.
Содержимое файла planimetry.py:
from math import pi, pow class Rectangle: def __init__(self, a, b): self.w = a self.h = b def square(self): return round(self.w * self.h, 2) def perimeter(self): return 2 * (self.w + self.h) class Circle: def __init__(self, radius): self.r = radius def square(self): return round(pi * pow(self.r, 2), 2) def length(self): return round(2 * pi * self.r)
Код файла stereometry.py:
from math import pi, pow class Cuboid: def __init__(self, a, b, c): self.length = a self.width = b self.height = c def S(self): sq = 2 * (self.length * self.width + self.length * self.height + self.width * self.height) return round(sq, 2) def V(self): v = self.length * self.width * self.height return round(v, 2) class Ball: def __init__(self, radius): self.r = radius def S(self): s = 4 * pi * pow(self.r, 2) return round(s, 2) def V(self): v = (4 / 3) * pi * pow(self.r, 3) return round(v, 2)
Также в каталоге пакета должен быть файл __init__.py, даже если этот файл будет пустым. Его наличие позволяет интерпретатору понять, что перед ним пакет, а не просто каталог. Файл __init__.py может быть не пустым, а содержать переменную, в которой перечислены модули, которые будут импортироваться командой from имя_пакета import *
, а также какой-либо инициирующий код, например, подключение к базе данных.
Теперь попробуем импортировать модули пакета:
>>> import geometry.planimetry as pl >>> import geometry.stereometry as st >>> a = pl.Rectangle(3, 4) >>> b = st.Ball(5) >>> a.square() 12 >>> b.V() 523.6
Если бы выполнялся импорт только пакета, мы не смогли бы обращаться к модулям:
>>> import geometry >>> b = geometry.stereometry.Ball(5) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'geometry' has no attribute 'stereometry'
Тогда возникает вопрос: в чем выгода пакетов, если все равно приходится импортировать модули индивидуально? Основной смысл заключается в структурировании пространств имен. Представьте, что есть разные пакеты, содержащие одноименные модули и классы. В таком случае точечная нотация через имя пакета, подпакета, модуля дает возможность пользоваться в программе одноименными сущностями из разных пакетов. Например, a.samename
и b.samename
.
Кроме того точечная нотация дает своего рода описание объекту. Например, выражения geometry.planimetry.House()
или geometry.stereometry.House()
говорят, что в первом случае будет создан двумерный объект-дом, во-втором – трехмерный. Такие имена несут больше информации об объекте, чем просто House()
.
Однако в файле __init__.py в переменной __all__
можно перечислить, какие модули будут импортироваться через from имя_пакета import *
:
__all__ = ['planimetry', 'stereometry']
После этого можно делать так:
>>> from geometry import * >>> b = stereometry.Ball(5) >>> a = planimetry.Circle(5)
Выполнение модуля как скрипта
В Python обычный файл-скрипт, или файл-программа, не отличается от файла-модуля почти ничем. Нет команд языка, которые бы "говорили", что вот это – модуль, а это – скрипт. Отличие заключается лишь в том, что обычно модули не содержат команды вызова функций и создания экземпляров в основной ветке. В модуле обычно происходит только определение классов и функций.
Однако возможности языка позволяют в модули помещать код, который будет выполняться, когда файл не импортируется, а сам передается интерпретатору как самостоятельная программа. Выглядит это примерно так:
class A: def __str__(self): return "A" if __name__ == "__main__": print(A())
То, что находится в теле if
, выполнится только в случае исполнения файла как скрипта. Но не при импорте.
pl@pl-desk:~$ python3 test.py A
Встроенный атрибут __name__
, представляющий собой переменную, есть у каждого файла. При импорте этой переменной присваивается имя модуля:
>>> import math >>> math.__name__ 'math' >>> planimetry.__name__ 'geometry.planimetry'
Однако когда файл исполняется как скрипт, значение __name__
становится равным строке "__main__"
. Это можно увидеть, если в код поместить print(__name__)
и выполнить файл как скрипт.
Таким образом, если __name__
равен "__main__"
, то выполняется код, вложенный в тело условного оператора. Обычно сюда помещают код для тестирования модуля в процессе разработки, а в готовый модуль – примеры, как пользоваться определенными здесь сущностями.
Практическая работа
В практической работе урока 7 "Композиция" требовалось разработать интерфейс взаимодействия с пользователем. Разнесите сам класс и интерфейс по разным файлам. Какой из них выполняет роль модуля, а какой – скрипта? Оба файла можно поместить в один каталог.
Курс с примерами решений практических работ:
pdf-версия