Python по всем правилам. Делаем свою структуру данных совместимой с фичами Python

Если ты пишешь на Python, то наверняка видел в стандартных библиотеках определения методов, обернутых в двойные подчеркивания. Эти «магические» методы образуют многие из полезных интерфейсов, которыми ты постоянно пользуешься, — например, когда получаешь значение по номеру элемента или выводишь что-то через print. Эти методы можно и нужно использовать и в своих программах. Как — сейчас покажу.

Вообще, любой хорошо спроектированный язык определяет набор соглашений и применяет их в своей стандартной библиотеке. Соглашения могут касаться как чисто внешних признаков, вроде синтаксиса названий (CamelCase, snake_case), так и поведения объектов. Язык Python в этом смысле — весьма последовательный.

Синтаксис в Python нерасширяем, но зато интерфейсы взаимодействия между объектами хорошо определены и доступны любому разработчику. В отличие от Java в Python нет формальной концепции интерфейсов класса, любой класс может предоставлять любой интерфейс, достаточно определить методы с нужными именами и аргументами и убедиться, что их поведение соответствует ожидаемому.

INFO

Везде в статье речь идет о Python 3. Python 2.7 уже можно считать мертвым.

Интерфейсы в Python

Поскольку Python динамически типизирован, проверить соответствие класса объекта на этапе компиляции невозможно. Возможности для указания аннотаций типов из Python 3.5 предназначены прежде всего для внешних статических анализаторов и не используются во время выполнения. Явная проверка класса с помощью type() считается дурным тоном.

В крайнем случае можно использовать isinstance() — в отличие от type() эта функция возвращает True не только для самого класса, но и для всех его потомков. Проверка с помощью type() сломается при наследовании, именно поэтому люди к ней так плохо относятся.

Интерфейсы объектов определяются так называемыми магическими методами. По соглашению их имена окружаются двойным подчеркиванием. Метод __init__(), который служит конструктором класса, — пример, известный каждому. Почти каждая стандартная операция, включая форматированный вывод и арифметику, реализуется каким-то магическим способом.

WWW

Полное описание стандартных магических методов можно найти в разделе документации Data Model.

Для демонстрации мы напишем примитивную и медленную реализацию ассоциативного массива на основе списка из кортежей, «идентичную натуральной» в смысле интерфейса.

Делаем свой ассоциативный массив

Реализация будет очень простой — связный список из пар «ключ — значение». Например, эквивалент ассоциативного массива {1: 2, 3: 4} будет [(1, 2), (3, 4)]. Она значительно медленнее встроенной: например, поиск значения элемента по ключу будет требовать O(n) операций, в то время как встроенная требует O(1). Для демонстрации, впрочем, вполне сойдет.

Свой класс мы назовем Assoc. Определим класс и его конструктор:

class Assoc(object):
    def __init__(self, contents=[]):
        self._contents = contents

Для удобства тестирования мы сделали, чтобы начальное значение можно было передать в конструкторе, вроде Assoc([(1,2), (3,4)]).

Теперь приступим к магии! Будем считать, что код мы сохранили в файл assoc.py.

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

В Python существуют два разных метода для получения строкового представления объектов: __repr__ и __str__. Различие между ними довольно тонкое, но существенное: __repr__, по замыслу, должен выдавать допустимое выражение Python, с помощью которого можно создать такой же объект. Это не всегда возможно, поэтому на практике у многих объектов он возвращает просто что-то такое, что позволяет разработчику идентифицировать объект, вроде <Foo object at 0x7f94fe2f22e8>. Именно он вызывается, если ввести имя переменной в интерактивном интерпретаторе.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Даниил Батурин: @dmbaturin Координатор проекта VyOS (https://vyos.io), «языковед», функциональщик, иногда сетевой администратор

Комментарии (3)

  • Сложно воспринимать текст серьезно после такого начала: def __init__(self, contents=[])
    Потому что вот к чему это приводит:
    >>> a = Assoc()
    >>> a['bar'] = 3
    >>> print(a)
    {bar: 3}

    >>> b = Assoc()
    >>> print(b)
    {bar: 3}

  • Что-то странно видеть под названием статьи "Делаем свою структуру данных", туториал по дандер методам. 🤷🏼‍♂️

    • Буквально одно слово в заголовке не дочитали.