Как поместить задачу в модуль python
Перейти к содержимому

Как поместить задачу в модуль python

  • автор:

Модули

Модуль в языке Python представляет отдельный файл с кодом, который можно повторно использовать в других программах.

Для создания модуля необходимо создать собственно файл с расширением *.py , который будет представлять модуль. Название файла будет представлять название модуля. Затем в этом файле надо определить одну или несколько функций.

Допустим, основной файл программы называется main.py . И мы хотим подключить к нему внешние модули.

Для этого сначала определим новый модуль: создадим в той же папке, где находится main.py, новый файл, который назовем message.py . По умолчанию интерпретатор Python ищет модули по ряду стандартных путей, один из которых — это папка главного, запускаемого скрипта. Поэтому, чтобы интерпретатор подхватил модуль message.py, для простоты оба файла поместим в один проект.

Модули в Python

Соответственно модуль будет называться message . Определим в нем следующий код:

hello = "Hello all" def print_message(text): print(f"Message: ")

Здесь определена переменная hello и функция print_message, которая в качестве параметра получает некоторый текст и выводит его на консоль.

В основном файле программы — main.py используем данный модуль:

import message # подключаем модуль message # выводим значение переменной hello print(message.hello) # Hello all # обращаемся к функии print_message message.print_message("Hello work") # Message: Hello work

Для использования модуля его надо импортировать с помощью оператора import , после которого указывается имя модуля: import message .

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

Получив пространство имен модуля, мы сможем обратиться к его функциям по схеме

Например, обращение к функции print_message() из модуля message:

message.print_message("Hello work")

И после этого мы можем запустить главный скрипт main.py, и он задействует модуль message.py. В частности, консольный вывод будет следующим:

Hello all Message: Hello work

Подключение функциональности модуля в глобальное пространство имен

Другой вариант настройки предполагает импорт функциональности модуля в глобальное пространство имен текущего модуля с помощью ключевого слова from :

from message import print_message # обращаемся к функии print_message из модуля message print_message("Hello work") # Message: Hello work # переменная hello из модуля message не доступна, так как она не импортирована # print(message.hello) # print(hello)

В данном случае мы импортируем из модуля message в глобальное пространство имен функцию print_message() . Поэтому мы сможем ее использовать без указания пространства имен модуля как если бы она была определена в этом же файле.

Все остальные функции, переменные из модуля недоступны (как например, в примере выше переменная hello). Если мы хотим их также использовать, то их можно подключить по отдельности:

from message import print_message from message import hello # обращаемся к функции print_message из модуля message print_message("Hello work") # Message: Hello work # обращаемся к переменной hello из модуля message print(hello) # Hello all

Если необходимо импортировать в глобальное пространство имен весь функционал, то вместо названий отдельных функций и переменных можно использовать символ зводочки * :

from message import * # обращаемся к функции print_message из модуля message print_message("Hello work") # Message: Hello work # обращаемся к переменной hello из модуля message print(hello) # Hello all

Но стоит отметить, что импорт в глобальное пространство имен чреват коллизиями имен функций. Например, если у нас том же файле определена функция с тем же именем до ее вызова, то будет вызываться функция, которая определена последней:

from message import * print_message("Hello work") # Message: Hello work - применяется функция из модуля message def print_message(some_text): print(f"Text: ") print_message("Hello work") # Text: Hello work - применяется функция из текущего файла

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

Установка псевдонимов

При импорте модуля и его функциональности мы можем установить для них псевдонимы. Для этого применяется ключевое слово as , после которого указывается псевдоним. Например, установим псевдоним для модуля:

import message as mes # модуль message проецируется на псевдоним mes # выводим значение переменной hello print(mes.hello) # Hello all # обращаемся к функии print_message mes.print_message("Hello work") # Message: Hello work

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

Подобным образом можно установить псевдонимы для отдельной функциональности модуля:

from message import print_message as display from message import hello as welcome print(welcome) # Hello all - переменная hello из модуля message display("Hello work") # Message: Hello work - функция print_message из модуля message

Здесь для функции print_message из модуля message устанавливается псевдоним display, а для переменной hello — псевдоним welcome. И через эти псевдонимы мы сможем к ним обращаться.

