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?
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
.