Les chaînes en Python devraient-elles être itérables?

Et Guido a créé des chaînes à l'image de C, à l'image de tableaux de personnages qui les ont créés. Et Guido a vu que c'était bon. Ou pas?

Imaginez que vous écrivez du code complètement idiomatique pour contourner certaines données avec l'imbrication. Beau vaut mieux que laid, simple vaut mieux que complexe, vous vous arrêtez donc à la version suivante du code:

from collections.abc import Iterable def traverse(list_or_value, callback): if isinstance(list_or_value, Iterable): for item in list_or_value: traverse(item, callback) else: callback(list_or_value) 

Vous écrivez un test unitaire, et qu'en pensez-vous? Cela ne fonctionne pas, et non seulement ne fonctionne pas, mais

 >>> traverse({"status": "ok"}, print) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in traverse File "<stdin>", line 4, in traverse File "<stdin>", line 4, in traverse [Previous line repeated 989 more times] File "<stdin>", line 2, in traverse File "/usr/local/opt/python/libexec/bin/../../Frameworks/Python.framework/Versions/3.7/lib/python3.7/abc.py", line 139, in __instancecheck__ return _abc_instancecheck(cls, instance) RecursionError: maximum recursion depth exceeded in comparison 

Comment? Pourquoi? À la recherche d'une réponse, vous plongerez dans le monde merveilleux des collections d'une profondeur infinie.

En fait, une chaîne est le seul Iterable intégré qui renvoie toujours Iterable en tant qu'élément! Nous pouvons bien sûr construire un autre exemple en créant une liste et en l'ajoutant à nous-mêmes une ou deux fois, mais voyez-vous souvent cela dans votre code? Et la ligne est Iterable une profondeur infinie, se glissant sous le couvert de la nuit jusque dans votre production.

Un autre exemple. Quelque part dans le code, vous devez vérifier à plusieurs reprises la présence d'éléments dans des conteneurs. Vous décidez d'écrire un assistant qui l'accélère de plusieurs façons. Vous écrivez une solution universelle qui utilise uniquement la méthode __contains__ (la seule méthode de la classe de base abstraite du Container ), mais vous décidez ensuite d'ajouter une super-optimisation pour un cas spécial - une collection. Après tout, vous pouvez simplement marcher le long et faire un set !

 import functools from typing import Collection, Container def faster_container(c: Container) -> Container: if isinstance(c, Collection): return set(c) return CachedContainer(c) class CachedContainer(object): def __init__(self, c: Container): self._contains = functools.lru_cache()(c.__contains__) def __contains__(self, stuff): return self._contains(stuff) 

III ... votre solution ne fonctionne pas! Eh bien ici! Encore une fois!

 >>> c = faster_container(othello_text) >>> "Have you pray'd to-night, Desdemona?" in c False 

(Mais la mauvaise réponse a été émise très rapidement ...)

Pourquoi? Parce qu'une chaîne en Python est une collection étonnante dans laquelle la sémantique de la méthode __contains__ pas cohérente avec la sémantique de __iter__ et __len__ .

En fait, une chaîne est une collection:

 >>> from collections.abc import Collection >>> issubclass(str, Collection) True 

Mais la collection ... quoi? __iter__ et __len__ considèrent qu'il s'agit d'une collection de caractères:

 >>> s = "foo" >>> len(s) 3 >>> list(s) ['f', 'o', 'o'] 

Mais __contains__ pense que c'est une collection de sous-chaînes!

 >>> "oo" in s True >>> "oo" in list(s) False 

Que peut-on faire?


Bien que le comportement de str.__contains__ puisse sembler étrange dans le contexte des implémentations __contains__ autres types standard, ce comportement est l'une des nombreuses petites choses qui rendent Python aussi pratique qu'un langage de script; vous permettant d'écrire du code rapide et littéraire dessus. Je ne suggérerais pas de changer le comportement de cette méthode, d'autant plus que nous ne l'utilisons presque jamais pour vérifier la présence d'un seul caractère dans une chaîne.

Et au fait, savez-vous pourquoi? Parce que nous n'utilisons presque jamais une chaîne comme une collection de caractères dans un langage de script! Manipulation de caractères spécifiques dans une chaîne, accès par index - le plus souvent le destin des tâches dans les entretiens. Donc, vous devriez peut-être supprimer __iter__ de la chaîne, le cacher derrière une méthode comme .chars() ? Cela résoudrait ces deux problèmes.

Il est temps de discuter vendredi dans les commentaires!

Source: https://habr.com/ru/post/fr451252/


All Articles