Псевдонимы могут быть полезны, когда нас не устраивают имена функций и переменных, например, они слишком длинные, и мы хотим их сократить, либо мы хотим дать им более описательные, с нашей точки зрения, имена. Либо если в текущем файле уже есть функциональность с теми же именами, и с помощью установки псевдонимов мы можем избежать конфликта имен. Например:

from message import print_message as display def print_message(some_text): print(f"Text: ") # функция print_message из модуля message display("Hello work") # Message: Hello work # функция print_message из текущего файла print_message("Hello work") # Text: Hello work

Имя модуля

В примере выше модуль main.py, который является главным, использует модуль message.py. При запуске модуля main.py программа выполнит всю необходимую работу. Однако, если мы запустим отдельно модуль message.py сам по себе, то ничего на консоли не увидим. Ведь модуль message просто определяет функцию и переменную и не выполняет никаких других действий. Но мы можем сделать так, чтобы модуль message.py мог использоваться как сам по себе, так и подключаться в другие модули.

При выполнении модуля среда определяет его имя и присваивает его глобальной переменной __name__ (с обеих сторон по два подчеркивания). Если модуль является запускаемым, то его имя равно __main__ (также по два подчеркивания с каждой стороны). Если модуль используется в другом модуле, то в момент выполнения его имя аналогично названию файла без расширения py. И мы можем это использовать. Так, изменим содержимое файла message.py :

hello = "Hello all" def print_message(text): print(f"Message: ") def main(): print_message(hello) if __name__ == "__main__": main()

В данном случае в модуль message.py для тестирования функциональности модуля добавлена функция main . И мы можем сразу запустить файл message.py отдельно от всех и протестировать код.

Следует обратить внимание на вызов функции main:

if __name__ == "__main__": main()

Переменная __name__ указывает на имя модуля. Для главного модуля, который непосредственно запускается, эта переменная всегда будет иметь значение __main__ вне зависимости от имени файла.

Поэтому, если мы будем запускать скрипт message.py отдельно, сам по себе, то Python присвоит переменной __name__ значение __main__ , далее в выражении if вызовет функцию main из этого же файла.

Однако если мы будем запускать другой скрипт, а этот — message.py — будем подключать в качестве вспомогательного, для message.py переменная __name__ будет иметь значение message . И соответственно метод main в файле message.py не будет работать.

Данный подход с проверкой имени модуля является более рекомендуемым подходом, чем просто вызов метода main.

В файле main.py также можно сделать проверку на то, является ли модуль главным (хотя в прицнипе это необязательно):

import message def main(): message.print_message("Hello work") # Message: Hello work if __name__ == "__main__": main()

Python предоставляет ряд встроенных модулей, которые мы можем использовать в своих программах. В следующих статьях рассмотрим основные из них.

Модули¶

Как можно использовать код повторно, помещая его в функции, мы уже видели. А что, если нам понадобится повторно использовать различные функции в других наших программах? Как вы уже, наверное, догадались, ответ — модули.

Существуют разные способы составления модулей, но самый простой — это создать файл с расширением .py , содержащий функции и переменные.

Другой способ — написать модуль на том языке программирования, на котором написан сам интерпретатор Python. Например, можно писать модули на языке программирования C, которые после компиляции могут использоваться стандартным интерпретатором Python.

Модуль можно импортировать в другую программу, чтобы использовать функции из него. Точно так же мы используем стандартную библиотеку Python. Сперва посмотрим, как использовать модули стандартной библиотеки.

Пример: (сохраните как using_sys.py )

1 2 3 4 5 6 7
import sys print('Аргументы командной строки:') for i in sys.argv: print(i) print('\n\nПеременная PYTHONPATH содержит', sys.path, '\n') 

Вывод:

 1 2 3 4 5 6 7 8 9 10 11
$ python3 using_sys.py we are arguments Аргументы командной строки: using_sys.py we are arguments Переменная PYTHONPATH содержит ['', 'C:\\Windows\\system32\\python30.zip', 'C:\\Python30\\DLLs', 'C:\\Python30\\lib', 'C:\\Python30\\lib\\plat-win', 'C:\\Python30', 'C:\\Python30\\lib\\site-packages'] 

Как это работает:

В начале мы импортируем модуль sys командой import . Этим мы говорим Python, что хотим использовать этот модуль. Модуль sys содержит функции, относящиеся к интерпретатору Python и его среде, т. е. к системе (system).

