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.
- Изменение списка или словаря, переданного как аргумент по умолчанию, влечёт неожиданное поведение для всех экземпляров.
Менеджер контекста для работы с файлами
Класс, реализующий протокол менеджера контекста, позволяет использовать оператор 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
Использование дескриптора позволяет вынести логику валидации в отдельный класс, повышая переиспользуемость.