web-dev-qa-db-fra.com

Comment se fait-il que les objets de correspondance d'expression régulière ne soient pas itérables même s'ils implémentent __getitem__?

Comme vous le savez peut-être, implémentant un __getitem__ la méthode rend une classe itérable :

class IterableDemo:
    def __getitem__(self, index):
        if index > 3:
            raise IndexError

        return index

demo = IterableDemo()
print(demo[2])  # 2
print(list(demo))  # [0, 1, 2, 3]
print(hasattr(demo, '__iter__'))  # False

Cependant, cela n'est pas vrai pour les objets de correspondance d'expression régulière:

>>> import re
>>> match = re.match('(ab)c', 'abc')
>>> match[0]
'abc'
>>> match[1]
'ab'
>>> list(match)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '_sre.SRE_Match' object is not iterable

Il convient de noter que cette exception n'est pas levée dans le __iter__, car cette méthode n'est même pas implémentée:

>>> hasattr(match, '__iter__')
False

Alors, comment est-il possible d'implémenter __getitem__ sans rendre la classe itérable?

52
Aran-Fey

Il y a des mensonges, des putains de mensonges et puis il y a la documentation Python.

Avoir __getitem__ Pour une classe implémentée dans [~ # ~] c [~ # ~] ne suffit pas pour qu'elle soit itérable. C'est parce qu'il y a en fait 2 emplacements dans le PyTypeObject où le __getitem__ Peut être mappé à: tp_as_sequence et tp_as_mapping . Les deux ont un emplacement pour __getitem__ ( [1] , [2] ).

En regardant la source de SRE_Match , tp_as_sequence Est initialisé à NULL tandis que tp_as_mapping Est défini.

La fonction intégrée iter(), si elle est appelée avec un seul argument, appellera PyObject_GetIter , qui a le code suivant:

f = t->tp_iter;
if (f == NULL) {
    if (PySequence_Check(o))
        return PySeqIter_New(o);
    return type_error("'%.200s' object is not iterable", o);
}

Il vérifie d'abord l'emplacement tp_iter (Évidemment NULL pour les objets _SRE_Match); et à défaut, alors si PySequence_Check renvoie vrai, un nouvel itérateur de séquence, sinon un TypeError est levé.

PySequenceCheck vérifie d'abord si l'objet est une dict ou une sous-classe dict - et renvoie false dans ce cas. Sinon, il renvoie la valeur de

s->ob_type->tp_as_sequence &&
    s->ob_type->tp_as_sequence->sq_item != NULL;

et puisque s->ob_type->tp_as_sequence était NULL pour une instance de _SRE_Match, 0 sera retourné et PyObject_GetIter soulève TypeError: '_sre.SRE_Match' object is not iterable.

52
Antti Haapala