Когда Python выполняет команду import sys , он ищет модуль sys . В данном случае это один из встроенных модулей, и Python знает, где его искать.

Если бы это был не скомпилированный модуль, т. е. модуль, написанный на Python, тогда интерпретатор Python искал бы его в каталогах, перечисленных в переменной sys.path . Если модуль найден, выполняются команды в теле модуля, и он становится доступным. Обратите внимание, что инициализация 1 происходит только при первом импорте модуля.

Доступ к переменной argv в модуле sys предоставляется при помощи точки, т. е. sys.argv . Это явно показывает, что это имя является частью модуля sys . Ещё одним преимуществом такого обозначения является то, что имя не конфликтует с именем переменной argv , которая может использоваться в вашей программе.

Переменная sys.argv является списком строк (списки будут детально обсуждаться в одной из последующих глав). Она содержит список аргументов командной строки, т. е. аргументов, переданных программе из командной строки.

Если вы используете среду разработки 2 для написания и запуска программ, поищите где-нибудь в её меню возможность передавать параметры командной строки.

В нашем примере, когда мы запускаем » python using_sys.py we are arguments «, мы запускаем модуль using_sys.py командой python , а всё, что следует далее — аргументы, передаваемые программе 3 . Python сохраняет аргументы командной строки в переменной sys.argv для дальнейшего использования.

Помните, что имя запускаемого сценария 4 всегда является первым аргументом в списке sys.argv . Так что в приведённом примере ‘using_sys.py’ будет элементом sys.argv[0] , ‘we’ — sys.argv[1] , ‘are’ — sys.argv[2] , а ‘arguments’ — sys.argv[3] . Помните, что в Python нумерация начинается с 0, а не с 1.

sys.path содержит список имён каталогов, откуда импортируются модули. Заметьте, что первая строка в sys.path пуста; эта пустая строка показывает, что текущая директория также является частью sys.path , которая совпадает со значением переменной окружения PYTHONPATH . Это означает, что модули, расположенные в текущем каталоге, можно импортировать напрямую. В противном случае придётся поместить свой модуль в один из каталогов, перечисленных в sys.path .

Помните, что текущий каталог — это каталог, в котором была запущена программа. Выполните » import os; print(os.getcwd()) «, чтобы узнать текущий каталог программы.

Файлы байткода .pyc¶

Импорт модуля — относительно дорогостоящее мероприятие, поэтому Python предпринимает некоторые трюки для ускорения этого процесса. Один из способов — создать байт-компилированные файлы (или байткод) с расширением .pyc , которые являются некой промежуточной формой, в которую Python переводит программу (помните раздел Введение о том, как работает Python?). Такой файл .pyc полезен при импорте модуля в следующий раз в другую программу — это произойдёт намного быстрее, поскольку значительная часть обработки, требуемой при импорте модуля, будет уже проделана. Этот байткод также является платформо-независимым.

Обычно файлы .pyc создаются в том же каталоге, где расположены и соответствующие им файлы .py . Если Python не может получить доступ для записи файлов в этот каталог, файлы .pyc созданы не будут.

Оператор from . import . ¶

Чтобы импортировать переменную argv прямо в программу и не писать всякий раз sys. при обращении к ней, можно воспользоваться выражением » from sys import argv «.

Для импорта всех имён, использующихся в модуле sys , можно выполнить команду » from sys import * «. Это работает для любых модулей.

В общем случае вам следует избегать использования этого оператора и использовать вместо этого оператор import , чтобы предотвратить конфликты имён и не затруднять чтение программы.

Пример:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
from math import * n = int(input("Введите диапазон:- ")) p = [2, 3] count = 2 a = 5 while (count  n): b=0 for i in range(2,a): if ( i  sqrt(a)): if (a % i == 0): print(a,"непростое") b = 1 else: pass if (b != 1): print(a,"простое") p = p + [a] count = count + 1 a = a + 2 print(p) 

Имя модуля — name

У каждого модуля есть имя, и команды в модуле могут узнать имя их модуля. Это полезно, когда нужно знать, запущен ли модуль как самостоятельная программа или импортирован. Как уже упоминалось выше, когда модуль импортируется впервые, содержащийся в нём код исполняется. Мы можем воспользоваться этим для того, чтобы заставить модуль вести себя по-разному в зависимости от того, используется ли он сам по себе или импортируется в другую программа. Этого можно достичь с применением атрибута модуля под названием __name__ .

