¿Qué es el protocolo de secuencia de Python?

Python hace mucho con métodos mágicos y la mayoría de estos son parte de algún protocolo. Estoy familiarizado con el “protocolo iterador” y el “protocolo numérico”, pero recientemente tropecé con el término “protocolo de secuencia” . Pero incluso después de algunas investigaciones no estoy exactamente seguro de cuál es el “protocolo de secuencia”.

Por ejemplo, la función de la API C PySequence_Check comprueba (de acuerdo con la documentación) si algún objeto implementa el “protocolo de secuencia”. El código fuente indica que esta es una clase que no es un dict pero implementa un método __getitem__ que es aproximadamente idéntico a lo que la documentación en iter también dice:

[…] debe admitir el protocolo de secuencia (el método __getitem__() con argumentos enteros que comienzan en 0). […]

Pero el requisito de comenzar con 0 no es algo que esté “implementado” en PySequence_Check .

Luego está también el tipo de __iter__ __len__ ., que básicamente dice que la instancia debe implementar __reversed__ , __contains__ , __iter__ y __len__ .

    Pero según esa definición, una clase que implementa el “protocolo de secuencia” no es necesariamente una secuencia, por ejemplo, el “modelo de datos” y la clase abstracta garantiza que una secuencia tiene una longitud. Pero una clase que solo implementa __getitem__ (que pasa PySequence_Check ) lanza una excepción cuando usa len(an_instance_of_that_class) .

    ¿Podría alguien aclararme la diferencia entre una secuencia y el protocolo de secuencia (si hay una definición para el protocolo además de leer el código fuente) y cuándo usar qué definición?

    No es realmente consistente.

    Aquí está 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 comprueba si un objeto proporciona el protocolo de secuencia C, implementado a través de un miembro tp_as_sequence en el objeto PyTypeObject representa el tipo del objeto. Este miembro tp_as_sequence es un puntero a una estructura que contiene un montón de funciones para el comportamiento de secuencia, como sq_item para la recuperación de elementos por índice numérico y sq_ass_item para la asignación de elementos.

    Específicamente, PySequence_Check requiere que su argumento no sea un dict, y que proporcione sq_item .

    Los tipos con un __getitem__ escrito en Python proporcionarán sq_item independientemente de si se trata de secuencias o asignaciones conceptuales, por lo que una asignación escrita en Python que no herede de dict pasará PySequence_Check .


    Por otro lado, collections.abc.Sequence solo verifica si un objeto se hereda concretamente de collections.abc.Sequence o si su clase (o una superclase) está register explícitamente con collections.abc.Sequence . Si solo implementas una secuencia por ti mismo sin hacer ninguna de esas cosas, no pasará su isinstance(your_sequence, Sequence) . Además, la mayoría de las clases registradas con collections.abc.Sequence no admiten todos los métodos de collections.abc.Sequence . En general, collections.abc.Sequence es mucho menos confiable de lo que la gente suele esperar que sea.


    En cuanto a lo que cuenta como una secuencia en la práctica, generalmente es cualquier cosa que admita __len__ y __getitem__ con índices enteros comenzando en 0 y no es un mapeo. Si los documentos para una función dicen que toma cualquier secuencia, eso es casi siempre todo lo que necesita. Desafortunadamente, “no es un mapeo” es difícil de probar por razones similares a cómo “es una secuencia” es difícil de precisar.

    Para que un tipo esté de acuerdo con el protocolo de secuencia, estas 4 condiciones deben cumplirse:

    • Recuperar elementos por índice

      item = seq[index]

    • Encuentra artículos por valor

      index = seq.index(item)

    • Contar artículos

      num = seq.count(item)

    • Producir una secuencia invertida.

      r = reversed(seq)