Python классы: иллюстрированные примеры и инструкции

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

Основы создания класса в Python

Рассмотрим наиболее эффективный подход к созданию класса в Python на примере банковского счета. Используем __slots__ для экономии памяти и @property для контроля доступа.


class BankAccount:
    __slots__ = ('_balance', '_owner')
    def __init__(self, owner, balance=0):
        self._owner = owner
        self._balance = balance
    @property
    def balance(self):
        return self._balance
    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError('Balance cannot be negative')
        self._balance = value
    def deposit(self, amount):
        if amount <= 0:
            raise ValueError('Deposit amount must be positive')
        self._balance += amount
    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError('Withdrawal amount must be positive')
        if amount > self._balance:
            raise ValueError('Insufficient funds')
        self._balance -= amount
    def __repr__(self):
        return 'BankAccount(owner=' + self._owner + ', balance=' + str(self._balance) + ')'

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

Пояснение: __slots__ предотвращает создание __dict__ для каждого экземпляра, что уменьшает расход памяти. @property позволяет контролировать установку значения баланса, отклоняя отрицательные значения. Методы deposit и withdraw проверяют аргументы и состояние счета. __repr__ обеспечивает читаемое представление.

Как упростить класс для хранения данных с помощью dataclass?

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


from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    year: int
    isbn: str = ''

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

Такой класс подходит для представления сущностей с фиксированным набором полей. Возникающие проблемы: поля без аннотаций типов не будут учтены; если требуется сложная логика инициализации, dataclass может не подойти.

Типичная ошибка: забыть указать тип поля. В этом случае dataclass проигнорирует поле, и оно не появится в конструкторе.

Как сделать класс неизменяемым?

Установите параметр frozen=True в декораторе dataclass или используйте namedtuple.


from dataclasses import dataclass

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

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

Любая попытка изменить атрибуты такого экземпляра вызовет исключение FrozenInstanceError. Это гарантирует неизменность состояния объекта.

Стоит учитывать, что если внутри замороженного класса есть изменяемые объекты (список, словарь), они останутся изменяемыми, что нарушает иммутабельность.

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

Используйте @classmethod для создания альтернативных конструкторов.


import datetime

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        age = datetime.date.today().year - birth_year
        return cls(name, age)

    @classmethod
    def from_string(cls, data):
        name, age = data.split(',')
        return cls(name.strip(), int(age.strip()))

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

Метод класса получает сам класс (cls) и возвращает экземпляр, созданный через __init__. Так можно удобно конвертировать данные из разных форматов.

Частая ошибка - забыть cls в параметрах или вернуть не тот объект (например, не вызвать cls(...)).

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

Переопределите методы __getattr__ и __setattr__ для обработки произвольных атрибутов.


class DynamicAttributes:
    def __init__(self, **kwargs):
        self._store = {}
        for k, v in kwargs.items():
            self._store[k] = v

    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(name)
        if name in self._store:
            return self._store[name]
        raise AttributeError('DynamicAttributes object has no attribute ' + name)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            self._store[name] = value

    def __repr__(self):
        items = ', '.join('{k}={v!r}'.format(k=k, v=v) for k, v in self._store.items())
        return 'DynamicAttributes(' + items + ')'

Внутренний словарь _store хранит все динамические атрибуты. Для системных атрибутов (начинающихся с _) используется обычное присваивание. Это позволяет создавать классы, атрибуты которых определяются во время выполнения.

Опасность: легко попасть в бесконечную рекурсию, если в __setattr__ попытаться присвоить значение атрибуту без вызова super(). Также сложно отлаживать, так как атрибуты не фиксированы.

Типичные ошибки при создании классов в Python

  • Использование изменяемого объекта (список, словарь) в качестве значения по умолчанию для аргумента метода. Все вызовы будут использовать один и тот же объект.
  • Забывание self в определении метода - приводит к ошибке при вызове.
  • Попытка создать свойство с тем же именем, что и атрибут, что приводит к циклическому вызову.
  • Неверный порядок разрешения методов при множественном наследовании - следует изучить MRO.
  • Изменение списка или словаря, переданного как аргумент по умолчанию, влечёт неожиданное поведение для всех экземпляров.
- Object attribute python (атрибуты объекта в python)
- Python call method (вызов метода в python)
- Python класс данных (класс данных в python)

Менеджер контекста для работы с файлами

Класс, реализующий протокол менеджера контекста, позволяет использовать оператор with.

Пример

class OpenFile:
    def __init__(self, filename, mode='r'):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        return False  # не подавляем исключения

Пример использования:

Пример

with OpenFile('test.txt', 'w') as f:
    f.write('Hello')
# файл автоматически закрыт даже при исключении

Результат: файл test.txt создан и закрыт корректно.

Класс-итератор для обратного отсчёта

Реализация протокола итератора с помощью методов __iter__ и __next__.

Пример

class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

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

Пример

for i in Countdown(5):
    print(i, end=' ')
5 4 3 2 1

Декоратор класса для добавления методов

Декоратор, который принимает класс и добавляет в него новый метод.

Пример

def add_method(cls):
    def new_method(self):
        return 'Добавленный метод'
    cls.new_method = new_method
    return cls

@add_method
class MyClass:
    pass

obj = MyClass()
print(obj.new_method())
Добавленный метод

Одиночка (Singleton) через метакласс

Метакласс, гарантирующий, что у класса будет только один экземпляр.

Пример

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=SingletonMeta):
    pass

a = SingletonClass()
b = SingletonClass()
print(a is b)  # True
True

Дескриптор для проверки неотрицательности

Дескриптор контролирует присваивание значения атрибуту, гарантируя его неотрицательность.

Пример

class PositiveNumber:
    def __set_name__(self, owner, name):
        self.name = f'_{name}'

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.name, 0)

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError('Значение должно быть неотрицательным')
        setattr(obj, self.name, value)

class Order:
    quantity = PositiveNumber()

    def __init__(self, quantity):
        self.quantity = quantity

order = Order(10)
# order.quantity = -5  # вызовет ValueError

Использование дескриптора позволяет вынести логику валидации в отдельный класс, повышая переиспользуемость.

Пример класса в Python - comments

En
Python пример класса (python)