Пример: (сохраните как using_name.py )

1 2 3 4
if __name__ == '__main__': print('Эта программа запущена сама по себе.') else: print('Меня импортировали в другой модуль.') 

Вывод:

1 2 3 4 5 6 7
$ python3 using_name.py Эта программа запущена сама по себе. $ python3 >>> import using_name Меня импортировали в другой модуль. >>> 

Как это работает:

В каждом модуле Python определено его имя — __name__ 5 . Если оно равно ‘__main__’ , это означает, что модуль запущен самостоятельно пользователем, и мы можем выполнить соответствующие действия.

Создание собственных модулей¶

Создать собственный модуль очень легко. Да вы всё время делали это! Ведь каждая программа на Python также является и модулем. Необходимо лишь убедиться, что у неё установлено расширение .py . Следующий пример объяснит это.

Пример: (сохраните как mymodule.py )

1 2 3 4 5 6
def sayhi(): print('Привет! Это говорит мой модуль.') __version__ = '0.1' # Конец модуля mymodule.py 

Выше приведён простой модуль. Как видно, в нём нет ничего особенного по сравнению с обычной программой на Python. Далее посмотрим, как использовать этот модуль в других наших программах.

Помните, что модуль должен находиться либо в том же каталоге, что и программа, в которую мы импортируем его, либо в одном из каталогов, указанных в sys.path .

Ещё один модуль (сохраните как mymodule_demo.py ):

1 2 3 4
import mymodule mymodule.sayhi() print ('Версия', mymodule.__version__) 

Вывод:

1 2 3
$ python mymodule_demo.py Привет! Это говорит мой модуль. Версия 0.1 

Как это работает:

Обратите внимание, что мы используем всё то же обозначение точкой для доступа к элементам модуля. Python повсеместно использует одно и то же обозначение точкой, придавая ему таким образом характерный «Python-овый» вид и не вынуждая нас изучать всё новые и новые способы делать что-либо.

Вот версия, использующая синтаксис from..import (сохраните как mymodule_demo2.py ):

1 2 3 4
from mymodule import sayhi, __version__ sayhi() print('Версия', __version__) 

Вывод mymodule_demo2.py такой же, как и mymodule_demo.py .

Обратите внимание, что если в модуле, импортирующем данный модуль, уже было объявлено имя __version__ , возникнет конфликт. Это весьма вероятно, так как объявлять версию любого модуля при помощи этого имени — общепринятая практика. Поэтому всегда рекомендуется отдавать предпочтение оператору import , хотя это и сделает вашу программу немного длиннее.

Вы могли бы также использовать:

from mymodule import * 

Это импортирует все публичные имена, такие как sayhi , но не импортирует __version__ , потому что оно начинается с двойного подчёркивания

Одним из руководящих принципов в Python является «Явное лучше Неявного». Выполните команду » import this «, чтобы узнать больше, а также просмотрите это обсуждение, в котором приводятся примеры по каждому из принципов.

Функция dir¶

Встроенная функция dir() возвращает список имён, определяемых объектом. Например, для модуля в этот список входят функции, классы и переменные, определённые в этом модуле.

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

Пример:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
$ python3 >>> import sys # получим список атрибутов модуля 'sys' >>> dir(sys) ['__displayhook__', '__doc__', '__excepthook__', '__name__', '__package__', '__s tderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_compact_freelists', '_current_frames', '_getframe', 'api_version', 'argv', 'builtin_module_names', ' byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle' , 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'getcheckinterval', 'getdefaultencoding', 'getfil esystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'gettrace', 'getwindowsversion', 'hexversion', 'intern', 'maxsize', 'maxunicode ', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platfor m', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setprofile', 'setrecursionlimit ', 'settrace', 'stderr', 'stdin', 'stdout', 'subversion', 'version', 'version_in fo', 'warnoptions', 'winver'] >>> dir() # получим список атрибутов текущего модуля ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> a = 5 # создадим новую переменную 'a' >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'a', 'sys'] >>> del a # удалим имя 'a' >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> 

Как это работает:

