[ Сборник задач ]
Тема 12.
Импорт. Модули и пакеты

[ Сборник задач ]
Тема 12. Импорт. Модули и пакеты

Python Workbook Cover T1
Операторы: import
Контент: Вопросы (6шт) + задачи (1шт)

Оглавление

1
Введение
Рассмотрим модули и пакеты в Python. Научимся их импортировать разными способами. Создадим собственные модули и используем в своем коде.
Перейти
2
Вопросы и ответы
6 вопросов по теме "Импорт. Модули и пакеты" + ответы
Перейти
3
Условия задач
Одно большое задание по теме базового уровня
Перейти
4
Решения задач
Приводим код решения задания
Перейти
1
One

Введение

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

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

Читайте также

2
Two

Вопросы по теме "Импорт. Модули и пакеты"

1. Опишите способы импортирования функции sqrt из модуля math.
Вариантов импортирования функции из модуля или пакета в Python представлено не мало. Опишем самые распространенные и понятные большинству пользователей. Более замудренные и редкие способы применяются теми, кто понимает, зачем ему это надо. На практике такое встречается редко и используется экспертами для специфичных задач.

Стандартные способы (на примере модуля math):
1) Через импорт библиотеки

Пример – Интерактивный режим
>>> import math
>>> math.sqrt(25)
5.0


2) Через импорт всех объектов модуля

Пример – Интерактивный режим
>>> from math import *
>>> sqrt(25)
5.0


3) Через импорт конкретной функции

Пример – Интерактивный режим
>>> from math import sqrt
>>> sqrt(25)
5.0


4) С помощью импорта функции с переименованием

Пример – Интерактивный режим
>>> from math import sqrt as square_root
>>> square_root(25)
5.0


5) С помощью импорта модуля с переименованием

Пример – Интерактивный режим
>>> import math as mathematics
>>> mathematics.sqrt(25)
5.0


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

Относительный импорт используется разработчиками внутри пакетов и не предназначен для скриптов, которые можно вызывать напрямую. Пример такого импортирования представлен в практическом задании.
2. Приведите основные отличия модуля от пакета.
При выходе из интерпретатора Python вся наша работа фактически теряется (переменные, функции, классы). Чтобы этого не допустить, код принято записывать в отдельные файлы с расширением .py (это называется созданием скрипта). По мере роста файла с кодом ориентироваться в нем становится неудобно. Более того, в других программах может потребоваться идентичный функционал. Копировать заново в новый документ те же исходники – дело неблагодарное. Вдруг нам понадобится сделать правки? Менять во всех файлах строки – повод для возникновения ошибок.

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

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

Таким образом, модуль – отдельный файл как внутри пакета, так и сам по себе в рамках проекта или области его видимости. Если у библиотеки есть свойство __path__, то перед нами пакет.

Пример – Интерактивный режим
>>> import requests
>>> requests.__path__ # Это пакет
['D:\\progi\\ytb\\venv\\lib\\site-packages\\requests']
>>> import math # Это модуль
>>> math.__path__
AttributeError: module 'math' has no attribute '__path__'

3. Как можно отобразить список всех установленных в исполняемую среду модулей?
Python предоставляет несколько способов получить список установленных библиотек:

1) Через функцию help()

Пример – Интерактивный режим
>>> help('modules')
1 brain_gi lxml shutil
2 brain_hashlib lzma signal



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

2) Через модуль sys

Пример – Интерактивный режим
>>> import sys
>>> sys.modules.keys()
dict_keys(['sys', 'builtins', '_frozen_importlib', '_imp', '_thread', '_weakref',…]


Показываются лишь те библиотеки, которые встроены в Python или используются нами в основном скрипте, т.е. загруженные.

3) Через функцию dir()

Пример – Интерактивный режим
>>> dir()
[..., 'math', 'modf', 'nan', 'nextafter', 'perm', 'pi', …]


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

4) С помощью консоли
Находясь в виртуальном окружении проекта и используя терминал, мы сможем увидеть все установленные нами модули и пакеты (внутри окружения) и их версии.

Пример – Консоль
$ pip list

colorama 0.4.4
comtypes 1.1.8
cycler 0.10.0

$ pip freeze

PyRect==0.1.4
PyScreeze==0.1.26
pytesseract==0.3.7
python-dateutil==2.8.1
python-docx==0.8.10



