На предыдущих
занятиях мы с вами иногда подключали сторонние модули для расширения базовой
функциональности языка Python. И делали это так:
import math
import random
import time
то есть, писали
ключевое слово import, после которого указывали название модуля:
import <название
модуля>
или, можно
сделать так:
import <модуль1>,
<модуль2>, …, <модульN>
Но что такое
модуль? В самом простом случае – это набор конструкций языка Python, например,
набор часто используемых функций. Так, подключая модуль math, мы получаем
доступ к тригонометрическим функциям:
cos,
sin, tan, и т.п.
И, потом можем
вызвать их по следующему синтаксису:
<название
модуля>.<название функции>
В частности,
так:
res = math.cos(0.7)
print( res )
И при запуске увидим
значение косинуса при угле в 0,7 радиан.
Но зачем нужны
модули? Дело в том, что при создании крупных проектов стараются предельно
упрощать процесс разработки программ. И один из методов здесь – это
декомпозиция большой задачи на множество более мелких, которые, как правило,
можно реализовать независимо друг от друга. Представьте, например, что нам
нужно создать программу по распознаванию лиц и разработчики решили для этого
использовать нейронную сеть. Тогда программу в целом можно (упрощенно) представить
следующими модулями.
При этом каждый
модуль может включать (подключать) другие вспомогательные модули, образуя
сложную иерархическую архитектуру программы.
Кроме того,
модули бывают полезны, когда предполагается часто использовать определенный
функционал в своих проектах. Не случайно язык Python поставляется с
набором стандартных модулей, таких как math, random, sys и другие. Они
значительно облегчают разработку самых разных программ. Их полный набор можно
посмотреть на сайте https://docs.python.org/3/library/
или выполнить поиск на ресурсе https://pypi.org.
Давайте теперь
подробнее посмотрим как подключаются и создаются модули в Python. Начнем с того,
что мы хотим создать свой собственный модуль. Для этого создадим файл в том же
каталоге, что и файл ex1.py и назовем его
mymath.py
Содержимое этого
файла будет состоять из одной константы и трех функций:
PI = 3.1415
def max2(a, b):
return a if(a > b) else b
def max3(a, b, c):
return max2(a, max2(b, c))
def sum(*vals):
S = 0
for x in vals:
S += x
return S
Все, наш модуль
создан. Теперь подключим его в нашей программе:
Обратите
внимание, мы указываем имя файла нашего модуля без расширения py. То есть, имя
файла – это и есть имя модуля. Далее, вызовем из него функцию, допустим, sum:
m = mymath.sum(1,2,3)
print(m)
Здесь мы указали
сначала имя модуля, поставили точку и затем, имя функции. То же самое, если
требуется обратиться к отдельной переменной:
Откуда появилось
mymath и что это такое?
В действительности, это так называемое, пространство имен, которое
автоматически создается при подключении модуля. И в этом пространстве
определены все функции и переменные, чтобы избежать возможных конфликтов с
другими подобными функциями. Например, в Python уже существует
функция sum, но наш вариант
из модуля mymath никак не
повлияет на ее работоспособность. Вот для этого и нужны пространства имен.
Если же мы не
хотим создавать нового пространства имен, то можно импортировать элементы
модуля по следующему правилу:
В этом случае
будет доступна только указанная функция sum модуля mymath. Причем, для ее
использования уже не нужно писать название модуля, а просто вызвать ее по
имени:
А вот другие
элементы, например, переменная PI:
будет
недоступна, т.к. не была импортирована. Если нужно импортировать таким образом
несколько функций или переменных, то следует их перечислить через запятую:
from mymath import sum, PI
или, для
получения всего содержимого, записать звездочку:
Но, используя
такой способ, можно столкнуться с конфликтом имен уже существующих переменных и
функций с тем же именем. В нашем случае функция sum перекрывает
стандартную функцию sum и прежняя уже недоступна. Чтобы такого не
происходило можно указывать алиас (псевдоним) импортируемого элемента:
from mymath import sum as my_sum
И к нашей
функции мы можем теперь обратиться через этот алиас:
Или, записать так:
from mymath import PI, sum as my_sum
Тогда переменная
PI доступна по
своему имени, а функция sum по имени my_sum.
Во всех наших
примерах модуль mymath находился в том же каталоге, что и файл ex1.py. И это было
сделано не случайно. Когда мы что-либо импортируем, то файл ищется следующим
образом:
-
в
текущей директории программы (в данном случае ex1.py);
-
в
директориях, указанных в переменной окружения PYTHONPATH;
-
в
директориях стандартных библиотек;
-
в
дополнительных путях, прописанные в .pth-файлах и пакетах сторонних
разработчиков.
Если мы наш файл
mymath.py поместим, например, в подкаталог lib, то при импорте
указываем его следующим образом:
from lib.mymath import PI, sum as my_sum
Если же
импортируем непосредственно:
то будет
сформировано пространство имен lib.mymath:
m = lib.mymath.sum(1,2,3)
print(m)
print(lib.mymath.PI)
А теперь давайте
посмотрим на особенности операции импорта. Предположим, что импортируем наш
модуль
затем, меняем
значение переменной:
и снова
импортируем модуль:
выводим
полученное значение:
и видим значение
3, а не 3,1415. Почему так произошло? Почему данные не обновились при повторном
импорте? Дело в том, что Python в целях
оптимизации импортирует каждый отдельный модуль только один раз и затем уже не
меняет. Поэтому значение переменной PI осталось неизменным. Однако,
модуль можно принудительно перезагрузить, используя вспомогательный модуль. В Python версии 3.4 и выше – это importlib:
from importlib import reload
reload(mymath)
или так:
import importlib
importlib.reload(mymath)
Теперь, мы видим
обновленное значение 3,1415.
Далее, когда мы
импортируем данные с использованием from:
то они буквально
копируются в глобальную область видимости нашей программы ex1.py. То есть, здесь
идет создание новой переменной PI с соответствующим значением. В этом
легко убедиться. Давайте импортируем модуль еще раз:
и изменим это
значение:
выведем
переменные в консоль:
получим
результат:
3 и 3,1415
Видите? Изменение
переменной PI никак не
затронуло переменную mymath.PI, т.к. это две
совершенно разные переменные.
Но если подобную
операцию выполнить с переменной, которая ссылается на изменяемый тип данных,
например, список:
то ситуация
кардинально меняется. Добавим этот список в наш модуль и запишем такую
программу:
from mymath import ar
import mymath
print(ar, mymath.ar, sep="\n")
На выходе получаем
одинаковые списки. Но если мы изменим один из них:
то это скажется
и на изменении другого! То есть, изменяемые типы данных не копируются и обе
переменные ar и mymath.ar ссылаются на
один и тот же список.
Чтобы убедиться
в этом окончательно, давайте вместо списка запишем кортеж:
Это неизменяемый
тип, следовательно, должны при импортах получать копии, проверим:
from mymath import ar
import mymath
ar += (10,)
print(ar, mymath.ar, sep="\n")
Да, теперь
изменение одного не ведет к изменению другого. Вот этот момент с импортом
данных следует иметь в виду.
Далее, в Python имеется
полезная функция
dir(<модуль>)
которая
возвращает имена всех данных, которые импортируются с указанным модулем. Например,
вот такая программа:
import mymath
print( dir(mymath) )
Отобразит в
консоли следующий список имен:
['PI',
'__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
'__package__', '__spec__', 'ar', 'max2', 'max3', 'sum']
Здесь помимо
наших переменных PI, ar и функций max2, max3, sum имеется еще
набор служебных переменных.
Задания для самоподготовки
1. Создайте
модуль с двумя функциями, которые бы вычисляли периметр и площадь
прямоугольника. Подключите этот модуль к основной программе и вызовите эти
функции с аргументами, введенные с клавиатуры.
2. Задайте в
модуле словарь, в котором ключами являются английские слова, а значениями
соответствующие русские (переводы). Также добавьте необходимые функции для
добавления и удаления новых слов в этом словаре. Импортируйте этот модуль в
основную программу и реализуйте мини-словарь со следующим меню (функционалом):
1.
Перевести слово
2.
Добавить слово
3.
Удалить слово
4. Завершить
работу
Попробуйте
развить идею словаря и добавьте возможность автоматического сохранения и
считывания данных из файла (в файле сохраняется словарь целиком).