Pixamp

Контейнеры в Python

18.02.2019 19:40 CPython 3.7.2

The article image

В рамках данной статьи мы попробуем понять, что такое контейнеры (containers) в Python, познакомимся со встроенными контейнерами, а также попытаемся разобраться с последовательностями (sequences) и отображениями (mappings). По ходу дела освоим операторы is и in, а также встроенные функции issubclass() и isinstance().

Контейнеры – это такие объекты, которые могут хранить другие объекты. Примерами контейнеров могут служить уже знакомые нам списки, кортежи, множества и словари:

>>> l = [1.5, 2.2, 3.14]          # список (list)
>>> t = ('spam', 'eggs', 'ham')   # кортеж (tuple)
>>> s = {286, 386, 486}           # множество (set)
>>> d = {'l': l, 't': t, 's': s}  # словарь (dict)

Мы видим, что…

  1. список l хранит числа с плавающей точкой 1.5, 2.2 и 3.14,
  2. кортеж t хранит строки 'spam', 'eggs' и 'ham',
  3. множество s – целые числа 286, 386 и 486,
  4. словарь d – все ранее перечисленные объекты.

Впрочем, если быть точным, то здесь надо заметить, что контейнеры хранят не сами объекты, а ссылки на них.

Давайте проверим это утверждение. Для этого рассмотрим такой пример:

>>> obj1 = 'unix time'
>>> obj2 = (1, 1, 1970)
>>> obj3 = [19, 1, 2038]
>>> container = (obj1, obj2, obj3)  # контейнер, содержащий ссылки на объекты obj1, obj2 и obj3
>>> container
('unix time', (1, 1, 1970), [19, 1, 2038])
>>> container[0] is obj1
True
>>> container[1] is obj2
True
>>> container[2] is obj3
True

Примечание

Обратите внимание, что здесь не только container является контейнером, но и obj2, а также obj3. Контейнеры могут хранить ссылки на другие контейнеры!

В примере я использую оператор is, который позволяет проверить ссылаются ли его операнды на один и тот же объект или нет. Например, container[0] и obj1 ссылаются на одну и ту же строку unix time, поэтому результат проверки – True.

В качестве альтернативы такой проверке я также мог бы, например, использовать встроенную функцию Python id(), которая возвращает уникальное для объекта целое число (в CPython – это адрес объекта в памяти):

>>> id(container[0]) == id(obj1)
True

Если возвращаемые id() числа одинаковые, то container[0] и obj1 ссылаются на один и тот же объект.

Проиллюстрируем взаимосвязь данных объектов на уровне ссылок:

Container references

Мы можем изменить один из объектов, при этом изменения коснутся и нашего контейнера container:

>>> obj3[:] = [13, 12, 1901]  # меняем "19, 1, 2038" на "13, 12, 1901"
>>> container
('unix time', (1, 1, 1970), [13, 12, 1901])

Чаще всего контейнеры являются либо последовательностями, либо отображениями. В свою очередь, последовательности бывают изменяемыми (mutable sequences) и неизменяемыми (immutable sequences). Примером изменяемой последовательности является список, а неизменяемой – кортеж. Аналогичная ситуация и с отображениями: есть изменяемые (mutable mappings), а есть неизменяемые (immutable mapping). Словарь является примером изменяемого отображения.

Примечание

Обратите внимание, что если контейнер неизменяемый, то это не значит, что объекты, ссылки на которые он хранит, не могут меняться. Мы видели в последнем примере, что они могут меняться: container – кортеж, который является неизменяемой последовательностью, но тем не менее мы без проблем изменили один из его объектов. “Неизменяемость” здесь означает то, что мы не можем добавлять, удалять или заменять элементы самого кортежа, не более.

Разные типы контейнеров отличаются друг от друга возможностями, которые они предоставляют. В зависимости от наличия тех или иных методов (в том числе магических) можно говорить о том или ином типе. Один из методов, который рекомендуется реализовывать при написании контейнера – метод __contains__().

Например, все контейнеры из примеров выше поддерживают оператор in, то есть мы можем проверить, содержит ли тот или иной контейнер нужный нам объект:

