Il est très probable que re.finditer
utilise une surcharge de mémoire assez minime.
def split_iter(string):
return (x.group(0) for x in re.finditer(r"[A-Za-z']+", string))
Démonstration :
>>> list( split_iter("A programmer's RegEx test.") )
['A', "programmer's", 'RegEx', 'test']
éditer : Je viens de confirmer que cela prend une mémoire constante dans python 3.2.1, en supposant que ma méthodologie de test était correcte. J'ai créé une chaîne de caractères de très grande taille (1GB ou plus), puis j'ai itéré dans l'itérable avec une fonction for
(PAS une compréhension de liste, qui aurait généré de la mémoire supplémentaire). Cela n'a pas entraîné de croissance notable de la mémoire (c'est-à-dire que s'il y a eu une croissance de la mémoire, elle était bien inférieure à la chaîne de 1 Go).
Version plus générale :
En réponse à un commentaire "Je ne vois pas le rapport avec str.split
", voici une version plus générale :
def splitStr(string, sep="\s+"):
# warning: does not yet work if sep is a lookahead like `(?=b)`
if sep=='':
return (c for c in string)
else:
return (_.group(1) for _ in re.finditer(f'(?:^|{sep})((?:(?!{sep}).)*)', string))
# alternatively, more verbosely:
regex = f'(?:^|{sep})((?:(?!{sep}).)*)'
for match in re.finditer(regex, string):
fragment = match.group(1)
yield fragment
L'idée est que ((?!pat).)*
annule un groupe en s'assurant qu'il correspond de manière avide jusqu'à ce que le motif commence à correspondre (les lookaheads ne consomment pas la chaîne dans la machine à état fini de la regex). En pseudocode : consommer de manière répétée ( begin-of-string
xor {sep}
) + as much as possible until we would be able to begin again (or hit end of string)
Démonstration :
>>> splitStr('.......A...b...c....', sep='...')
<generator object splitStr.<locals>.<genexpr> at 0x7fe8530fb5e8>
>>> list(splitStr('A,b,c.', sep=','))
['A', 'b', 'c.']
>>> list(splitStr(',,A,b,c.,', sep=','))
['', '', 'A', 'b', 'c.', '']
>>> list(splitStr('.......A...b...c....', '\.\.\.'))
['', '', '.A', 'b', 'c', '.']
>>> list(splitStr(' A b c. '))
['', 'A', 'b', 'c.', '']
(Il convient de noter que str.split a un comportement déplaisant : il s'agit d'un cas spécial où le fait d'avoir sep=None
comme le fait d'abord str.strip
pour supprimer les espaces en tête et en queue. La méthode ci-dessus ne le fait pas délibérément ; voir le dernier exemple où sep= "\s+"
.)
(J'ai rencontré plusieurs bogues (y compris une erreur interne re.error) en essayant d'implémenter ceci...). Le lookbehind négatif vous limitera aux délimiteurs de longueur fixe, donc nous ne l'utilisons pas. Presque tout ce qui est autre que la regex ci-dessus semble entraîner des erreurs avec les cas limites de début de chaîne et de fin de chaîne (par ex. r'(.*?)($|,)'
sur ',,,a,,b,c'
renvoie à ['', '', '', 'a', '', 'b', 'c', '']
avec une chaîne vide étrangère à la fin ; on peut consulter l'historique des modifications pour trouver une autre regex apparemment correcte qui présente en fait de subtils bogues).
(Si vous voulez l'implémenter vous-même pour obtenir de meilleures performances (bien qu'elles soient lourdes, les regex s'exécutent principalement en C), vous écrirez du code (avec ctypes ? je ne sais pas comment faire fonctionner les générateurs avec ?), avec le pseudocode suivant pour les délimiteurs de longueur fixe : Hachez votre délimiteur de longueur L. Gardez un hachage courant de longueur L pendant que vous parcourez la chaîne de caractères en utilisant un algorithme de hachage courant, temps de mise à jour O(1). Chaque fois que le hachage pourrait être égal à votre délimiteur, vérifiez manuellement si les derniers caractères étaient le délimiteur ; si c'est le cas, donnez la sous-chaîne depuis le dernier rendement. Cas particulier pour le début et la fin de la chaîne. Ce serait une version génératrice de l'algorithme du manuel pour faire une recherche de texte O(N). Des versions multiprocesseurs sont également possibles. Elles peuvent sembler exagérées, mais la question implique que l'on travaille avec des chaînes de caractères vraiment énormes... À ce moment-là, on peut envisager des choses folles comme la mise en cache des offsets d'octets s'il y en a peu, ou travailler à partir du disque avec un objet de visualisation d'octets sauvegardé sur le disque, acheter plus de RAM, etc. etc.)
5 votes
Cette question pourrait être lié.
1 votes
La raison en est qu'il est très difficile de penser à un cas où cela serait utile. Pourquoi voulez-vous cela ?
12 votes
@Glenn : Récemment, j'ai vu une question sur la division d'une longue chaîne de caractères en morceaux de n mots. Une des solutions
split
la chaîne de caractères et renvoie ensuite un générateur fonctionnant sur le résultat desplit
. Cela m'a fait penser qu'il y avait un moyen desplit
pour retourner un générateur pour commencer.6 votes
Il y a une discussion pertinente sur le Python Issue tracker : bugs.python.org/issue17343
2 votes
@GlennMaynard cela peut être utile pour l'analyse syntaxique de chaînes de caractères ou de fichiers nus, mais n'importe qui peut écrire un générateur d'analyse syntaxique très facilement en utilisant des DFA et des rendements créés par lui-même.