Обе команды дают тот же результат, но в разной степени наглядности (кому как удобнее).
4. Что импортируется при использовании конструкции import *? В чем опасность ее применения?
Никто не запрещает использовать конструкцию import *, так как в некоторых случаях это удобно. Она импортирует все объекты библиотеки, которые разрешены разработчиком. В общем виде метод замедляет работу программы на 20-30 %, но для несложных проектов это не критично.

Чтобы узнать, что будет импортировано конкретным модулем в таком способе его загрузки, удобно воспользоваться функцией dir().

Пример – Интерактивный режим
>>> import math
>>> dir(math)
[…, 'ceil', 'comb', 'copysign', 'cos', 'cosh', 'degrees', 'dist', 'e', 'erf', 'erfc', 'exp', …]


Часть функций и свойств модуля разработчик может скрыть, если начинает их имена с одного или двух нижних подчеркиваний. Более эффективный способ – ограничить список доступных для импорта объектов свойством __all__ (актуально для пакетов).

Это список, в котором перечислены все предлагаемые пользователю сущности.
Пример: __all__ = ['pi', 'sqrt', 'cos']

Главная опасность такого импорта – конфликт имен. Например, в модуле math имеется функция sqrt(), но и в библиотеке cmath (для комплексных чисел) есть такая же функция. В результате, вы можете так и не понять, которой из них пользуетесь, так как не ясно, из какого она модуля. В случае возникновения ошибок придется тратить время на их решение из-за такого неаккуратного импортирования. Официальная документация, более того, крайне не рекомендует использовать такой способ, так как он нарушает принцип ясности (Явное лучше неявного).
5. Зачем нужна инструкция if __name__ == '__main__'?
Многие из вас могли видеть в чужом коде конструкцию if __name__ == '__main__', хотя на практике и без нее все прекрасно работает в большинстве случаев.
Разберемся, все же, для чего она требуется и когда ее используют. В небольшом проекте, когда нет огромного количества перекрестных импортов библиотек между файлами, мы обычно работаем в головном документе, где происходит все самое важное.

Его имя, к слову, то самое __main__ из вышеприведенной инструкции.

Пример – IDE
print(__name__)

Результат выполнения
__main__


Если мы запустим любой скрипт с этой командой, то всегда выведется __main__. Переменная __name__ приобретает имя __main__ именно в момент исполнения текущего модуля. Однако если добавить этот код в другой скрипт, который мы импортируем в вызываемый, то имя его будет другим (по названию файла).

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

Пример – IDE
if __name__ == '__main__':
print(1)


Результат выполнения напрямую:
1


Если же мы импортируем скрипт с конструкцией в другой файл, то код внутри выражения не исполнится. Если не добавить это выражение, то при импорте будет запускаться исполняемый код модуля, что в большинстве случаев не требуется.
6*. В чем разница в способах импорта через importlib.import_module() и __import__()?
Импорт через importlib.import_module() и __import__() считается продвинутым и применяется опытными разработчиками. Позволяет использовать собственные импортеры, загрузчики и поисковики.

importlib.import_module() загружает конкретный подпакет или модуль из библиотеки, тогда как __import__() предоставляет доступ только к головному файлу библиотеки.

Для наглядности приведем оба варианта.

Пример – IDE
import importlib

mod = importlib.import_module('bs4.formatter')
print(mod.__name__)
print(dir(mod))

mod = __import__('bs4.formatter')
print(mod.__name__)
print(dir(mod))