>>> 1.5 in l           # l = [1.5, 2.2, 3.14]
True
>>> 'eggs' in t        # t = ('spam', 'eggs', 'ham')
True
>>> 486 in s           # s = {286, 386, 486}
True
>>> 'l' in d           # d = {'l': l, 't': t, 's': s}
True
>>> obj3 in container  # container = (obj1, obj2, obj3)
True

В случае использования оператора in вызывается магический метод контейнера __contains__(), который возвращает True или False в зависимости от наличия или отсутствия в контейнере нужного объекта.

Для разных типов контейнеров характерны свои методы. По совокупности таких методов можно определить предоставляет тот или иной класс определенный интерфейс или нет.

Модуль collections.abc содержит большую коллекцию так называемых абстрактных классов (про абстрактные классы мы подробно поговорим в следующих статьях), которые могут помочь с реализацией собственных классов контейнеров, а также позволяют проверить предоставляется ли тот или иной интерфейс:

>>> issubclass(list, collections.abc.MutableSequence)
True
>>> issubclass(tuple, collections.abc.Sequence)
True
>>> issubclass(dict, collections.abc.MutableMapping)
True

Функция issubclass() возвращает True, если класс, передаваемый первым аргументом, является прямым, непрямым или виртуальным подклассом класса, передаваемого вторым аргументом. В примере выше list является как раз таким виртуальным подклассом collections.abc.MutableSequence. Он предоставляет интерфейс изменяемой последовательности, но не является прямым или непрямым потомком MutableSequence.

Если мы посмотрим на реализацию MutableSequence, то увидим, что он расширяет Sequence (или, говоря другими словами, MutableSequence является подклассом Sequence). А Sequence является подклассом Reversible и Collection, которые, в свою очередь, тоже являются подклассами других классов:

Mutable sequence

Класс Container – один из базовых классов Collection. Единственное требование к объекту предоставляющему интерфейс Container – это наличие метода __contains__(). Отсюда, объект предоставляющий интерфейс MutableSequence должен иметь метод __contains__() плюс все методы других соответствующих MutableSequence базовых классов (таких как Sequence, Reversible, Collection и других). Пользуясь таблицей на соответствующей странице официальной документации, можно распутать все хитросплетения наследования, а также посмотреть на список методов того или иного класса.

Так, для большинства операции из данного списка, мы можем сопоставить соответствующий метод из вышеупомянутой таблицы collections.abc. Например, мы уже разобрались, что x in s – это __contains__(), а вот s[i] – это __getitem__(), а len(s)__len__().


Помимо списков и кортежей есть также текстовые и бинарные последовательности. Текстовые последовательности или просто строки (str) предоставляют интерфейс неизменяемой последовательности (плюс дополнительный набор специальных методов). Поддерживаются все соответствующие операции. Например:

>>> text = 'I remember piano lessons'
>>> 'piano' in text  # вызывается __contains__()
True
>>> text[0]          # __getitem__()
'I'
>>> len(text)        # __len__()
24

Проверяем, предоставляет ли text интерфейс collections.abc.Sequence:

>>> isinstance(text, collections.abc.Sequence)
True

Примечание

Обратите внимание, что строки (str), последовательности байт (bytes), массивы байт (bytearray) и тому подобное формально не являются контейнерами, так как не хранят ссылки на другие объекты. Контейнеры могут хранить ссылки на объекты разных типов, тогда как простые последовательности ограничены по сути одним типом данных (хорошим примером такой простой последовательности здесь может быть, например, массив array.array). Такие последовательности хоть и уступают в гибкости контейнерным последовательностям, но позволяют сэкономить память, а также достичь большей эффективности в выполнении тех или иных задач.

В дополнение к встроенным, модуль collections содержит набор специализированных контейнеров. Но о них мы поговорим в следующий раз.


Список полезных ссылок по теме:

  1. Список общих операций для последовательностей
  2. Дополнение к списку выше для изменяемых последовательностей
  3. Коллекция контейнеров из модуля collections
  4. Абстрактные базовые классы collections.abc

Дата последнего редактирования статьи: 15.05.2019 15:22

Следующая статья › Операции над списками в Python
Предыдущая статья › Функции reduce, map и zip в Python

Главная страница