Модель данных Python: от __init__ до __getitem__
Модель данных (data model) в Python определяет, как объекты взаимодействуют с языком через набор специальных методов (также называемых dunder-методами). Реализация этих методов позволяет пользовательским классам поддерживать итерацию, индексацию, арифметические операции, преобразование в строку и другие протоколы.
Реализация модели данных в пользовательских классах
Как сделать класс, который ведет себя как встроенный тип?
Рассмотрим класс Vector, представляющий точку на плоскости. Реализовав методы __init__, __repr__, __str__, __len__, __getitem__, __setitem__ и __add__, мы получим полноценный объект.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Vector({self.x!r}, {self.y!r})'
def __str__(self):
return f'({self.x}, {self.y})'
def __len__(self):
return 2
def __getitem__(self, index):
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError('Vector index out of range')
def __setitem__(self, index, value):
if index == 0:
self.x = value
elif index == 1:
self.y = value
else:
raise IndexError('Vector index out of range')
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
Python arguments types (типы аргументов в python)
После определения класса можно выполнять операции:
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(repr(v1))
print(v1)
print(len(v1))
print(v1[0], v1[1])
v1[0] = 10
print(v1)
v3 = v1 + v2
print(v3)
Python load module (загрузка модуля в python)
Vector(1, 2) (1, 2) 2 1 2 (10, 2) (13, 6)
Pd pandas python (импорт пакетов python)
Типичные ошибки:
- Не возвращать NotImplemented из арифметических методов, если тип аргумента не поддерживается. Это приводит к исключению TypeError при попытке сложения с другим типом.
- Путать __repr__ и __str__. Первый должен предоставлять однозначное строковое представление для отладки, второй - удобочитаемое для пользователя.
- Не соблюдать сигнатуры методов, например, __len__ должен возвращать целое число, иначе вызов len() завершится ошибкой.
Как сократить объем кода при определении классов?
Декоратор @dataclass автоматически генерирует __init__, __repr__, __eq__ и другие методы.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
How to use python (как использовать python)
Теперь класс Point имеет полную поддержку инициализации и строкового представления:
p = Point(1.0, 2.0)
print(p)
как писать код на python (как писать код на python)
Point(x=1.0, y=2.0)
Python log 2 (логарифм по основанию 2 в python)
Проблемы:
- По умолчанию поля являются изменяемыми. Для неизменяемости нужно указать frozen=True.
- Порядок полей в строковом представлении соответствует порядку объявления. Если нужен другой порядок, придется переопределять __repr__ вручную.
- Нельзя добавить арифметические методы автоматически - их все равно придется определять.
Как создать простой неизменяемый объект с доступом по атрибутам и индексу?
collections.namedtuple создает класс, экземпляры которого ведут себя как кортежи с именованными полями.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x, p.y)
print(p[0], p[1])
print(len(p))
Python data model (модель данных python)
1 2 1 2 2
Python begin end (начало и конец программы на python)
Ограничения:
- Объекты неизменяемы, что хорошо для хеширования, но не подходит для частых изменений.
- Добавление методов требует наследования, что усложняет код.
- Строковое представление фиксированное, его трудно изменить без подкласса.
Как реализовать поддержку всех арифметических операций, включая обратные?
Для полноценной перегрузки операторов необходимо определять как обычные (__add__), так и обратные методы (__radd__), а также методы для присваивания (__iadd__), если требуется поддержка +=.
class Vector:
# ... предыдущие методы ...
def __radd__(self, other):
if isinstance(other, (int, float)):
return Vector(self.x + other, self.y + other)
return NotImplemented
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
return self
return NotImplemented
Begin python (начало работы с python)
Теперь возможно сложение с числом:
v = Vector(1, 2)
v2 = 10 + v
v += Vector(1, 1)
print(v2)
print(v)
Python локальная переменная (локальные переменные в python)
(11, 12) (2, 3)
Распространенная ошибка: забыть определить обратные методы, что приводит к TypeError при сложении с объектом другого типа слева. Всегда следует возвращать NotImplemented для неподдерживаемых типов, чтобы Python мог попытаться вызвать обратный метод.
Расширенные примеры использования модели данных
Итерация: протокол итератора
Реализация __iter__ и __next__ позволяет объекту использоваться в циклах for.
class CountDown:
def __init__(self, start):
self.current = start + 1
def __iter__(self):
return self
def __next__(self):
self.current -= 1
if self.current < 0:
raise StopIteration
return self.current
for i in CountDown(3):
print(i, end=' ')
3 2 1 0
Контекстный менеджер: протокол with
Методы __enter__ и __exit__ обеспечивают управление ресурсами.
class FileManager:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
return False
with FileManager('test.txt', 'w') as f:
f.write('Hello, data model!')
После выхода из блока with файл будет закрыт автоматически.
Хеширование и равенство для использования в множествах
Для того чтобы объекты можно было помещать в set или использовать как ключи словаря, необходимо реализовать __hash__ и __eq__.
class ImmutablePoint:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if isinstance(other, ImmutablePoint):
return (self.x, self.y) == (other.x, other.y)
return NotImplemented
def __hash__(self):
return hash((self.x, self.y))
p1 = ImmutablePoint(1, 2)
p2 = ImmutablePoint(1, 2)
p3 = ImmutablePoint(3, 4)
s = {p1, p2, p3}
print(len(s))
2