Создание модуля и импорт классов
Мы разбили код на файлы — отлично. Но теперь один файл должен «взять» класс из другого. Именно для этого существует механизм import. В этом уроке разберём все способы импорта, подводные камни и магическую конструкцию if __name__ == "__main__".
📄 Что такое модуль — проще некуда
Прежде чем говорить про импорт, разберёмся с главным термином: что такое «модуль»?
Модуль — это просто файл с расширением .py.
Любой файл Python — уже модуль. Всё!
Серьёзно — это всё. Нет ничего специального, что нужно добавить или сделать. Если у вас есть файл geometry.py — у вас уже есть модуль geometry. Если есть user.py — есть модуль user.
Имя модуля = имя файла без расширения .py. Всегда маленькими буквами, через подчёркивание если несколько слов: my_module.py, email_sender.py.
📦 Пакет — это папка с файлами .py. Про пакеты подробнее поговорим в следующем уроке. Сейчас сосредоточимся на модулях — одиночных файлах.
✏️ Создаём модуль geometry.py
Давайте создадим конкретный модуль, с которым будем работать в этом уроке. Это файл geometry.py — в нём два класса для работы с геометрическими фигурами: Circle (круг) и Rectangle (прямоугольник).
Файл geometry.py:
# geometry.py
# Модуль с геометрическими фигурами
import math # встроенный модуль Python, нужен для math.pi
class Circle:
"""Класс для работы с кругом."""
def __init__(self, radius):
self.radius = radius # радиус круга
def area(self):
"""Площадь круга: π × r²"""
return math.pi * self.radius ** 2
def perimeter(self):
"""Длина окружности: 2 × π × r"""
return 2 * math.pi * self.radius
def __str__(self):
return f"Круг с радиусом {self.radius}"
class Rectangle:
"""Класс для работы с прямоугольником."""
def __init__(self, width, height):
self.width = width # ширина
self.height = height # высота
def area(self):
"""Площадь прямоугольника: ширина × высота"""
return self.width * self.height
def perimeter(self):
"""Периметр прямоугольника: 2 × (ширина + высота)"""
return 2 * (self.width + self.height)
def __str__(self):
return f"Прямоугольник {self.width}×{self.height}"
Этот файл мы сохраняем как geometry.py. Теперь у нас есть модуль geometry с двумя классами. Посмотрим, как импортировать из него нужные нам вещи.
📁 Важно: файл main.py (откуда мы будем делать импорт) должен лежать в той же папке, что и geometry.py. Пока просто держите их рядом.
📦 Способ 1: import module — импорт всего модуля
Первый и самый простой способ — импортировать весь модуль целиком. После этого вы обращаетесь к его содержимому через точку: module.ClassName.
# main.py — способ 1: import module
import geometry # импортируем весь модуль geometry
# Чтобы использовать классы, пишем: geometry.ИмяКласса
circle = geometry.Circle(5) # круг с радиусом 5
rect = geometry.Rectangle(4, 6) # прямоугольник 4×6
print(circle) # Круг с радиусом 5
print(circle.area()) # 78.53981633974483
print(rect) # Прямоугольник 4×6
print(rect.perimeter()) # 20
✅
Плюсы
Сразу видно, откуда пришёл класс: geometry.Circle — явно говорит «класс Circle из модуля geometry». Никакой путаницы, если одинаковые имена есть в разных модулях.
⚠️
Минусы
Чуть длиннее писать: каждый раз geometry.Circle вместо просто Circle. Когда имя модуля длинное — это может раздражать.
💡 Когда использовать: когда важна «подпись» откуда пришёл класс. Особенно полезно в больших файлах, где много разных импортов — сразу ясно, что к чему относится.
🎯 Способ 2: from module import Class — импорт конкретного класса
Второй способ — импортировать конкретный класс напрямую. После этого класс доступен по имени без приставки модуля.
# main.py — способ 2: from module import Class
from geometry import Circle # берём только Circle
from geometry import Rectangle # берём только Rectangle
# Теперь можно писать просто Circle и Rectangle — без "geometry."
circle = Circle(5)
rect = Rectangle(4, 6)
print(circle.area()) # 78.53981633974483
print(rect.perimeter()) # 20
Можно импортировать несколько имён сразу через запятую:
# Импорт нескольких классов одной строкой
from geometry import Circle, Rectangle
circle = Circle(3)
rect = Rectangle(10, 5)
print(circle, rect)
✅
Плюсы
Короче писать: просто Circle(5) вместо geometry.Circle(5). Видно, что именно используется из модуля — перечислено в строке import.
⚠️
Минусы
Если в двух модулях есть класс с одинаковым именем — будет конфликт. Например, если и geometry, и svg содержат Circle — второй импорт перезапишет первый.
💡 Самый популярный способ в реальном коде. Используйте его по умолчанию — он чистый и явный: видно какие конкретно классы вы берёте из модуля.
🏷️ Способ 3: from module import Class as MyClass — переименование (alias)
Иногда нужно переименовать класс при импорте. Это называется псевдоним или alias. Ключевое слово as.
Зачем это нужно? Две типичные ситуации:
🔴 Ситуация 1: конфликт имён
В модуле geometry есть класс Circle, и в модуле svg тоже есть Circle. Без алиаса второй импорт затрёт первый. С алиасом — оба живут мирно.
🔵 Ситуация 2: длинное имя
Класс называется VeryLongAndDescriptiveCircleClass. Писать это каждый раз утомительно. Алиас позволяет дать ему удобное короткое имя внутри вашего файла.
# main.py — способ 3: import с алиасом
# Пример 1: решаем конфликт имён
from geometry import Circle as GeomCircle
from svg import Circle as SvgCircle # тоже называется Circle!
geom = GeomCircle(5) # геометрический круг
icon = SvgCircle(10) # SVG-иконка круга
# теперь нет путаницы — оба класса доступны под разными именами
# Пример 2: сокращаем длинное имя
from geometry import Rectangle as Rect
r = Rect(4, 6) # гораздо удобнее, чем Rectangle(4, 6)
print(r.area()) # 24
💡 Важно: алиас — это локальное переименование только внутри вашего файла. Оригинальный класс в модуле geometry по-прежнему называется Rectangle. Вы просто создаёте новое имя-ярлык для него у себя.
📌 Способ 4: import module as alias — псевдоним для модуля
Алиас можно дать не только классу, но и целому модулю. Именно так вы уже встречали в мире Python самый известный псевдоним:
import numpy as np # стандартная практика в мире Python
import pandas as pd # тоже классика
import matplotlib.pyplot as plt # и это тоже
# Теперь вместо numpy.array пишем np.array
arr = np.array([1, 2, 3])
print(arr)
Наш модуль geometry тоже можно импортировать с псевдонимом:
# main.py — алиас для всего модуля
import geometry as geo # теперь обращаемся через "geo"
circle = geo.Circle(5)
rect = geo.Rectangle(4, 6)
print(circle.area()) # 78.53...
print(rect.perimeter()) # 20
🌍 Почему import numpy as np — это стандарт?
Библиотеку NumPy используют миллионы программистов. Если все пишут np, то код становится понятным сразу — любой Python-разработчик в мире видит np.array и знает: это NumPy. Это соглашение, которое облегчает чтение чужого кода.
🚫 Способ 5: from module import * — почему так делать НЕ стоит
Звёздочка * означает «импортировать всё, что есть в модуле». Это выглядит удобно — написал одну строчку и получил всё сразу!
# Выглядит удобно...
from geometry import *
circle = Circle(5) # работает!
rect = Rectangle(4, 6) # работает!
Но давайте посмотрим, что происходит на практике — представьте, что у вас три модуля:
# shapes.py — содержит: Circle, Rectangle, Color
# colors.py — содержит: Color, Palette, Theme
# utils.py — содержит: Circle, format_number, Color
# Импортируем всё из всех трёх модулей
from shapes import * # добавляет: Circle, Rectangle, Color
from colors import * # добавляет: Color (ПЕРЕЗАПИСЫВАЕТ предыдущий!), Palette, Theme
from utils import * # добавляет: Circle (ПЕРЕЗАПИСЫВАЕТ!), format_number, Color (СНОВА!)
# Теперь попробуй угадать: какой Circle здесь?
c = Circle(5)
# Это Circle из utils.py — он импортировался последним и перезаписал остальных.
# Но это НЕ очевидно при чтении кода!
💥 Засорение пространства имён — что это такое?
Пространство имён — это как список всех «слов» (имён переменных, классов, функций), которые Python знает в вашем файле. Когда вы пишете from geometry import *, все имена из модуля мгновенно оказываются в вашем пространстве имён.
Если в разных модулях есть имена с одинаковым названием — они молча перезапишут друг друга. Никакой ошибки не будет. Просто последний импорт выигрывает. И потом вы часами ищете баг: «почему мой Circle ведёт себя не так?»
❌
Проблемы import *
- Невозможно понять, откуда пришло имя
- Скрытые конфликты имён
- IDE не может подсказать автодополнение
- Тяжело читать и поддерживать
✅
Правило хорошего тона
Всегда явно указывай, что импортируешь: from geometry import Circle, Rectangle. Это занимает пару символов больше, но делает код прозрачным и безопасным.
🔥 Единственное исключение: иногда import * встречается в __init__.py пакетов для «реэкспорта» — но это отдельная тема. Для обычного кода: никогда не используй import *.
⚙️ Что физически происходит при импорте: sys.modules
Когда вы пишете import geometry, Python делает несколько шагов под капотом. Давайте разберём их — это поможет понять одно важное поведение.
Поиск файла
Python ищет файл geometry.py в нескольких местах: сначала в текущей папке, потом в стандартных папках библиотек. Если не нашёл — ошибка ModuleNotFoundError.
Выполнение кода модуля
Python выполняет весь код файла geometry.py сверху вниз. Именно поэтому классы и функции становятся доступными — Python прочитал определения и «выучил» их.
Кеширование в sys.modules
Загруженный модуль сохраняется в специальный словарь sys.modules. Если тот же модуль импортируется ещё раз — Python просто берёт его из кеша. Код модуля НЕ выполняется повторно.
Посмотрим на это поведение в коде:
import sys
# Первый импорт — Python найдёт файл и выполнит его код
import geometry
print("geometry" in sys.modules) # True — модуль в кеше
# Второй импорт — Python просто возьмёт из кеша, ничего не перевыполняет
import geometry # это строка НИЧЕГО нового не делает!
# Можно посмотреть на все загруженные модули
# print(sys.modules) # большой словарь — не советую выводить на экран 😄
🎯 Практическое значение кеша
Если в нескольких файлах вашего проекта написано import geometry — это нормально и эффективно. Python загрузит модуль один раз при первом импорте, а все последующие просто получат кешированную версию. Скорость работы не страдает, память расходуется один раз.
🔑 Магия if __name__ == "__main__" — разбираемся раз и навсегда
Вы наверняка видели эту конструкцию в чужом коде и думали: «Что это такое и зачем нужно?» Сейчас разберём — она не такая страшная, как выглядит.
Что такое __name__?
У каждого Python-файла есть специальная встроенная переменная __name__ (с двумя подчёркиваниями с каждой стороны). Она автоматически устанавливается Python и содержит имя модуля.
Но! Есть одно особое правило: если файл запускается напрямую (python geometry.py), то __name__ равно строке "__main__". Если файл импортируется другим файлом — __name__ равно имени файла ("geometry").
Покажем это на примере. Обновим наш geometry.py:
# geometry.py — с конструкцией if __name__ == "__main__"
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
def __str__(self):
return f"Круг с радиусом {self.radius}"
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
def __str__(self):
return f"Прямоугольник {self.width}×{self.height}"
# Этот блок выполняется ТОЛЬКО при прямом запуске файла:
# python geometry.py
# При импорте (import geometry) этот блок НЕ выполняется!
if __name__ == "__main__":
print("=== Тестируем наш модуль ===")
c = Circle(5)
print(c)
print(f"Площадь: {c.area():.2f}")
print(f"Длина окружности: {c.perimeter():.2f}")
r = Rectangle(4, 6)
print(r)
print(f"Площадь: {r.area()}")
print(f"Периметр: {r.perimeter()}")
Теперь разберём, что происходит в двух сценариях:
🖥️ Сценарий А: прямой запуск
python geometry.py
Python устанавливает __name__ = "__main__". Условие __name__ == "__main__" истинно → блок выполняется → тесты запускаются. Вы видите вывод на экране.
📦 Сценарий Б: импорт модуля
import geometry
Python устанавливает __name__ = "geometry". Условие __name__ == "__main__" ложно → блок НЕ выполняется → никакого лишнего вывода нет. Только классы загружаются.
🤔 Зачем это нужно на практике?
Представьте: вы написали модуль geometry.py и хотите его протестировать. Без if __name__ == "__main__" тестовый код запускается ВСЕГДА — и при прямом запуске, и при любом импорте. Это значит, каждый раз когда кто-то делает import geometry, на экран вылетает куча вывода. Это очень раздражает!
С конструкцией if __name__ == "__main__" тесты запускаются только когда вы явно запускаете файл. При импорте — тихо, только классы.
Типичная структура файла с этой конструкцией:
# любой_модуль.py
# 1. Импорты
import math
# 2. Классы и функции
class MyClass:
pass
def my_function():
pass
# 3. Точка входа — выполняется только при прямом запуске
if __name__ == "__main__":
# тесты, демонстрация работы, отладка
obj = MyClass()
my_function()
print("Всё работает!")
⭐ Хорошая привычка: добавляй if __name__ == "__main__": в каждый файл с классами. Это признак профессионально написанного кода. IDE и линтеры часто даже подсказывают это автоматически.
📊 Все способы импорта: сводная таблица
Теперь у нас есть все способы. Запомнить их поможет таблица:
import geometry
Весь модуль. Доступ через точку: geometry.Circle(). Источник виден сразу.
from geometry import Circle
Конкретный класс напрямую. Писать просто Circle(). Самый частый способ.
from geometry import Circle as C
Класс с псевдонимом. Используем C(). Помогает при конфликтах имён или длинных названиях.
import geometry as geo
Весь модуль с псевдонимом. Доступ: geo.Circle(). Стандарт для numpy, pandas.
from geometry import *
❌ Всё подряд. Засоряет пространство имён. Скрытые конфликты. Не использовать.
✅ Итоги урока
- Модуль — это просто файл
.py. Любой файл Python уже является модулем — ничего дополнительно делать не нужно. import module— импортирует весь модуль; доступ через точку:module.ClassName. Источник всегда виден.from module import Class— импортирует конкретный класс напрямую. Самый распространённый способ в реальном коде.as— псевдоним для модуля или класса. Решает конфликты имён и сокращает длинные названия. Стандарт для numpy (as np), pandas (as pd).from module import *— не использовать! Засоряет пространство имён, создаёт скрытые конфликты, делает код нечитаемым.- При импорте Python выполняет код модуля один раз и кеширует его в
sys.modules. Повторныйimportработает мгновенно из кеша. if __name__ == "__main__":— блок, который выполняется только при прямом запуске файла, но не при импорте. Используй для тестов и демонстрации кода внутри модуля.
👉 В следующем уроке мы разберём пакеты — папки с несколькими модулями. Узнаем зачем нужен __init__.py, как строить иерархию папок и импортировать из вложенных пакетов. Это последний кирпич для построения настоящих структурированных Python-проектов.