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

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

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

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

 

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

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

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

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

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

 

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

Реализация будет очень простой — связный список из пар «ключ — значение». Например, эквивалент ассоциативного массива {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>. Именно он вызывается, если ввести имя переменной в интерактивном интерпретаторе.

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *