Модель данных Python: от __init__ до __getitem__

Раздел: Python -> Основы Python

Модель данных (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 мог попытаться вызвать обратный метод.

- Python bool (тип bool в python)
- Python how to convert (преобразование типов в python)
- иначе python (конструкция else в 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

Модель данных Python - comments

En
Python data model (python)