НОЧУ ДПО "Национальный открытый университет "ИНТУИТ"
Опубликован: 24.01.2021 | Доступ: свободный | Студентов: 1229 / 21 | Длительность: 03:57:00
Лекция 19:

Взаимодействие модулей

< Лекция 1 || Лекция 19: 123

Смотреть на youtube

До сих пор все наши программные проекты состояли из одного модуля. Модуль может содержать все, что необходимо для реализации проекта - код, данные и методы. Если модуль один, то он выступает в роли главного модуля со статусом main, так что код модуля запускается на выполнение, в процессе выполнения могут вызываться методы модуля, которые, в свою очередь, вызывают другие методы, так что проект начинает жить, выполняться, пока не достигнет завершения.

На размер модуля никаких ограничений не накладывается. Так что любой сложный проект может быть реализован как единый модуль. Это было бы верно, если бы это не было так сложно. Также как сложный метод должен быть декомпозирован, пока не сведется к совокупности достаточно простых и понятных методов, сложный проект следует декомпозировать на относительно простые модули, каждый со своей спецификой. Если в проекте есть модуль physics и lyrics, то первый содержит сервисы, связанные с физикой, а второй - с лирикой. Конечно, физики и лирики должны общаться, и модули одного проекта должны быть связаны друг с другом. Взаимодействие достигается за счет того, что модуль способен экспортировать свои атрибуты другим модулям, а клиентский модуль может импортировать атрибуты модуля экспортера.

Заметьте, в отличие от традиционного подхода ООП, когда поля и методы класса могут иметь разный уровень доступности - общедоступные, закрытые, защищенные (public, private, protected), все атрибуты модуля общедоступны клиентам.

Пришла пора уточнить понятие атрибута модуля. До сих пор я достаточно вольно обходился с этим понятием, рассматривая, например, взаимодействие атрибутов и методов модуля. В языке Python атрибутами модуля являются все имена, определенные на уровне модуля. Так что если в модуле есть определение def A … и присваивание B = [], то это означает, что модуль имеет два атрибута А и В, один из них представляет функцию, второй - данные. Мне кажется, что иногда крайне важно понимать разницу между данными и методами, поэтому я часто использую понятие атрибут, когда речь идет о данных, и предпочитаю использовать термин метод, когда атрибут является методом. Настоящие программисты питонисты меня будут за это ругать, но для меня Python один из многих языков программирования и потому я не следую строго терминологии, принятой в этом языке. Это же касается понятия метод, который я часто использую там, где по терминологии Python следовало бы использовать термин функция. Определение def A … - это по определению Python функция, а я предпочитаю термин метод, поскольку для меня функция определяется так, как это принято в математике, - объект, возвращающий значение, а если функция значения не возвращает, выражением не является и вызывается как оператор, то это процедура, но в Python термин процедура не используется, а жаль, поскольку часто важно различать процедуры и функции. Когда же эти различия не важны, то следует использовать общий термин - метод. В контексте этой лекции термин атрибут модуля используется в традиционном для Python смысле, как имя, определенное на уровне модуля, независимо от того, что задает атрибут - данные или метод.

Итак, все атрибуты модуля общедоступны для клиентов. Как же быть, если мы хотим скрыть от клиентов детали реализации? Единственный способ - это соответствующие данные и методы встроить в методы класса, тогда такие атрибуты становятся локальными и не будут доступны клиентам. Инкапсуляция - это полезный прием. Клиентам предоставляются сервисы с тщательно выверенным интерфейсом. Интерфейс сервиса не должен меняться, хотя детали реализации могут изменяться, но это не будет отражаться на клиентах.

Импорт атрибутов

Рассмотрим проект, содержащий n модулей. Среди этих модулей должен быть хотя бы один модуль, содержащий код. Среди модулей, содержащих код, должен быть единственный модуль, имеющий статус главного модуля. Как назначить некоторый модуль главным? У модуля помимо атрибутов, определенных текстом модуля, есть встроенные атрибуты, о которых поговорим подробнее чуть позже, а сейчас же скажу, что есть встроенный атрибут __name__. Если присвоить этому атрибуту имя __main__, то модуль и станет главным.

В Visual Studio, где я работаю с проектами Python , все делается проще. В обозревателе решения, нажав на одном из модулей проекта правую кнопку мыши, в контекстном меню следует выбрать пункт меню "Задать как файл запуска". Модуль становится главным и выделяется жирным шрифтом. Модуль становится "точкой большого взрыва", с которой начинается жизнь вселенной, порождаемой программным проектом. В процессе развития проекта модули связываются друг с другом, запрашивая нужные сервисы. Рассмотрим два модуля, связанные отношением "клиент - поставщик". Модуль поставщик предоставляет (экспортирует) атрибуты, а клиент их импортирует.

