В одном из уроков курса "Введение в Python" уже рассматривались основы работы с модулями. Было объяснено, как импортировать модули, а также создавать свои собственные. В этом материале акцентировалось внимание на использовании функций, которые удобно импортировать в различные программы. Однако стоит отметить, что модули включают в себя в основном классы, а не только функции.
В этом уроке рассмотрим, как объединить несколько модулей в пакет, и как модули могут быть использованы как полноценные программы.
Пакеты модулей
В программировании принято объединять связанные модули в пакеты. Пакет — это своего рода каталог с файлами-модулями. Внутри пакета могут быть вложены и другие каталоги с добавочными файлами.
Для примера предположим, что вы разрабатываете пакет для вычисления площадей и периметров геометрических фигур. Пакет будет состоять из двух модулей: в первом будут классы для двумерных фигур, во втором — для трехмерных.
Назовем наш каталог-пакет geometry и создадим два модуля — planimetry.py и stereometry.py. Поместим пакет в один из каталогов, перечисленных в списке sys.path. Первая запись этого списка — домашний каталог, обозначенный пустой строкой. Таким образом, проще всего разместить пакет в том же каталоге, что и основной скрипт.
В случае, если скрипт не требуется, и есть необходимость проверить пакет в интерактивном режиме, в Linux удобнее разместить его в домашнем каталоге.
Содержимое файла planimetry.py:
from math import pi, pow class Rectangle: def __init__(self, a, b): self.width = a self.height = b def square(self): return round(self.width * self.height, 2) def perimeter(self): return 2 * (self.width + self.height) 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 self.__squareSurface = 2 * (a*b + a*c + b*c) self.__volume = a * b * c def S(self): return round(self.__squareSurface, 2) def V(self): return round(self.__volume, 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, geometry.stereometry >>> a = geometry.planimetry.Rectangle(3, 4) >>> b = geometry.stereometry.Ball(5) >>> a.square() 12 >>> b.V() 523.6
Импортируя только пакет без явного указания модулей, мы не сможем к ним обратиться:
pl@pl-desk:~$ python3 Python 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> 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() показывают, что в первом случае создается плоский объект-дом, а во втором — объемный.
Файл __init__.py позволяет указать, какие модули будут импортированы с помощью 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
У каждого файла в Python изначально есть встроенный атрибут __name__, который при импорте файла равен имени модуля:
>>> import math >>> math.__name__ 'math' >>> planimetry.__name__ 'geometry.planimetry'
Однако, когда файл запускается как скрипт, значение __name__ становится равным "__main__". Это можно проверить, добавив в код print(__name__) и запустив файл как скрипт.
Таким образом, проверка на __name__ равное "__main__" позволяет размещать в модуле код, который будет выполнен только если файл запускается напрямую. Обычно это используется для тестирования модуля в процессе разработки, а в готовых модулях — для примера использования.
Практическая работа
В практической задаче из урока 7 "Композиция" необходимо было создать интерфейс для взаимодействия с пользователем. Попробуйте разделить класс и интерфейс на два файла. Определите, какой файл выполнять роль модуля, а какой — скрипта. Оба этих файла можно поместить в один каталог.