Что такое модули в Python?

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

Преимущества использования модулей

Создание простого модуля

Создание модуля в Python очень просто - достаточно создать файл с расширением .py и поместить в него нужный код:

Простой модуль математических функций
# math_utils.py - наш собственный модуль

"""
Модуль с математическими утилитами
Содержит полезные функции для математических операций
"""

# Константы
ПИ = 3.141592653589793
Е = 2.718281828459045

# Функции
def факториал(n):
    """Вычисляет факториал числа n"""
    if n < 0:
        raise ValueError("Факториал определен только для неотрицательных чисел")
    if n == 0 or n == 1:
        return 1
    результат = 1
    for i in range(2, n + 1):
        результат *= i
    return результат

def наибольший_общий_делитель(a, b):
    """Вычисляет наибольший общий делитель двух чисел"""
    while b:
        a, b = b, a % b
    return a

def наименьшее_общее_кратное(a, b):
    """Вычисляет наименьшее общее кратное двух чисел"""
    return abs(a * b) // наибольший_общий_делитель(a, b)

def простое_число(n):
    """Проверяет, является ли число простым"""
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

def список_простых(предел):
    """Возвращает список простых чисел до заданного предела"""
    простые = []
    for число in range(2, предел + 1):
        if простое_число(число):
            простые.append(число)
    return простые

# Классы
class КомплексноеЧисло:
    def __init__(self, действительная, мнимая=0):
        self.действительная = действительная
        self.мнимая = мнимая
    
    def __str__(self):
        if self.мнимая >= 0:
            return f"{self.действительная} + {self.мнимая}i"
        else:
            return f"{self.действительная} - {abs(self.мнимая)}i"
    
    def __add__(self, другое):
        if isinstance(другое, КомплексноеЧисло):
            return КомплексноеЧисло(
                self.действительная + другое.действительная,
                self.мнимая + другое.мнимая
            )
        elif isinstance(другое, (int, float)):
            return КомплексноеЧисло(
                self.действительная + другое,
                self.мнимая
            )
        else:
            return NotImplemented
    
    def модуль(self):
        return (self.действительная ** 2 + self.мнимая ** 2) ** 0.5

# Защита от прямого выполнения
if "__main__" == "__main__":  # Это будет False при импорте
    print("Этот код выполняется только при прямом запуске модуля")
    print(f"Факториал 5: {факториал(5)}")
    print(f"Простые числа до 20: {список_простых(20)}")

Импорт модулей

Существует несколько способов импорта модулей в Python:

Различные способы импорта
# main.py - файл, использующий наш модуль

# 1. Полный импорт модуля
import math_utils

print(f"Значение ПИ: {math_utils.ПИ}")
print(f"Факториал 6: {math_utils.факториал(6)}")
print(f"НОД(48, 18): {math_utils.наибольший_общий_делитель(48, 18)}")

# 2. Импорт с псевдонимом
import math_utils as mu

print(f"НОК(12, 18): {mu.наименьшее_общее_кратное(12, 18)}")

# 3. Импорт конкретных элементов
from math_utils import факториал, простое_число

print(f"Факториал 7: {факториал(7)}")
print(f"17 простое число? {простое_число(17)}")

# 4. Импорт всех элементов (не рекомендуется)
from math_utils import *

print(f"Значение Е: {Е}")
print(f"Простые числа до 10: {список_простых(10)}")

# 5. Импорт класса из модуля
from math_utils import КомплексноеЧисло

число1 = КомплексноеЧисло(3, 4)
число2 = КомплексноеЧисло(1, -2)

print(f"Число 1: {число1}")
print(f"Число 2: {число2}")
print(f"Сумма: {число1 + число2}")
print(f"Модуль числа 1: {число1.модуль():.2f}")

Модуль __init__.py и пакеты

Для создания пакетов (коллекций модулей) используется файл __init__.py:

Создание пакета
# Структура пакета geometry/
# geometry/
# ├── __init__.py
# ├── circle.py
# ├── rectangle.py
# └── triangle.py

# geometry/__init__.py
"""
Геометрический пакет
Содержит модули для работы с различными геометрическими фигурами
"""

# Импортируем основные классы для удобства использования
from .circle import Круг
from .rectangle import Прямоугольник
from .triangle import Треугольник

# Определяем, что будет доступно при импорте *
__all__ = ['Круг', 'Прямоугольник', 'Треугольник']

# Определяем версию пакета
__version__ = "1.0.0"

print("Геометрический пакет загружен")