Сперва мы видим результат применения dir к импортированному модулю sys . Видим огромный список атрибутов, содержащихся в нём.

Затем мы вызываем функцию dir , не передавая ей параметров. По умолчанию, она возвращает список атрибутов текущего модуля. Обратите внимание, что список импортированных модулей также входит туда.

Чтобы пронаблюдать за действием dir , мы определяем новую переменную a и присваиваем ей значение, а затем снова вызываем dir . Видим, что в полученном списке появилось дополнительное значение. Удалим переменную/атрибут из текущего модуля при помощи оператора del , и изменения вновь отобразятся на выводе функции dir .

Замечание по поводу del : этот оператор используется для удаления переменной/имени, и после его выполнения, в данном случае — del a , к переменной a больше невозможно обратиться — её как будто никогда и не было.

Обратите внимание, что функция dir() работает для любого объекта. Например, выполните » dir(‘print’) «, чтобы увидеть атрибуты функции print , или » dir(str) «, чтобы увидеть атрибуты класса str .

Пакеты¶

К настоящему времени вы, вероятно, начали наблюдать некоторую иерархию в организации ваших программ. Переменные обычно находятся в функциях. Функции и глобальные переменные обычно находятся в модулях. А что, если возникнет необходимость как-то организовать модули? Вот здесь-то и выходят на сцену пакеты.

Пакеты — это просто каталоги с модулями и специальным файлом __init__.py , который показывает Python, что этот каталог особый, так как содержит модули Python.

Представим, что мы хотим создать пакет под названием «world» с субпакетами «asia», «africa» и т. д., которые, в свою очередь, будут содержать модули «india», «madagascar» и т. д.

Для этого следовало бы создать следующую структуру каталогов:

 1 2 3 4 5 6 7 8 9 10 11 12 13
| - / | |---- world/ | |---- __init__.py | |---- asia/ | | |---- __init__.py | | |---- india/ | | |---- __init__.py | | |---- foo.py | |---- africa/ | |---- __init__.py | |---- madagascar/ | |---- __init__.py | |---- bar.py 

Пакеты — это удобный способ иерархически организовать модули. Такое часто встречается в стандартной библиотеке.

Резюме¶

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

Мы увидели, как пользоваться этими модулями и создавать свои.

Далее мы познакомимся с некоторыми интересными концепциями, называемыми «структуры данных».

  1. Инициализация — ряд действий, производимых при начальной загрузке (прим. перев.) ↩
  2. IDE — от англ. «Integrated Development Environment» — «интегрированная среда разработки» (прим. перев.) ↩
  3. «we are arguments» — англ. «мы аргументы» (прим. перев.) ↩
  4. Программу на интерпретируемом языке программирования также называют сценарием или скриптом (прим. перев.) ↩
  5. name — англ. «имя» (прим. перев.) ↩

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

Что такое модули, как их импортировать в программу, а также как создавать собственные модули, было описано в одном из уроков курса «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 "", line 1, in 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-версия

X Скрыть Наверх

Объектно-ориентированное программирование на Python

Ликбез по пакетам и шпаргалка по модулям в Python

Ликбез по пакетам и шпаргалка по модулям в Python главное изображение

Как вы, возможно знаете, код на Python хранится в модулях (modules), которые могут быть объединены в пакеты (packages). Это руководство призвано подробно рассказать именно о пакетах, однако совсем не упомянуть модули нельзя, поэтому я немного расскажу и о них. Многое из того, что применимо к модулям, справедливо и для пакетов, особенно если принять во внимание тот факт, что каждый, как правило, ведёт себя как модуль.

Кратко о модулях

Модуль в Python — это файл с кодом. Во время же исполнения модуль представлен соответствующим объектом, атрибутами которого являются:

  1. Объявления, присутствующие в файле.
  2. Объекты, импортированные в этот модуль откуда-либо.

При этом определения и импортированные сущности ничем друг от друга не отличаются: и то, и другое — это всего лишь именованные ссылки на некоторые объекты первого класса (такие, которые могут быть переданы из одного участка кода в другой как обычные значения).

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

Модули и видимость содержимого

В Python нет настоящего сокрытия атрибутов объектов, поэтому и атрибуты объекта модуля так или иначе всегда доступны после импорта последнего. Однако существует ряд соглашений, которые влияют на процесс импортирования и поведение инструментов, работающих с кодом.

