Классы Python от базовых конструкций до продвинутых техник

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

Классы в Python: базовые конструкции и альтернативы

Как определить класс и использовать наследование в Python?

Основной способ определения класса это использование ключевого слова class. Внутри класса определяются методы, первый параметр которых self, ссылающийся на экземпляр. Конструктор __init__ инициализирует атрибуты. Наследование позволяет расширять функциональность, а полиморфизм работает с разными классами через единый интерфейс.


class Animal:
    def __init__(self, name):
        self.name = name
        self._age = 0

    def speak(self):
        raise NotImplementedError("Должен быть переопределён")

class Dog(Animal):
    def speak(self):
        return f"{self.name} говорит Гав"

class Cat(Animal):
    def speak(self):
        return f"{self.name} говорит Мяу"

def make_sound(animal):
    print(animal.speak())

dog = Dog("Бобик")
cat = Cat("Мурка")
make_sound(dog)
make_sound(cat)

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

В примере self передается неявно, наследование задается скобками, а полиморфизм реализован через переопределение метода speak. Для абстрактного поведения базовый класс выбрасывает исключение.

Типичные ошибки: пропуск self в определении метода, забытый вызов super().__init__ в наследнике, попытка создать экземпляр класса, не переопределившего абстрактный метод (если нет защиты). Рекомендуется использовать ABC для явного указания абстрактных методов.

Как уменьшить шаблонный код в классах, хранящих данные?

Модуль dataclasses предоставляет декоратор @dataclass, который автоматически генерирует __init__, __repr__, __eq__ и другие методы на основе аннотаций полей.


from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0

p = Point(1.0, 2.0)
print(p)

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

Пояснение: аннотации обязательны. Можно задать неизменяемость через frozen=True, управлять порядком полей с помощью field, а для изменяемых типов (список, словарь) использовать default_factory.

Если не указать тип поля, dataclass проигнорирует его. Изменяемые значения по умолчанию (например, list) будут общими для всех экземпляров это частая ошибка. Исправляется через field(default_factory=list).

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

Декоратор @property позволяет определить метод, который при обращении ведет себя как атрибут. Сеттеры и делитеры дают контроль над изменением и удалением.


class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Радиус должен быть положительным")
        self._radius = value

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

c = Circle(5)
c.radius = 10
print(c.area)

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

Свойства удобны для валидации, вычисляемых полей и придания интерфейсу единообразия.

Создание свойства с тем же именем, что и атрибут, вызывает бесконечную рекурсию в сеттере, если внутри сеттера происходит присваивание self.radius = value. Нужно использовать атрибут с подчеркиванием (_radius).

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

@staticmethod определяет метод, не получающий ни self, ни cls. Он ведет себя как обычная функция, помещенная в пространство имен класса. @classmethod получает cls и может вызываться как от экземпляра, так и от класса, часто используется для альтернативных конструкторов.


class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

    @classmethod
    def create_from_string(cls, s):
        import ast
        return cls(*ast.literal_eval(s))

class Vector(MathUtils):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v = Vector.create_from_string("(3,4)")
print(v, MathUtils.add(2,3))

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

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

Новички путают staticmethod и classmethod. Статический метод не видит класс и не может создавать его экземпляры; classmethod видит класс и может вызывать конструктор.

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

Модуль abc предоставляет ABC (Abstract Base Class) и декоратор @abstractmethod. Класс, наследующий от ABC и содержащий abstractmethod, нельзя инстанциировать, если не переопределены все абстрактные методы.


from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

    def perimeter(self):
        return 4 * self.side

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

Теперь Square можно создать, а Shape нет.

Если забыть переопределить любой абстрактный метод, при создании экземпляра возникнет TypeError. Также нельзя создать экземпляр самого абстрактного класса. Полезно для контрактов и проектирования.

Как уменьшить потребление памяти экземплярами класса?

Атрибут класса __slots__ определяет фиксированный набор атрибутов, которые может иметь экземпляр. При этом у экземпляра не создается словарь __dict__, что экономит память и ускоряет доступ к атрибутам.


class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
# p.z = 3  # AttributeError

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

Нельзя добавлять новые атрибуты вне списка. __slots__ может конфликтовать с множественным наследованием, если не соблюдать правила (каждый класс должен иметь свои __slots__). Не работает с property, если в __slots__ не указано имя свойства. Несовместим с наследованием от классов без __slots__.
- Python call method (вызов метода в python)
- Python класс данных (класс данных в python)
- Class method python (методы классов в python)

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

Пример 1. Дескриптор для валидации числовых полей

Дескрипторы мощный механизм, лежащий в основе property. Создадим дескриптор, проверяющий, что значение является положительным числом.

Пример

class PositiveNumber:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, obj, objtype):
        return obj.__dict__.get(self.name)

    def __set__(self, obj, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError(f"{self.name} должно быть положительным числом")
        obj.__dict__[self.name] = value

class Rectangle:
    width = PositiveNumber()
    height = PositiveNumber()

    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

r = Rectangle(10, 5)
print(r.area())
try:
    r.width = -5
except ValueError as e:
    print(e)
50
width должно быть положительным числом

Метод __set_name__ автоматически вызывается при создании класса и запоминает имя атрибута. __get__ возвращает значение из словаря экземпляра, __set__ проверяет и сохраняет. Это позволяет избежать дублирования логики валидации.

Пример 2. Метакласс для автоматической регистрации подклассов

Метаклассы управляют созданием классов. Реализуем метакласс, который собирает все неабстрактные подклассы в реестр.

Пример

class RegistryMeta(type):
    registry = {}
    def __new__(cls, name, bases, dct):
        new_class = super().__new__(cls, name, bases, dct)
        if not dct.get('_abstract'):
            cls.registry[name] = new_class
        return new_class

class Base(metaclass=RegistryMeta):
    _abstract = True

class Dog(Base):
    pass

class Cat(Base):
    pass

class Sheep(Base):
    pass

print(RegistryMeta.registry)
{'Dog': , 'Cat': , 'Sheep': }

Здесь __new__ метакласса вызывается при каждом создании класса. Если в словаре класса нет ключа '_abstract' (или он False), класс регистрируется. Это удобно для плагинов и систем наследования, где нужен список всех наследников.

Пример 3. Перегрузка операторов для вектора

Переопределив магические методы, можно заставить объекты работать с операторами +, -, ==, abs и другими.

Пример

import math

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"

    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)

    def __eq__(self, other):
        return (self.x == other.x) and (self.y == other.y)

    def __abs__(self):
        return math.hypot(self.x, self.y)

v1 = Vector2D(3, 4)
v2 = Vector2D(1, 2)
print(v1 + v2)
print(v1 - v2)
print(v1 == v2)
print(abs(v1))
Vector2D(4, 6)
Vector2D(2, 2)
False
5.0

Магические методы делают классы естественными для интуитивных операций. Можно перегружать __neg__, __mul__, __truediv__ и многие другие.

Классы в Python - comments

En
классы языка python (python)