Модули и пакеты

Что такое модули, как их импортировать в программу, а также как создавать собственные модули, было описано в одном из уроков курса "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() говорят, что в первом случае будет создан двумерный объект-дом, во-втором – трехмерный. Это куда информативней, чем просто 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 "Композиция" требовалось разработать интерфейс взаимодействия с пользователем. Разнесите сам класс и интерфейс по разным файлам. Какой из них выполняет роль модуля, а какой – скрипта? Оба файла можно поместить в один каталог.

Курс с примерами решений практических работ: android-приложение, pdf-версия.

Создано

Обновлено