# geometry/circle.py
import math

class Круг:
    def __init__(self, радиус):
        self.радиус = радиус
    
    def площадь(self):
        return math.pi * self.радиус ** 2
    
    def периметр(self):
        return 2 * math.pi * self.радиус
    
    def __str__(self):
        return f"Круг(радиус={self.радиус})"

# geometry/rectangle.py
class Прямоугольник:
    def __init__(self, ширина, высота):
        self.ширина = ширина
        self.высота = высота
    
    def площадь(self):
        return self.ширина * self.высота
    
    def периметр(self):
        return 2 * (self.ширина + self.высота)
    
    def __str__(self):
        return f"Прямоугольник(ширина={self.ширина}, высота={self.высота})"

# geometry/triangle.py
import math

class Треугольник:
    def __init__(self, сторона_a, сторона_b, сторона_c):
        if not self._может_существовать(сторона_a, сторона_b, сторона_c):
            raise ValueError("Треугольник с такими сторонами не может существовать")
        self.сторона_a = сторона_a
        self.сторона_b = сторона_b
        self.сторона_c = сторона_c
    
    def _может_существовать(self, a, b, c):
        return a + b > c and a + c > b and b + c > a
    
    def площадь(self):
        # Формула Герона
        p = self.периметр() / 2
        return math.sqrt(p * (p - self.сторона_a) * (p - self.сторона_b) * (p - self.сторона_c))
    
    def периметр(self):
        return self.сторона_a + self.сторона_b + self.сторона_c
    
    def __str__(self):
        return f"Треугольник(стороны={self.сторона_a}, {self.сторона_b}, {self.сторона_c})"

# Использование пакета
# main.py
import geometry

круг = geometry.Круг(5)
прямоугольник = geometry.Прямоугольник(4, 6)
треугольник = geometry.Треугольник(3, 4, 5)

print(f"{круг}: площадь = {круг.площадь():.2f}, периметр = {круг.периметр():.2f}")
print(f"{прямоугольник}: площадь = {прямоугольник.площадь()}, периметр = {прямоугольник.периметр()}")
print(f"{треугольник}: площадь = {треугольник.площадь():.2f}, периметр = {треугольник.периметр()}")

Работа с относительными и абсолютными импортами

В пакетах можно использовать относительные импорты для ссылки на другие модули в том же пакете:

Относительные и абсолютные импорты
# Структура пакета mypackage/
# mypackage/
# ├── __init__.py
# ├── core.py
# ├── utils.py
# └── subpackage/
#     ├── __init__.py
#     └── helpers.py

# mypackage/core.py
class ОсновнойКласс:
    def __init__(self, имя):
        self.имя = имя
    
    def приветствие(self):
        return f"Привет, я {self.имя}"

# mypackage/utils.py
from .core import ОсновнойКласс  # Относительный импорт

def создать_объект(имя):
    return ОсновнойКласс(имя)

def обработать_строку(строка):
    return строка.strip().upper()

# mypackage/subpackage/helpers.py
from ..core import ОсновнойКласс  # Относительный импорт из родительского пакета
from ..utils import обработать_строку  # Относительный импорт из соседнего модуля

def помощник(имя):
    обработанное_имя = обработать_строку(имя)
    объект = ОсновнойКласс(обработанное_имя)
    return f"Помощник создал: {объект.приветствие()}"

# mypackage/__init__.py
from .core import ОсновнойКласс
from .utils import создать_объект, обработать_строку
from .subpackage.helpers import помощник

__all__ = ['ОсновнойКласс', 'создать_объект', 'обработать_строку', 'помощник']

# main.py - использование пакета
import mypackage

# Абсолютные импорты
объект1 = mypackage.ОсновнойКласс("Абсолют")
print(объект1.приветствие())

объект2 = mypackage.создать_объект("Функция")
print(объект2.приветствие())

print(mypackage.обработать_строку("  привет, мир  "))
print(mypackage.помощник("относительный импорт"))

Практические примеры

Рассмотрим практические примеры создания и использования модулей:

Система управления библиотекой
# Структура пакета library/
# library/
# ├── __init__.py
# ├── book.py
# ├── member.py
# └── library.py