Так атрибуты, имя которых начинается с одиночного подчёркивания, считаются как бы помеченными «для внутреннего использования», и обычно не отображаются в IDE при обращению к объекту «через точку». И linter обычно предупреждает об использовании таких атрибутов, мол, «небезопасно!». «Опасность» состоит в том, что автор кода имеет полное право изменять состав таких атрибутов без уведомления пользователей кода. Поэтому программист, использовавший в своём коде приватные части чужого кода рискует в какой-то момент получить код, который перестанет работать при обновлении сторонней библиотеки.

Итак, мы можем определять публичные атрибуты модуля, приватные атрибуты (так называют упомянутые выше атрибуты «для внутреннего пользования»). И данное разделение касается не только определений, содержащихся в самом модуле, но и импортируемых сущностей. Ведь все импортированные объекты становятся атрибутами и того модуля, в который они импортированы.

Есть и третья группа атрибутов — атрибуты, добавляемые в область видимости при импортировании всего содержимого модуля («со звёздочкой», from module import * ). Если ничего явно не указывать, то при таком импортировании в текущую область видимости добавятся все публичные атрибуты модуля. Помимо данного умолчания существует и возможность явно указать, что конкретно будет экспортировано при импорте со звёздочкой. Для управления названным методом импорта существует атрибут __all__ , в который можно положить список (а ещё лучше — кортеж) строк с именами, которые будут экспортироваться.

Живой пример видимости атрибутов модулей.

Рассмотрим пример, демонстрирующий всё вышеописанное. Пусть у нас будет два файла:

# Файл "module.py" from other_module import CAT, DOG as _DOG, _GOAT FISH = 'fish' MEAT = 'meat' _CARROT = 'carrot' __all__ = ('FISH', '_CARROT') 
# Файл "other_module.py" CAT = 'cat' DOG = 'dog' _GOAT = 'goat' 

Рассмотрим сначала обычный импорт import module . Если импортировать модуль таким образом, то IDE, REPL и остальные инструменты «увидят» у модуля следующие атрибуты:

  • FISH , MEAT т.к. имена констант — публичные,
  • CAT , т.к. константа импортирована под публичным именем.

А эти атрибуты не будут видны:

  • _DOG , т.к. при импортировании константа переименована в приватной манере,
  • _GOAT , т.к. импортирована по своему приватному имени (тут линтер может и поругать за обращение к приватному атрибуту модуля!),
  • _CARROT , ибо приватная константа.

Импорт import other_module я не рассматриваю как тривиальный случай.

Теперь рассмотрим импорт всего содержимого module:

from module import * 

После импортирования в текущей области видимости мы получим ровно два новых имени: FISH и _CARROT — именно они перечислены в атрибуте __all__ . Заметьте, что в данном случае при массовом импорте добавится даже приватный атрибут, потому что он явно указан!

Последствия импорта from other_module import * тоже очевидны и я их не рассматриваю.

Наконец-то, пакеты!

Пакет в Python — директория с обязательным модулем __init__.py . Остальное содержимое опционально и может включать в себя и модули, и другие пакеты.

Импортирование пакетов

Пакет с единственным модулем __init__.py при импорте ведёт себя как обычный модуль. Содержимое инициализирующего модуля определяет атрибуты объекта пакета.

Прочие модули пакета и вложенные пакеты не импортируются автоматически вместе с пакетом-родителем, но могут быть импортированы отдельно с указанием полного имени. Важный момент: при импортировании вложенного модуля всегда сначала импортируются модули инициализации всех родительских пакетов (если оные ещё ни разу не импортировались, но об этом я расскажу ниже).

Рассмотрим, к примеру, следующую структуру директорий и файлов:

Когда мы импортируем модуль submodule.py , то фактически происходит следующее (именно в таком порядке):

  1. загружается и выполняется модуль package/__init__.py ,
  2. загружается и выполняется package/subpackage/__init__.py ,
  3. наконец, импортируется package/subpackage/submodule.py .

При импорте package.module предварительно загружается только package/__init__.py .

