Что такое протокол последовательности Python?

Python много делает с магическими методами, и большинство из них являются частью какого-то протокола. Я знаком с протоколом итератора и числовым протоколом, но недавно наткнулся на термин протокол последовательности. Но даже после некоторых исследований я не совсем уверен, что такое протокол последовательности.

Например, функция C API PySequence_Check проверяет (согласно документации), если какой-то объект реализует протокол последовательности. исходный код указывает, что это класс, не dict, но реализует метод __getitem__, который примерно идентичен документации на iter также указывает:

[...] должен поддерживать протокол последовательности (метод __getitem__() с целочисленными аргументами, начинающимися с 0).[...]

Но требование начинать с 0 не реализовано в PySequence_Check.

Также есть тип collections.abc.Sequence, что в основном говорит о том, что экземпляр должен реализовать __reversed__, __contains__, __iter__ и __len__.

Но по этому определению класс, реализующий протокол последовательности, не обязательно является последовательностью, например, модель данных и абстрактный класс гарантируют, что последовательность имеет длину. Но класс, просто реализующий __getitem__ (передавая PySequence_Check), выдает исключение при использовании len(an_instance_of_that_class).

Может ли кто-нибудь объяснить мне разницу между последовательностью и протоколом последовательности (если есть определение протокола помимо чтения исходного кода) и когда какое определение использовать?


person MSeifert    schedule 23.04.2017    source источник
comment
collections.abc.Sequence требует __getitem__ и __len__. Для всего остального есть методы миксина. Что касается итерации, если только __getitem__ определен без __iter__, то встроенный iter создает простой итератор, который начинается с индекса 0. Чтобы reversed работал, __len__ также должен быть определен, чтобы он мог начинаться с последнего индекса.   -  person Eryk Sun    schedule 23.04.2017
comment
@eryksun Но классу не нужен __len__ для реализации протокола последовательности (что касается PySequence_Check). И класс, реализующий __len__ и __getitem__, но не наследующий от collections.abc.Sequence, не передает isinstance(an_instance, Sequence). Именно это и вызвало мой вопрос. :)   -  person MSeifert    schedule 23.04.2017
comment
Здесь есть хорошее подробное объяснение PySequence_Check: grokbase.com/t/python/python-list/054erpfcep/   -  person Ashwini Chaudhary    schedule 05.07.2018


Ответы (2)


Это не совсем последовательно.

Вот PySequence_Check:

int
PySequence_Check(PyObject *s)
{
    if (PyDict_Check(s))
        return 0;
    return s != NULL && s->ob_type->tp_as_sequence &&
        s->ob_type->tp_as_sequence->sq_item != NULL;
}

PySequence_Check проверяет, предоставляет ли объект протокол последовательности C, реализованный через член tp_as_sequence в PyTypeObject, представляющий тип объекта. Этот элемент tp_as_sequence является указателем на структуру, содержащую набор функций для поведения последовательности, таких как sq_item для извлечения элемента по числовому индексу и sq_ass_item для назначения элемента.

В частности, PySequence_Check требует, чтобы его аргумент не был словарем, и чтобы он предоставлял sq_item.

Типы с __getitem__, написанные на Python, будут предоставлять sq_item независимо от того, являются ли они концептуально последовательностями или сопоставлениями, поэтому сопоставление, написанное на Python, которое не наследуется от dict, будет передавать PySequence_Check.


С другой стороны, collections.abc.Sequence только проверяет, действительно ли объект наследуется от collections.abc.Sequence или его класс (или суперкласс) явно registered с collections.abc.Sequence. Если вы просто реализуете последовательность самостоятельно, не делая ни того, ни другого, она не пройдет isinstance(your_sequence, Sequence). Кроме того, большинство классов, зарегистрированных с помощью collections.abc.Sequence, не поддерживают все методы collections.abc.Sequence. В целом, collections.abc.Sequence намного менее надежен, чем люди обычно ожидают.


Что касается того, что на практике считается последовательностью, то обычно это все, что поддерживает __len__ и __getitem__ с целочисленными индексами, начинающимися с 0, и не является отображением. Если в документах для функции сказано, что она принимает любую последовательность, это почти всегда все, что ей нужно. К сожалению, трудно проверить, не является ли отображением, по причинам, аналогичным тому, как трудно определить последовательность.

person user2357112 supports Monica    schedule 23.04.2017
comment
Я думаю, что PySequence_Check исключает dict, потому что подклассы могут реализовать __getitem__, а для пользовательских классов они возвращают True при реализации __getitem__: gist.github.com/MSeifert04/e39d91f9d262618a32f7db14aaab15f4. Спасибо за ответ (особенно за указание на то, что collections.abc.Sequence не имеет __subclasshook__, было для меня новым), я оставлю его непринятым на другой день, если кто-то еще захочет дать ответ. - person MSeifert; 23.04.2017
comment
@MSeifert: Да, я ошибался насчет определяемых пользователем классов и sq_item. Я мог бы поклясться, что не было обработки для обеспечения sq_item путем упаковки __getitem__, но, по-видимому, есть, и это не ново. - person user2357112 supports Monica; 23.04.2017
comment
См. issue 23864 относительно ограничений для issubclass с ABC, которые не являются пони с одним трюком. Это всегда казалось мне излишне ограниченным. - person Eryk Sun; 23.04.2017
comment
@eryksun Я спросил Гвидо, может ли он добавить отсутствующий __subclasshook__ в вашу проблему, так как она все еще открыта. Кстати, вы знаете, почему таблица Collections Abstract Base Classes отсутствует какой-либо абстрактный метод миксина (например, в классе Reversible отсутствует метод миксина __iter__, а в классе Coroutine отсутствует абстрактный метод __await__)? - person Maggyero; 10.12.2018
comment
@Maggyero: это унаследованные абстрактные методы. Reversible наследует __iter__ от Iterable, а Coroutine наследует __await__ от Awaitable. (Кроме того, __iter__ не является методом миксина, потому что реализация __iter__ в терминах __reversed__ была бы неэффективной и странной.) - person user2357112 supports Monica; 10.12.2018
comment
@user2357112 user2357112 Хорошо, но почему эти унаследованные абстрактные методы не перечислены в столбце Абстрактные методы классов Reversible и Coroutine (в отличие, например, от унаследованных абстрактных методов класса Collection, которые перечислены)? - person Maggyero; 10.12.2018

Чтобы тип соответствовал протоколу последовательности, должны быть выполнены следующие 4 условия:

  • Получить элементы по индексу

    item = seq[index]

  • Поиск предметов по стоимости

    index = seq.index(item)

  • Подсчет предметов

    num = seq.count(item)

  • Произвести обратную последовательность

    r = reversed(seq)

person FruitBat    schedule 01.10.2018
comment
Не могли бы вы указать источник этой информации или объяснить, откуда вы это знаете? - person EntangledLoops; 10.03.2021