# library/book.py
class Книга:
    def __init__(self, isbn, название, автор, год_издания):
        self.isbn = isbn
        self.название = название
        self.автор = автор
        self.год_издания = год_издания
        self.доступна = True
        self.владелец = None
    
    def выдать(self, читатель):
        if not self.доступна:
            raise ValueError(f"Книга '{self.название}' уже выдана")
        self.доступна = False
        self.владелец = читатель
        return True
    
    def вернуть(self):
        if self.доступна:
            raise ValueError(f"Книга '{self.название}' уже в библиотеке")
        self.доступна = True
        предыдущий_владелец = self.владелец
        self.владелец = None
        return предыдущий_владелец
    
    def __str__(self):
        статус = "доступна" if self.доступна else f"выдана {self.владелец.имя}"
        return f"'{self.название}' by {self.автор} ({self.год_издания}) - {статус}"

# library/member.py
class Читатель:
    def __init__(self, id_читателя, имя, email):
        self.id_читателя = id_читателя
        self.имя = имя
        self.email = email
        self.книги = []
    
    def взять_книгу(self, книга):
        if len(self.книги) >= 5:
            raise ValueError(f"{self.имя} уже имеет максимальное количество книг")
        if книга in self.книги:
            raise ValueError(f"{self.имя} уже имеет эту книгу")
        self.книги.append(книга)
    
    def вернуть_книгу(self, книга):
        if книга not in self.книги:
            raise ValueError(f"{self.имя} не имеет этой книги")
        self.книги.remove(книга)
    
    def список_книг(self):
        return [str(книга) for книга in self.книги]
    
    def __str__(self):
        return f"Читатель {self.имя} (ID: {self.id_читателя})"

# library/library.py
from .book import Книга
from .member import Читатель

class Библиотека:
    def __init__(self, название):
        self.название = название
        self.книги = {}
        self.читатели = {}
    
    def добавить_книгу(self, книга):
        if книга.isbn in self.книги:
            raise ValueError(f"Книга с ISBN {книга.isbn} уже существует")
        self.книги[книга.isbn] = книга
        print(f"Книга '{книга.название}' добавлена в библиотеку")
    
    def зарегистрировать_читателя(self, читатель):
        if читатель.id_читателя in self.читатели:
            raise ValueError(f"Читатель с ID {читатель.id_читателя} уже зарегистрирован")
        self.читатели[читатель.id_читателя] = читатель
        print(f"Читатель '{читатель.имя}' зарегистрирован")
    
    def найти_книгу(self, isbn_или_название):
        # Поиск по ISBN
        if isbn_или_название in self.книги:
            return self.книги[isbn_или_название]
        
        # Поиск по названию
        for книга in self.книги.values():
            if isbn_или_название.lower() in книга.название.lower():
                return книга
        
        return None
    
    def выдать_книгу(self, isbn_книги, id_читателя):
        if isbn_книги not in self.книги:
            raise ValueError(f"Книга с ISBN {isbn_книги} не найдена")
        
        if id_читателя not in self.читатели:
            raise ValueError(f"Читатель с ID {id_читателя} не найден")
        
        книга = self.книги[isbn_книги]
        читатель = self.читатели[id_читателя]
        
        try:
            книга.выдать(читатель)
            читатель.взять_книгу(книга)
            print(f"Книга '{книга.название}' выдана читателю '{читатель.имя}'")
        except ValueError as e:
            print(f"Ошибка: {e}")
    
    def вернуть_книгу(self, isbn_книги):
        if isbn_книги not in self.книги:
            raise ValueError(f"Книга с ISBN {isbn_книги} не найдена")
        
        книга = self.книги[isbn_книги]
        
        try:
            читатель = книга.вернуть()
            читатель.вернуть_книгу(книга)
            print(f"Книга '{книга.название}' возвращена читателем '{читатель.имя}'")
        except ValueError as e:
            print(f"Ошибка: {e}")
    
    def показать_все_книги(self):
        if not self.книги:
            print("Библиотека пуста")
            return
        
        print(f"\nКниги в библиотеке '{self.название}':")
        for книга in self.книги.values():
            print(f"  {книга}")

# library/__init__.py
from .book import Книга
from .member import Читатель
from .library import Библиотека

__all__ = ['Книга', 'Читатель', 'Библиотека']

# main.py - использование системы библиотеки
import library

# Создание библиотеки
библиотека = library.Библиотека("Городская библиотека")

# Создание книг
книга1 = library.Книга("978-0134685991", "Effective Java", "Joshua Bloch", 2017)
книга2 = library.Книга("978-1449355739", "Learning Python", "Mark Lutz", 2013)
книга3 = library.Книга("978-0596158064", "Python Cookbook", "David Beazley", 2013)

# Добавление книг в библиотеку
библиотека.добавить_книгу(книга1)
библиотека.добавить_книгу(книга2)
библиотека.добавить_книгу(книга3)

# Создание читателей
читатель1 = library.Читатель(1, "Анна", "anna@email.com")
читатель2 = library.Читатель(2, "Борис", "boris@email.com")

# Регистрация читателей
библиотека.зарегистрировать_читателя(читатель1)
библиотека.зарегистрировать_читателя(читатель2)

# Показать все книги
библиотека.показать_все_книги()

# Выдача книг
библиотека.выдать_книгу("978-0134685991", 1)
библиотека.выдать_книгу("978-1449355739", 2)

# Показать книги после выдачи
библиотека.показать_все_книги()

# Попытка выдать уже выданную книгу
библиотека.выдать_книгу("978-0134685991", 2)

# Возврат книги
библиотека.вернуть_книгу("978-0134685991")

# Показать книги после возврата
библиотека.показать_все_книги()

Упражнения для самостоятельного решения

Попробуйте решить следующие задачи, применяя полученные знания о создании модулей:

Упражнения

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

# string_utils.py
"""
Модуль для работы со строками
Содержит полезные функции для манипуляций со строками
"""

def подсчет_гласных(строка):
    """Подсчитывает количество гласных в строке"""
    гласные = "аеёиоуыэюяaeiou"
    return sum(1 for символ in строка.lower() if символ in гласные)

def палиндром(строка):
    """Проверяет, является ли строка палиндромом"""
    очищенная = "".join(символ.lower() for символ in строка if символ.isalnum())
    return очищенная == очищенная[::-1]

def удалить_дубликаты_слов(строка):
    """Удаляет дубликаты слов из строки, сохраняя порядок"""
    слова = строка.split()
    уникальные_слова = []
    for слово in слова:
        if слово not in уникальные_слова:
            уникальные_слова.append(слово)
    return " ".join(уникальные_слова)

def шифр_цезаря(строка, сдвиг=3):
    """Шифрует строку с помощью шифра Цезаря"""
    результат = ""
    for символ in строка:
        if символ.isalpha():
            # Определяем базу (A для заглавных, a для строчных)
            база = ord('A') if символ.isupper() else ord('a')
            # Применяем сдвиг с учетом цикличности
            сдвинутый = (база + (ord(символ) - база + сдвиг) % 26)
            результат += chr(сдвинутый)
        else:
            результат += символ
    return результат

# test_string_utils.py
import string_utils

print(f"Гласных в 'Привет, мир!': {string_utils.подсчет_гласных('Привет, мир!')}")
print(f"'А роза упала на лапу Азора' - палиндром? {string_utils.палиндром('А роза упала на лапу Азора')}")
print(f"Без дубликатов: {string_utils.удалить_дубликаты_слов('это это тест тест строка')}")
print(f"Шифр Цезаря: {string_utils.шифр_цезаря('Привет, Мир!', 3)}")

Создайте пакет date_utils с модулями для работы с датами: форматирование дат, вычисление возраста, определение високосного года, расчет дней до события. Организуйте пакет с использованием __init__.py и относительных импортов.

# Структура пакета date_utils/
# date_utils/
# ├── __init__.py
# ├── formatting.py
# ├── calculations.py
# └── helpers.py

# date_utils/formatting.py
from datetime import datetime

def форматировать_дату(дата, формат="%d.%m.%Y"):
    """Форматирует дату в заданный формат"""
    if isinstance(дата, str):
        дата = datetime.strptime(дата, "%Y-%m-%d")
    return дата.strftime(формат)

def текущая_дата(формат="%d.%m.%Y"):
    """Возвращает текущую дату в заданном формате"""
    return datetime.now().strftime(формат)

# date_utils/calculations.py
from datetime import datetime, date

def вычислить_возраст(дата_рождения):
    """Вычисляет возраст по дате рождения"""
    if isinstance(дата_рождения, str):
        дата_рождения = datetime.strptime(дата_рождения, "%Y-%m-%d").date()
    
    сегодня = date.today()
    возраст = сегодня.year - дата_рождения.year
    
    # Проверяем, был ли день рождения в этом году
    if (сегодня.month, сегодня.day) < (дата_рождения.month, дата_рождения.day):
        возраст -= 1
    
    return возраст

def високосный_год(год=None):
    """Проверяет, является ли год високосным"""
    if год is None:
        год = datetime.now().year
    
    return год % 4 == 0 and (год % 100 != 0 or год % 400 == 0)

# date_utils/helpers.py
from datetime import datetime, timedelta

def дней_до_события(дата_события):
    """Вычисляет количество дней до события"""
    if isinstance(дата_события, str):
        дата_события = datetime.strptime(дата_события, "%Y-%m-%d").date()
    
    сегодня = datetime.now().date()
    разница = дата_события - сегодня
    return разница.days

def добавить_дни(дата, дни):
    """Добавляет заданное количество дней к дате"""
    if isinstance(дата, str):
        дата = datetime.strptime(дата, "%Y-%m-%d").date()
    
    новая_дата = дата + timedelta(days=дни)
    return новая_дата.strftime("%Y-%m-%d")

# date_utils/__init__.py
from .formatting import форматировать_дату, текущая_дата
from .calculations import вычислить_возраст, високосный_год
from .helpers import дней_до_события, добавить_дни

__all__ = [
    'форматировать_дату', 'текущая_дата',
    'вычислить_возраст', 'високосный_год',
    'дней_до_события', 'добавить_дни'
]

# test_date_utils.py
import date_utils

print(f"Текущая дата: {date_utils.текущая_дата()}")
print(f"Форматированная дата: {date_utils.форматировать_дату('2023-12-25', '%d %B %Y')}")
print(f"Возраст (дата рождения 1990-05-15): {date_utils.вычислить_возраст('1990-05-15')} лет")
print(f"2024 год високосный? {date_utils.високосный_год(2024)}")
print(f"Дней до Нового года: {date_utils.дней_до_события('2024-01-01')}")
print(f"Через 30 дней будет: {date_utils.добавить_дни('2023-10-01', 30)}")

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

# config_manager.py
import json
import os
from typing import Any, Dict, Optional

class ConfigManager:
    def __init__(self, файл_конфигурации: str):
        self.файл_конфигурации = файл_конфигурации
        self.конфигурация: Dict[str, Any] = {}
        self.загрузить()
    
    def загрузить(self) -> bool:
        """Загружает конфигурацию из файла"""
        try:
            if os.path.exists(self.файл_конфигурации):
                with open(self.файл_конфигурации, 'r', encoding='utf-8') as файл:
                    self.конфигурация = json.load(файл)
                return True
            else:
                print(f"Файл конфигурации {self.файл_конфигурации} не найден. Создается пустая конфигурация.")
                return False
        except json.JSONDecodeError as e:
            print(f"Ошибка чтения JSON: {e}")
            return False
        except Exception as e:
            print(f"Ошибка загрузки конфигурации: {e}")
            return False
    
    def сохранить(self) -> bool:
        """Сохраняет конфигурацию в файл"""
        try:
            with open(self.файл_конфигурации, 'w', encoding='utf-8') as файл:
                json.dump(self.конфигурация, файл, ensure_ascii=False, indent=4)
            return True
        except Exception as e:
            print(f"Ошибка сохранения конфигурации: {e}")
            return False
    
    def получить(self, ключ: str, значение_по_умолчанию: Any = None) -> Any:
        """Получает значение параметра по ключу"""
        return self.конфигурация.get(ключ, значение_по_умолчанию)
    
    def установить(self, ключ: str, значение: Any) -> None:
        """Устанавливает значение параметра"""
        self.конфигурация[ключ] = значение
    
    def обновить(self, новые_параметры: Dict[str, Any]) -> None:
        """Обновляет несколько параметров"""
        self.конфигурация.update(новые_параметры)
    
    def удалить(self, ключ: str) -> bool:
        """Удаляет параметр по ключу"""
        if ключ in self.конфигурация:
            del self.конфигурация[ключ]
            return True
        return False
    
    def все_параметры(self) -> Dict[str, Any]:
        """Возвращает все параметры конфигурации"""
        return self.конфигурация.copy()

# test_config_manager.py
from config_manager import ConfigManager

# Создание менеджера конфигурации
config = ConfigManager("app_config.json")

# Установка параметров
config.установить("database_url", "localhost:5432")
config.установить("debug", True)
config.установить("max_connections", 100)

# Обновление нескольких параметров
config.обновить({
    "timeout": 30,
    "retry_attempts": 3,
    "log_level": "INFO"
})

# Сохранение конфигурации
if config.сохранить():
    print("Конфигурация сохранена")

# Загрузка конфигурации
config2 = ConfigManager("app_config.json")
print(f"Database URL: {config2.получить('database_url')}")
print(f"Debug mode: {config2.получить('debug')}")
print(f"Все параметры: {config2.все_параметры()}")
Предыдущий урок Следующий урок