Так что же, если мы загрузим парочку вложенных модулей, то для каждого будет выполняться загрузка всех __init__.py по дороге? Не будет! Подсистема интерпретатора, отвечающая за загрузку модулей, кэширует уже загруженные пакеты и модули. Каждый конкретный модуль загружается ровно один раз, в том числе и инициализирующие модули __init__.py (короткие имена модулей хоть и одинаковы, но полные имена всегда разные). Все последующие импортирования модуля не приводят к его загрузке, только лишь нужные атрибуты копируются в соответствующие области видимости.

Пакеты и __all__

В целом атрибут __all__ в модуле инициализации пакета ведёт себя так же, как и в случае с обычным модулем. Но если при импорте пакета «со звёздочкой» среди перечисленных имён встретится имя вложенного модуля, а сам модуль не окажется импортирован ранее в этом же __init__.py , то этот модуль импортируется неявно! Очередной пример это продемонстрирует.

Вот структура пакета:

Файл же package/__init__.py содержит следующее (и только это!):

__all__ = ('a', 'b') 

А импортируем мы from package import * . В области видимости у нас окажутся объекты модулей a и b под своими именами (без полного пути, то есть без package. ). При этом сами модули в коде нигде явно не импортируются! Такая вот «автомагия».

Указанный автоматизм достаточно ограничен: не работает «вглубь», например — не импортирует «через звёздочку» указанные модули и подпакеты. Если же вам вдруг такого захочется, вы всегда сможете на соответствующих уровнях в __init__.py сделать from x import * и получить в корневом пакете плоскую область видимости со всем нужным содержимым. Но такое нужно довольно редко, потому что «не помогает» ни IDE, ни ручному поиску по коду. Впрочем, знать о фиче и иметь её в виду — не вредно, как мне кажется.

Изучайте Python на Хекслете Первые курсы в профессии Python-программист доступны бесплатно сразу после регистрации. Начните сегодня, учитесь в комфортном для вас темпе.

Пакеты, модули и точки входа

С модулем __init__.py разобрались. Настала очередь модуля __main__.py . Этот модуль позволяет сделать пакет исполняемым посредством вызова python -m … . Те из вас, кому знакомо оформление точки входа в модулях, могут догадаться, откуда ноги растут у магического выражения __name__ == ‘__main__’ — да, отсюда! Для остальных напоминаю: чтобы модуль сделать «исполняемым, но не при импорте», в конец модуля дописывается конструкция

if __name__ == '__main__': main() # тут что-то выполняем 

У модуля, который скармливается интерпретатору напрямую ( python file.py ) или в роли претендента на запуск ( python -m module ), атрибут __name__ будет содержать то самое магическое ‘__main__’ . А в остальное время атрибут содержит полное имя модуля. С помощью условия, показанного выше, модуль может решить, что делать при запуске.

У пакетов роль атрибута выполняет специальный файл __main__.py . Когда мы запустим пакет через python path/to/package или python -m package , интерпретатор будет искать и выполнять именно этот файл.

Более того, модули __main__ нельзя импортировать обычным способом, поэтому можно не бояться случайного импорта и писать команды прямо на верхнем уровне: всё равно странно в модуле с именем __main__ проверять, что его имя равно __main__ (хе-хе!).

А ещё модуль __main__.py удобен тем, что его можно класть в корень вашего проекта, после чего запускать проект можно будет с помощью команды python . ! Лаконично, не правда ли?

PEP 420, или неявные пространства имён

Раз уж развёл ликбез, расскажу и про эту штуку.

Долгое время в Python пакеты были обязаны иметь файл __init__.py — наличие этого файла позволяло отличить пакет от обычной директории с модулями (с которыми Python работать не мог). Но с версии Python3.3 вступил в силу PEP 420 , позволяющий создавать пространства имён «на вырост».

Теперь вы можете создавать пакет без __init__.py , и такой пакет сможет существовать полноценно, разве что при импорте содержимого не будет производиться инициализация. Но, конечно же, данное изменение делалось не с целью сэкономить на файлах. Подобные пакеты могут встречаться в путях поиска пакетов (о поиске пакетов я ниже расскажу) более одного раза: все встреченные структуры с общим корневым именем при загрузке схлопнутся в одно пространство имён.

