Python import, как и для чего?
В языке программирования Python подключение пакетов и модулей осуществляется с помощью import. Это позволяет распределять код по логическим «узлам» приложения(модели данных, обработчики, и тп.), что позволяет получить менее нагруженные кодом файлы.
- Повышается читаемость кода.
- Код логически разбит по «узлам», его поиск и дальнейший отлов ошибок становится понятнее и проще.
- Для разработки в команде это дает более четкое понимание, что и где делает каждый при выполнении «задания».
Как использовать import?
Синтаксис import в Python достаточно прост и интуитивно понятен:
# В данной строке импортируется something_we_want import something_we_want # В данной строке импортируется something_we_want, как aww(логично и просто) import something_we_want as aww # В данной строке импортируется из something_we_want something(логично и просто) from something_we_want import something # В данной строке импортируется из something_we_want something, как s(логично и просто) from something_we_want import something as s # Синтаксис as позволяет обращаться к импортируемому по новому нами описанному # далее имени(это работает только в рамках нашего файла)
Что можно импортировать?
Для более глубокого понимания import стоит рассмотреть пример, представленный ниже.
def something(): pass somedata = 5
# 1 случай import something_we_want something_we_want.something() import something_we_want print(something_we_want.somedata) # 2 случай import something_we_want as aww aww.something() import something_we_want as aww print(aww.somedata) # 3 случай from something_we_want import something something() from something_we_want import somedata print(somedata) # 4 случай from something_we_want import something as s s() from something_we_want import somedata as sd print(sd) # Классы импортируются по аналогии с функциями
Красиво, читаемо и понятно.
В чем же подвох?
Но даже в таком простом примере есть подвох, о котором многие не догадываются(если вы начинающий программист, то лучше перейдите к следующему оглавлению).
Идеология Python достаточно интересна, что позволяет ему иметь низкий порог вхождения, низкое время написания кода, высокую читаемость, но именно в ней и кроется подвох.
По своему опыту использования данного языка, сложилось отчетливое ощущение главной идеи ООП(все есть объект). Что же в этом плохого?
Все файлы, функции и тд. это объект. Но что это за объект и класс стоят за файлами(модулями)?
Все просто, это любимый всеми программистами класс, использующий паттерн проектирования Singleton.
Поэтому при достаточно ветвистой структуре, импорт переменной и дальнейшая ее модификация может порождать достаточно не простые в понимании баги(переменная в своем цикле жизни может иметь любое значение и никаких гарантий нет).
Ветвистая структура приложения и существующие подходы импортирования
Часто в разработке приложений программисты пытаются разбить программу по логическим «узлам». Данный подход повышает читаемость и позволяет вести разработку в команде(один человек занимается реализацией одного «узла», второй другого). Так порождается структура приложения, которая зачастую виду сложности функционала является достаточно обширной(ветвистой, потому что имея одну точку входа откуда уже обрастая функционалом это становится похожим на дерево).
Пример ветвистой структуры:
Существует 2 подхода импортирования(лучше выбрать один и придерживаться его весь проект):
Пример именованного импорта из models.py в auth.py:
# auth.py from app.models import User
Пример неименованного импорта из models.py в auth.py:
# auth.py from ..models import User # Количество точек указывает на сколько (обьектов) мы поднимаемся от исходного. # В данном примере первая точка поднимает нас на уровень обьекта handlers, # А вторая точка поднимает нас на уровень обьекта app
Это два абсолютно разных подхода. В первом случае мы «идем» из «корня»(входной точки нашего приложения). Во втором случае мы «идем» от «листа»(нашего файла).
Плюсы и минусы подходов импорта:
Видна структура импорта и приложения.
Видна часть структуры импорта.
Программисту не нужно знать полную структуру приложения.
Импорт не зависит от точки входа.
Код становится не привязанным к приложению. Что по сути позволяет исполнить код из любой точки(тесты, отдельно и тд.). Повышается отлаживаемость. Появляется возможность разработки отдельных узлов приложения без полного вовлечения программиста в проект.
Импорт зависит от точки входа.
Программисту необходимо знать структуру приложения. Код сильно связан с приложением. Что по сути усложняет отладку, тестирование, и тд. Программист становится сильно вовлеченным в проект.
Снижается читаемость импорта.
Хоть первый подход и имеет существенные минусы в использовании, но тем не менее он популярен. Программистам он привычнее, хоть и имеет недостатки. А начинающие часто не задумываются об альтернативах.
P.S.
Данная статья была написана для начинающих программистов, которые хотят научиться писать на языке программирования Python, поэтому часть примеров заведомо упрощена и нацелена на освещение существующих подходов.
Пишите тот код, который бы сами хотели получить от исполнителя.
Классы и модули. Обращение к классам других модулей
Класс может быть объявлен в интерактивной оболочке или в модуле.
Если класс объявлен в модуле, то он считается атрибутом модуля. Это означает, что к имени класса можно обращаться используя следующую форму
- moduleName – имя модуля, класс которого нужно подключить к текущему модулю;
- ClassName – имя класса в модуле moduleName .
Чтобы этот способ работал предварительно нужно выполнить директиву import
Этот способ обращения к классу используется и в случае наследования этого класса.
Пример. На рисунке 1 показан вызов метода Show() класса Hello модуля Module1 из программного кода модуля Module2 .
Рисунок 1. Обращение к класу Hello из другого модуля
После запуска программы «Module2.py» будет выведен следующий результат
2. Обращение к классу из другого модуля. Способ 2
При этом способе, к классу другого модуля можно обращаться без использования имени модуля перед именем класса (смотрите предыдущий способ). В этом случае нужно использовать сочетание директив from и import по следующему образцу
from moduleName import ClassName
- moduleName — имя модуля, в котором объявляется класс ClassName ;
- ClassName — имя класса, который объявлен в модуле moduleName .
После такого подключения, можно обращаться к имени ClassName без указания перед ним имени модуля.
Пример. На рисунке 2 изображен пример обращения к классу Triangle без использования имени Module1 .
Рисунок 2. Способ доступа к имени класса с помощью комбинации директив from-import
3. Случай, когда класс и модуль имеют одинаковые имена
Бывают случаи, когда модуль и класс в нем имеют одинаковые имена. Тогда, чтобы доступиться к имени класса с использованием способа 1 (смотрите п. 1), нужно перед ним обязательно указать имя модуля.
Если имя модуля не указать, то будет сгенерировано исключение. Отдельное имя воспринимается интерпретатором как имя модуля а не имя класса. Этот момент объясняет следующий пример.
Пример. Задан модуль circle.py , в котором реализован класс Circle
# Модуль circle.py # Класс, который определяет окружность import math class Circle: # Метод начальной инициализации def __init__(sl, x, y, r): sl.x = x sl.y = y sl.r = r # Возвращает площадь окружности def Area(sl): return math.pi*sl.r*sl.r
Тогда, чтобы использовать класс Circle в другом модуле, нужно при обращении к его имени обязательно указать имя модуля.
# Модуль с именем module2.py # Подключение модуля circle import circle # Обращение к классу Circle # обязательно указать имя модуля obj = circle.Circle(2, 3, 5) area = obj.Area() print("area color: #0000ff;">circleobj = Circle(2, 3, 5)то возникнет исключительная ситуация.
4. Случай, когда в модулях есть классы с одинаковыми именами
Если в подключаемом модуле объявлен класс, имя которого совпадает с именем класса текущего модуля, то в этом случае действует правило:
- чтобы доступиться к классу другого модуля, обязательно перед именем класса нужно задать имя другого модуля. В этом случае, способ с использованием директив from-import , не работает. Работает только способ с использованием директивы import .
Пример. В примере в разных модулях объявляются классы с одинаковыми именами. Для такого случая демонстрируется правильный способ использования класса другого модуля.
# Модуль с именем module1.py # В модуле объявляется класс с именем A class A: # Некоторый метод класса def A(self): print("Module1: A.A()")
Текст модуля module2.py . В этом модуле показано, как доступиться к классу A модуля module1.py .
# Модуль module2.py # Подключение модуля module1.py. # Только так, комбинация директив from-import не работает import module1 # В этом модуле есть свой класс с именем A class A: def A(self): print("Module2: A.A()") # 1. Обращение к классу A obj = A() # какой класс будет выбран. # Выбирается класс текущего модуля obj.A() # Module2: A.A() # 2. Если нужно обратиться к классу A # модуля module1, то только так obj2 = module1.A() obj2.A()
NameError: name 'module1' is not defined
После запуска программа выдаст следующий результат
5. Общепринятые соглашения при задании имен модулей и классов
При написании больших проектов, содержащих большое количество модулей и классов, важным является правильная идентификация. Поэтому, в языке Python в отношении имен классов и модулей приняты согласованные договоренности:
- имена модулей принято начинать с малых букв;
- имена классов принято начинать с больших букв.
Это необходимо для обеспечения лучшего визуального отличия.
Например. В модуле graphics реализован класс DrawLine . Тогда обращение к имени класса с использованием имени модуля будет следующим
import graphics . graphics.DrawLine