Результат выполнения
bs4.formatter
['EntitySubstitution', 'Formatter', 'HTMLFormatter', 'XMLFormatter', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
bs4
['BeautifulSoup', 'BeautifulStoneSoup', 'CData', 'Comment', 'Counter', 'DEFAULT_OUTPUT_ENCODING', 'Declaration', 'Doctype', 'FeatureNotFound', …]


В первом случае мы импортировали подпакет 'bs4.formatter' из библиотеки BeautifulSoup и теперь имеем доступ только к его объектам.
Во втором случае мы пытались сделать то же самое, но в результате получили головной пакет и его функции и свойства.
3
Three

Задание по теме "Импорт. Модули и пакеты"

Задача
Создайте пакет figures, состоящий из трех подпакетов: triangle, circle, square.

В каждом подпакете будем иметь файл code.py, где создадим ряд функций:

– для пакета circle: функции circle_perimeter() – вычисляет длину окружности, circle_area() – вычисляет площадь окружности. Еще заведем переменную default_radius = 5, которая будет скрыта при импорте модуля. Ее назначение – дефолтный радиус для окружности, если пользователь не введет свой. Обе функции принимают на вход только радиус.

– для пакета triangle: функции triangle_perimeter() – вычисляет периметр треугольника, triangle_area() – вычисляет площадь фигуры. Дополнительно создадим три переменные (длины сторон треугольника): a = 7, b = 2, c = 8, которые также не будут видны при импорте. На вход функциям передается длина трех сторон (если пользователь ничего не введет, то используются значения по умолчанию).

– для пакета square: функции square_perimeter() – вычисляет периметр квадрата, square_area() – вычисляет площадь фигуры. Дополнительная переменная a = 15 не доступна при импорте и принимается функциями, если пользователь не предоставил свои размеры стороны квадрата.

Ваша итоговая задача – позволить человеку, загрузившему ваш пакет, иметь возможность напрямую импортировать все функции из подпакетов. Например, он может написать так: from figure import circle_area.

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

Решение

Задача. Базовый уровень

Условие
Создайте пакет 'figures', состоящий из трех подпакетов: 'triangle', 'circle', 'square'.

В каждом подпакете будем иметь файл code.py, где создадим ряд функций:

– для пакета 'circle': функции circle_perimeter() – вычисляет длину окружности, circle_area() – вычисляет площадь окружности. 
Еще заведем переменную default_radius = 5, которая будет скрыта при импорте модуля. 
Ее назначение – дефолтный радиус для окружности, если пользователь не введет свой. 
Обе функции принимают на вход только радиус.

– для пакета 'triangle': функции triangle_perimeter() – вычисляет периметр треугольника, triangle_area() – вычисляет площадь фигуры. 
Дополнительно создадим три переменные (длины сторон треугольника): a = 7, b = 2, c = 8, которые также не будут видны при импорте. 
На вход функциям передается длина трех сторон (если пользователь ничего не введет, то используются значения по умолчанию).

– для пакета 'square': функции square_perimeter() – вычисляет периметр квадрата, square_area() – вычисляет площадь фигуры. 
Дополнительная переменная a = 15 не доступна при импорте и принимается функциями, если пользователь не предоставил свои размеры стороны квадрата.

Ваша итоговая задача – позволить человеку, загрузившему ваш пакет, иметь возможность напрямую импортировать все функции из подпакетов. 
Например, он может написать так: 'from figure import circle_area'.

Также вы, как разработчик, после написания всей библиотеки решили поменять ее имя на 'figures'. 
Постарайтесь сделать код таким, чтобы это не заставило вас переписывать все внутренние импорты с учетом нового именования.
Для решения задания понадобится использовать относительные импорты. Представленный подход – пример реализации удобного API. Причем это удобство получают обе стороны: вы, как разработчик (легко можно расширять библиотеку без полного переписывания импортов), а также пользователи, которым не нужно разбираться в дебрях библиотеки и осуществлять сумасшедшие импорты (типа from figures.circle.code import circle_area).

Ниже продемонстрирована файловая структура проекта:
Файловая структура
figures
  |_ circle
	|_ __init__.py
	|_ code.py
  |_ square
	|_ __init__.py
	|_ code.py
  |_ triangle
	|_ __init__.py
	|_ code.py
  |_ __init__.py
Содержимое каждого документа можно посмотреть/скачать на GitHub. Для примера посмотрим на код корневого файла пакета __init__.py, а также скриптов из подпакета circle.
Пример - IDE
from .circle import circle_area, circle_perimeter
from .triangle import triangle_area, triangle_perimeter
from .square import square_area, square_perimeter

__all__ = (
    'circle_area',
    'circle_perimeter',
    'square_area',
    'square_perimeter',
    'triangle_area',
    'triangle_perimeter'
)
Пример – IDE (файл «__init__.py» из пакета «circle»)
from .code import circle_area, circle_perimeter
Пример – IDE (файл «code.py» из пакета «circle»)
from math import pi
 
default_radius = 5
 
 
def circle_perimeter(radius=default_radius):
	return pi * 2 * radius
 
 
def circle_area(radius=default_radius):
	return pi * radius ** 2
Теперь данную библиотеку может скачать любой пользователь и без проблем импортировать ее в свой проект, получив легкий и удобный доступ к 6-ти созданным нами функциям.
Как вам материал?

Читайте также