Тут стоит отметить, что с полноценными пакетами подобное не срабатывало ранее и не будет работать в будущем. Если среди путей пакет с модулем инициализации находится в первый раз, все последующие пакеты с тем же именем будут проигнорированы. Это защищает вас от смешивания сторонних пакетов с системными. И даже просто от ошибок именования: назвав пакет так же, как называется встроенный пакет или модуль, вы получите ошибку — ваши определения не будут импортироваться.

Пакеты — пространства имён (Namespace Packages, NP) — а именно так называются пакеты без инициализации — не могут объединяться с полноценными пакетами, поэтому добавить что-то в системный пакет вам также не удастся. И тут всё защищено!

Какая же польза от неявных пространств имён? А вы представьте себя авторами, скажем, игрового движка. Вы хотите весь код держать в общем пространстве имён engine , но при этом не желаете, чтобы весь код поставлялся одним дистрибутивом (не каждому же пользователю нужны все-все компоненты движка). С NP вы можете в нескольких дистрибутивах использовать общее корневое имя engine , но разные подпакеты и подмодули. А на выходе вы получите возможность делать импорты вида

from engine import graphics, sound 

Важно: помните, если встретятся обычный пакет и NP с одинаковым именем, то победит обычный пакет! А NP, сколько бы их не было, не будут загружены!

Циклические импорты

Если вдруг вы захотите в один модуль импортировать другой, а другой захочет, пусть даже и не напрямую, импортировать первый, то вы получите ImportError . Потому что у вас случится циклический импорт. Про оный нужно просто знать и стараться архитектурить код так, чтобы циклов не случалось.

Если же приспичивает, и импортировать что-то «ну очень нужно», то можно попробовать обойтись локальным импортом:

### foo.py import bar A = bar(42) 
### bar.py # import foo — тут не импортируем def bar(x): return x + 1 def baz(): import foo # а тут уже можно return foo.A 

Да, это костыль. Но иногда полезный. В идеале — до ближайшего большого рефакторинга. Поэтому настраивайте linter на ловлю локальных импортов и стремитесь убирать такие костыли хоть когда-нибудь!

Поиск пакетов и модулей

Пайтон ищет модули и пакеты в директориях, во время исполнения перечисленных в списке sys.path — по порядку от первого пути к последнему.

В этом списке пути до стандартных библиотек обычно расположены раньше, чем директории со сторонними пакетами, чтобы нельзя было случайно заменить стандартный пакет сторонним (помним: кто первый, того и тапки — среди нескольких с одинаковыми именами загружается первый попавшийся пакет).

В списке путей (обычно в начале) присутствует и путь » , означающий текущую директорию. Это, в свою очередь, означает, что модули и пакет в текущем проекте имеют больший приоритет.

Обычно пути трогать не нужно, всё вполне нормально «работает само». Но если очень хочется, то путей у вас несколько:

  1. Использовать переменную окружения PYTHONPATH (значение — строка с путями, разделёнными символом : ),
  2. Во время исполнения изменить sys.path .

Первый способ — простой и понятный. Не сложнее добавления пути до исполняемых файлов в PATH (даже синтаксис тот же).

Второй способ — сложный и требующий внимательности. Дело в том, что sys.path нужно изменять максимально рано — где-нибудь в точке входа. Если не торопиться менять sys.path , то что-то уже может успеть загрузиться до того, как вы перестроите пути для поиска пакетов. А ведь эта загрузка может произойти в другом потоке исполнения! Отлаживать проблемы с очерёдностью загрузки модулей сложно. Лучше просто их не создавать.

Кстати, когда вы используете виртуальные окружения, sys.path будет содержать пути до локальных копий стандартных библиотек. Именно это позволяет виртуальному окружению быть самодостаточным (работать на любой машине с подходящей ОС — даже без установленного в систему Python!).

Что не было раскрыто?

Я специально не стал рассказывать про

  • создание модулей и пакетов на лету (без использования файлов исходников);
  • загрузку модулей не с диска, а из других источников;
  • расширение подсистемы импортирования с целью загрузки в виде объектов-модулей чего-то, не являющегося кодом вовсе (XML, CSV, JSON).

Темы эти насколько интересны, настолько и велики. На наше счастье, самим разбираться в такой тонкой и сложной машинерии приходится редко. Мы просто пользуемся готовыми магическими артефактами, а зачаровывают их другие 🙂 Если же вы захотите научиться магии, документация вам в руки.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *