Библиотека классов Python: модульная структура и повторное использование кода

Раздел: Python -> Объектно-ориентированное программирование

Основные варианты организации библиотеки классов

Эффективное решение: пакет с модулями и __init__.py

Как создать библиотеку классов, которую можно легко импортировать и расширять?

Наиболее удобный способ - группировка классов в пакете. Пакет представляет собой директорию с файлом __init__.py. Этот файл может быть пустым или содержать код для импорта подмодулей. Такой подход позволяет логически разделить классы по функциональности и избежать загромождения одного файла.

Пример структуры:

mylib/
    __init__.py
    shapes.py
    colors.py

атрибуты класса python (атрибуты классов и объектов в python)

В shapes.py определим классы фигур:

class Circle:
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return 3.14159 * self.radius ** 2

class Square:
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side ** 2

библиотека классов python (библиотека классов в python)

В colors.py - классы цветов:

class Red:
    hex = "#FF0000"
class Blue:
    hex = "#0000FF"

метод объекта python (методы объектов в python)

В __init__.py можно написать:

from .shapes import Circle, Square
from .colors import Red, Blue

метод call python (метод __call__ в python)

Теперь импорт выглядит просто:

from mylib import Circle, Red
c = Circle(5)
print(c.area())

Python структура объекта (структура объекта в python)

78.53975

Python создание объектов (создание объектов в python)

Это решение избавляет от необходимости запоминать полные пути к модулям и упрощает рефакторинг.

Типичные проблемы:

  • Циклический импорт: когда модуль A импортирует из B, а B из A. Решение - вынести общие классы в третий модуль или использовать импорт внутри функций.
  • Случайное создание pycache: если забыть __init__.py, пакет не будет распознан интерпретатором (в Python 3.3+ без него пакет называется «пространством имён», что может приводить к неожиданностям). Рекомендуется всегда добавлять __init__.py.

Вариант 1: Один модуль со всеми классами

Как сделать библиотеку классов, когда классов мало и их не нужно разделять?

Поместить все классы в один файл, например mylib.py. Это самый простой старт. Недостаток - при росте проекта файл становится нечитаемым, сложно поддерживать.

class Circle:
    ...
class Square:
    ...
class Red:
    ...

Self object python (объект self в python)

Импорт: from mylib import Circle.

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

Вариант 2: Разделение на модули без __init__.py

Как организовать классы в подпапки без централизованного импорта?

Создаётся директория, в ней несколько файлов. Импорт выполняется по полному пути: from mylib.shapes import Circle. Если __init__.py отсутствует, такой импорт всё равно работает (начиная с Python 3.3), но пакет становится «неявным пространством имён». Это может затруднить рефакторинг и обнаружение пакета инструментами.

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

Вариант 3: Контроль экспорта через __all__

Как ограничить список имён, доступных при импорте from mylib import *?

В модуле или __init__.py определить список __all__.

__all__ = ['Circle', 'Square']  # только эти имена будут импортированы

Object attribute python (атрибуты объекта в python)

Теперь from mylib import * импортирует только Circle и Square. Это повышает контроль над публичным API библиотеки.

Вариант 4: Использование относительных импортов внутри пакета

Как внутри пакета ссылаться на соседние модули?

Внутри модуля пакета можно использовать относительные импорты: from .shapes import Circle или from ..other import SomeClass. Это делает код независимым от внешнего расположения пакета. Однако относительные импорты не работают, если модуль запущен как скрипт напрямую.

Типичная ошибка: при запуске модуля из командной строки (python mylib/shapes.py) относительный импорт вызовет ImportError. Решение - использовать абсолютные импорты или запускать модуль как часть пакета (python -m mylib.shapes).

Вариант 5: Создание фабрики классов

Как динамически выбирать класс в зависимости от параметра?

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

# в __init__.py
from .shapes import Circle, Square

def create_shape(shape_type, *args):
    if shape_type == 'circle':
        return Circle(*args)
    elif shape_type == 'square':
        return Square(*args)
    else:
        raise ValueError(f'Unknown shape: {shape_type}')

Python call method (вызов метода в python)

Использование:

from mylib import create_shape
s = create_shape('circle', 10)
print(s.area())

Python класс данных (класс данных в python)

314.159

Это упрощает создание объектов и инкапсулирует логику выбора класса.

- класс python определение (определение классов в python)
- Self method python (параметр self в методах python)
- приватные атрибуты python (приватные атрибуты в python)

Расширенные примеры библиотеки классов с иерархией и паттернами

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

Пример 1: Абстрактный базовый класс для плагинов

Создадим пакет plugins с модулями base.py, plugin_a.py, __init__.py.

base.py:

Пример
from abc import ABC, abstractmethod
class Plugin(ABC):
    @abstractmethod
    def run(self, data):
        pass

class PluginMeta(type):
    """Метакласс, автоматически регистрирующий подклассы"""
    _registry = {}
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if not getattr(cls, '_abstract', False):  # не регистрировать ABC
            mcs._registry[name.lower()] = cls
        return cls
    @classmethod
    def get_plugin(mcs, name):
        return mcs._registry.get(name)

plugin_a.py:

Пример
from .base import Plugin, PluginMeta
class PluginA(Plugin, metaclass=PluginMeta):
    def run(self, data):
        return f"PluginA processed: {data}"

__init__.py:

Пример
from .base import Plugin, PluginMeta
from .plugin_a import PluginA
# При импорте PluginA регистрируется автоматически

Теперь можно получать плагин по имени:

Пример
from plugins import PluginMeta
pl_cls = PluginMeta.get_plugin('plugina')
if pl_cls:
    instance = pl_cls()
    print(instance.run('test'))
PluginA processed: test

Этот подход позволяет легко добавлять новые плагины - достаточно создать класс, наследующий от Plugin с метаклассом PluginMeta, и он автоматически регистрируется. Не требуется ручного добавления в реестр.

Пример 2: Композиция и делегирование в библиотеке геометрических фигур

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

Пример
class Color:
    def __init__(self, rgb):
        self.rgb = rgb
    def __repr__(self):
        return f'Color({self.rgb})'

class Shape:
    def __init__(self, color):
        self.color = color

class Circle(Shape):
    def __init__(self, radius, color):
        super().__init__(color)
        self.radius = radius
    def area(self):
        return 3.14 * self.radius ** 2

# Использование
red = Color((255,0,0))
c = Circle(5, red)
print(c.color, c.area())
Color((255,0,0)) 78.5

Такая композиция упрощает тестирование и замену компонентов. Например, можно легко подменить цвет, не меняя форму.

Пример 3: Использование dataclasses для неизменяемых объектов библиотеки

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

Пример
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: float
    y: float

class Line:
    def __init__(self, start: Point, end: Point):
        self.start = start
        self.end = end
    def length(self):
        return ((self.end.x - self.start.x)**2 + (self.end.y - self.start.y)**2)**0.5

p1 = Point(0,0)
p2 = Point(3,4)
line = Line(p1, p2)
print(line.length())
5.0

Dataclasses автоматически генерируют методы __init__, __repr__, __eq__. Флаг frozen=True делает объекты хэшируемыми, что полезно для использования в множествах и словарях.

Пример 4: Менеджер контекста для работы с ресурсами в библиотеке

Реализуем класс, который управляет соединением с базой данных (упрощённо).

Пример
class DatabaseConnection:
    def __enter__(self):
        print("Opening connection...")
        self.conn = "connection_object"
        return self.conn
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing connection...")
        self.conn = None

# Использование
with DatabaseConnection() as conn:
    print(f"Working with {conn}")
Opening connection...
Working with connection_object
Closing connection...

Такой подход гарантирует освобождение ресурса даже при исключениях.

Библиотека классов в Python - comments

En
библиотека классов python (python)