Пусть прямое имя файла, содержащего модуль поставщика, есть myservices.py, а клиента - Testing_Services.py. Тогда клиенту для импортирования атрибутов поставщика достаточно в коде выполнить предложение импорта:

import myservices

Заметьте, имя модуля в предложении import - это имя файла без уточнения .py и без указания пути к каталогу, содержащему файл с модулем.

Какова семантика инструкции импорта? Достаточно понимать, что в результате выполнения на глобальном уровне модуля Testing_Services будет видимо имя myservices, что позволяет получить доступ ко всем атрибутам модуля myservices, используя квалифицированные имена. Если в модуле myservices есть два выше упомянутых атрибута А и В, то вызов метода А имеет вид: myservices.А(…), а присваивание имеет вид: Х = myservices.В.

Таким образом все атрибуты поставщика доступны клиенту, и мы можем передавать информацию от поставщика клиенту.

Есть еще одна форма импорта атрибутов модуля, синтаксис которой имеет вид:

from <имя модуля> import {<список имен атрибутов> | *}

Семантика этой формы такова. Вначале выполняется импорт модуля, а затем в глобальную область видимости клиента переносятся имена из списка, если он задан, или все имена атрибутов, если использована форма с символом *. Преимущества этой формы в том, что можно не использовать квалифицированные имена, сокращая запись. Но есть и недостатки. Возникают проблемы, связанные с коллизией имен. Импортируемые имена могут совпадать с именами в клиентском модуле. С этой проблемой разобраться нетрудно. Хуже, когда клиент выполняет импорт из разных модулей. Тогда, в особенности, когда применяется форма импорта со звездочкой, разобраться в том, какому модулю принадлежит имя, довольно сложно. В ситуации, когда возможен импорт из разных модулей, надежнее использовать квалифицированные имена.

Удивительно, но при импорте возможна передача информации в обратном направлении - от клиента к поставщику. Ситуация здесь во многом схожа с тем, как метод модуля, работая на локальном уровне, может изменять атрибуты на глобальном уровне, и делает это двумя возможными способами.

При импорте изменение данных поставщика достигается двумя похожими способами. В первом способе изменяется состояние объекта поставщика при сохранении связи имени атрибута и объекта. Во втором способе используется аналог описания global имен. При импорте нет необходимости в задании списка global имен, их роль играют квалифицированные имена.

Рассмотрим пример. Пусть в коде поставщика myservices определены три атрибута:

n = 77;  r1 = [3, 5, 7]; r2 = [2, 4, 6, 8]

Пусть модуль Testing_Services, используя форму импорта from, импортировал эти атрибуты. Пусть в исполняемом коде этого модуля встретился такой фрагмент кода:

n = 99;  r1[0] = 9; myservices.r2 = [22, 44, 66, 88]

Тогда, в результате первого присваивания, в модуле Testing_Services появится атрибут n, связанный с объектом - числом 99. Это присваивание никак не отразится на атрибуте n модуля myservices. Второе присваивание изменит состояние списка, связанного с атрибутом r1 модуля myservices. Третье присваивание свяжет атрибут r2 модуля myservices с новым списком, поскольку используется квалифицированное имя, позволяющее точно определить, о каком объекте идет речь. Как видите, клиент может изменять атрибуты поставщика!

Уточним семантику выполнения инструкции import <имя модуля>. Заметьте, это выполняемая инструкция, а не декларация. Что происходит при выполнении импорта модуля? Если инструкция импорта модуля встретилась первый раз, то происходит трансляция модуля в промежуточный байт код, создаются все атрибуты модуля, вся эта информация сохраняется.

Возможно повторное выполнение инструкции импорта модуля. При повторном выполнении используется ранее созданная информация. Что нужно понимать? Если модуль импортирован, с ним выполнялись какие-либо действия, значения атрибутов изменялись, затем модуль повторно импортировался, то никакого обновления атрибутов не будет, при работе с модулем будут использоваться существующие до повторного импорта значения атрибутов.

< Лекция 1 || Лекция 19: 123
Елена Лаптева
Елена Лаптева

Думаю. что не смогу его закончить. Хотелось предупредить других - не тратьте зря время, ищите другой курс.

Михаил Сидоров
Михаил Сидоров

Если S - последовательность, то срез задается как S(i : j) и содержит j - i элементов,

а в примере используютс другие скобки - 

NL[1:3] = ["решили", "не", "искать"]

или это не срез, тогда, что это?