Autre que le fait qu'en 2.6 re.subne prendra pas d'argument de drapeaux ...
new123456
58
Je suis juste tombé sur un cas où l'utilisation a re.compiledonné une amélioration de 10 à 50 fois. La morale est que si vous avez beaucoup de regex (plus de MAXCACHE = 100) et que vous les utilisez beaucoup de fois chacune (et séparées par plus de regex de MAXCACHE entre les deux, de sorte que chacun soit vidé du cache: donc en utilisant le même nombre de fois, puis passer au suivant ne compte pas), alors cela aiderait certainement à les compiler. Sinon, cela ne fait aucune différence.
ShreevatsaR
8
Une petite chose à noter est que pour les chaînes qui n'ont pas besoin d'expressions régulières, le intest de sous-chaîne de chaîne est BEAUCOUP plus rapide:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop>python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Gamrix
@ShreevatsaR Intéressant! Pouvez-vous poster une réponse avec un exemple qui montre une amélioration de 10x-50x? La plupart des réponses données ici montrent en fait une amélioration de 3 fois dans certains cas précis, et dans d'autres cas presque aucune amélioration.
Basj
1
@Basj Done, a publié une réponse . Je n'ai pas pris la peine de trouver ce que j'utilisais Python en décembre 2013, mais la première chose simple que j'ai essayée montre le même comportement.
ShreevatsaR
Réponses:
436
J'ai beaucoup d'expérience en exécutant une expression régulière compilée 1000 fois par rapport à la compilation à la volée, et je n'ai remarqué aucune différence perceptible. Évidemment, c'est anecdotique, et certainement pas un bon argument contre la compilation, mais j'ai trouvé la différence négligeable.
EDIT: Après un rapide coup d'œil au code de la bibliothèque Python 2.5, je vois que Python compile et met en cache les expressions rationnelles chaque fois que vous les utilisez de toute façon (y compris les appels à re.match()), donc vous ne changez vraiment QUAND le regex est compilé, et ne devrait pas t gagner beaucoup de temps - seulement le temps qu'il faut pour vérifier le cache (une recherche de clé sur un dicttype interne ).
Du module re.py (les commentaires sont les miens):
def match(pattern, string, flags=0):return _compile(pattern, flags).match(string)def _compile(*key):# Does cache check at top of function
cachekey =(type(key[0]),)+ key
p = _cache.get(cachekey)if p isnotNone:return p# ...# Does actual compilation on cache miss# ...# Caches compiled regexif len(_cache)>= _MAXCACHE:
_cache.clear()
_cache[cachekey]= preturn p
Je précompile encore souvent les expressions régulières, mais uniquement pour les lier à un joli nom réutilisable, pas pour un gain de performance attendu.
Votre conclusion est incompatible avec votre réponse. Si les expressions régulières sont compilées et stockées automatiquement, il n'est pas nécessaire dans la plupart des cas de le faire à la main.
JFS
84
JF Sebastian, cela sert de signal au programmeur que l'expression rationnelle en question sera beaucoup utilisée et n'est pas destinée à être jetée.
kaleissin
40
Plus que cela, je dirais que si vous ne voulez pas subir le coup de compilation et de cache sur une partie critique de la performance de votre application, il est préférable de les compiler avant de remettre une partie non critique de votre application .
Eddie Parker
20
Je vois le principal avantage de l'utilisation d'expressions régulières compilées si vous réutilisez plusieurs fois la même expression régulière, réduisant ainsi la possibilité de fautes de frappe. Si vous venez de l'appeler une fois, alors non compilé est plus lisible.
Monkut
18
Donc, la principale différence sera lorsque vous utiliserez beaucoup d'expressions différentes (plus que _MAXCACHE), certaines une seule fois et d'autres beaucoup de fois ... alors il est important de conserver vos expressions compilées pour celles qui sont utilisées plus afin qu'elles ne sont pas vidés du cache lorsqu'il est plein.
fortran
133
Pour moi, le plus grand avantage re.compileest de pouvoir séparer la définition de l'expression régulière de son utilisation.
Même une simple expression telle que 0|[1-9][0-9]*(entier en base 10 sans zéros non significatifs) peut être suffisamment complexe pour que vous préfériez ne pas avoir à la retaper, vérifier si vous avez fait des fautes de frappe, puis vérifier à nouveau s'il y a des fautes de frappe lorsque vous commencez le débogage . De plus, il est plus agréable d'utiliser un nom de variable tel que num ou num_b10 que 0|[1-9][0-9]*.
Il est certainement possible de stocker des chaînes et de les transmettre à re.match; cependant, c'est moins lisible:
num ="..."# then, much later:
m = re.match(num, input)
Vers la compilation:
num = re.compile("...")# then, much later:
m = num.match(input)
Bien qu'elle soit assez proche, la dernière ligne de la seconde semble plus naturelle et plus simple lorsqu'elle est utilisée à plusieurs reprises.
Je suis d'accord avec cette réponse; Souvent, l'utilisation de re.compile génère un code plus lisible et non moins lisible.
Carl Meyer
1
Parfois, l'inverse est vrai, par exemple - si vous définissez l'expression régulière à un endroit et utilisez ses groupes correspondants dans un autre endroit éloigné.
Ken Williams
1
@KenWilliams Pas nécessairement, une expression rationnelle bien nommée dans un but spécifique doit être claire même lorsqu'elle est utilisée loin de la définition d'origine. Par exemple us_phone_numberou social_security_numberetc.
Brian M. Sheldon
2
@ BrianM.Sheldon pour bien nommer l'expression régulière ne vous aide pas vraiment à savoir ce que représentent ses différents groupes de capture.
Ken Williams
69
FWIW:
$ python -m timeit -s "import re""re.match('hello', 'hello world')"100000 loops, best of 3:3.82 usec per loop
$ python -m timeit -s "import re; h=re.compile('hello')""h.match('hello world')"1000000 loops, best of 3:1.26 usec per loop
donc, si vous utilisez beaucoup la même expression régulière, cela en vaut peut-être la peine re.compile(en particulier pour les expressions régulières plus complexes).
Les arguments standard contre l'optimisation prématurée s'appliquent, mais je ne pense pas que vous perdiez vraiment beaucoup de clarté / re.compilesimplicité en utilisant si vous pensez que vos regexps peuvent devenir un goulot d'étranglement des performances.
Mise à jour:
Sous Python 3.6 (je soupçonne que les synchronisations ci-dessus ont été effectuées en utilisant Python 2.x) et le matériel 2018 (MacBook Pro), j'obtiens maintenant les synchronisations suivantes:
% python -m timeit -s "import re""re.match('hello', 'hello world')"1000000 loops, best of 3:0.661 usec per loop
% python -m timeit -s "import re; h=re.compile('hello')""h.match('hello world')"1000000 loops, best of 3:0.285 usec per loop
% python -m timeit -s "import re""h=re.compile('hello'); h.match('hello world')"1000000 loops, best of 3:0.65 usec per loop
% python --version
Python3.6.5::Anaconda,Inc.
J'ai également ajouté un cas (notez les différences entre guillemets entre les deux dernières exécutions) qui montre qu'il re.match(x, ...)est littéralement [à peu près] équivalent re.compile(x).match(...), c'est-à-dire qu'aucune mise en cache en arrière-plan de la représentation compilée ne semble se produire.
Problèmes majeurs avec votre méthodologie ici, car l'argument de configuration N'INCLUT PAS dans le timing. Ainsi, vous avez supprimé le temps de compilation du deuxième exemple et vous venez de le calculer en moyenne dans le premier exemple. Cela ne signifie pas que le premier exemple se compile à chaque fois.
Triptyque
1
Oui, je suis d'accord que ce n'est pas une comparaison équitable des deux cas.
Kiv
7
Je vois ce que vous voulez dire, mais n'est-ce pas exactement ce qui se passerait dans une application réelle où l'expression régulière est utilisée plusieurs fois?
dF.
26
@Triptych, @Kiv: L'intérêt de la compilation d'expressions rationnelles distinctes de l'utilisation est de minimiser la compilation; le supprimer de la synchronisation est exactement ce que dF aurait dû faire, car il représente l'utilisation la plus précise du monde réel. Le temps de compilation est particulièrement hors de propos avec la façon dont timeit.py fait ses timings ici; il effectue plusieurs exécutions et ne signale que la plus courte, à quel point l'expression rationnelle compilée est mise en cache. Le coût supplémentaire que vous voyez ici n'est pas le coût de la compilation de l'expression rationnelle, mais le coût de la recherche dans le cache d'expression rationnelle compilé (un dictionnaire).
jemfinch
3
@Triptych Faut- import reil retirer la configuration? Tout dépend de l'endroit où vous voulez mesurer. Si j'exécute un script python plusieurs fois, le import retemps sera atteint. Lors de la comparaison des deux, il est important de séparer les deux lignes pour le timing. Oui, comme vous dites, c'est quand vous aurez le temps de frapper. La comparaison montre que soit vous prenez le coup de temps une fois et répétez le coup de temps moindre en compilant, soit vous prenez le coup à chaque fois en supposant que le cache est vidé entre les appels, ce qui, comme cela a été souligné, pourrait arriver. L'ajout d'un calendrier h=re.compile('hello')aiderait à clarifier.
Tom Myddeltyn
39
Voici un cas de test simple:
~$ for x in1101001000100001000001000000;do python -m timeit -n $x -s 'import re''re.match("[0-9]{3}-[0-9]{3}-[0-9]{4}", "123-123-1234")'; done
1 loops, best of 3:3.1 usec per loop
10 loops, best of 3:2.41 usec per loop
100 loops, best of 3:2.24 usec per loop
1000 loops, best of 3:2.21 usec per loop
10000 loops, best of 3:2.23 usec per loop
100000 loops, best of 3:2.24 usec per loop
1000000 loops, best of 3:2.31 usec per loop
avec re.compile:
~$ for x in1101001000100001000001000000;do python -m timeit -n $x -s 'import re''r = re.compile("[0-9]{3}-[0-9]{3}-[0-9]{4}")''r.match("123-123-1234")'; done
1 loops, best of 3:1.91 usec per loop
10 loops, best of 3:0.691 usec per loop
100 loops, best of 3:0.701 usec per loop
1000 loops, best of 3:0.684 usec per loop
10000 loops, best of 3:0.682 usec per loop
100000 loops, best of 3:0.694 usec per loop
1000000 loops, best of 3:0.702 usec per loop
Il semblerait donc que la compilation soit plus rapide avec ce cas simple, même si vous ne correspondez qu'une seule fois .
cela n'a pas vraiment d'importance, l'essentiel est d'essayer la référence dans l'environnement où vous exécuterez le code
david king
1
Pour moi, les performances sont presque exactement les mêmes pour 1000 boucles ou plus. La version compilée est plus rapide pour 1 à 100 boucles. (Sur les deux pythons 2.7 et 3.4).
Zitrax
2
Sur ma configuration Python 2.7.3, il n'y a pratiquement aucune différence. Parfois, la compilation est plus rapide, parfois c'est plus lent. La différence est toujours <5%, donc je compte la différence comme mesure de l'incertitude, car l'appareil n'a qu'un seul processeur.
Dakkaron
1
En Python 3.4.3 vu dans deux exécutions distinctes: l'utilisation de compilé était encore plus lente que non compilée.
Zelphir Kaltstahl
17
Je viens de l'essayer moi-même. Pour le cas simple de l'analyse d'un nombre d'une chaîne et de sa sommation, l'utilisation d'un objet d'expression régulière compilé est environ deux fois plus rapide que l'utilisation des reméthodes.
Comme d'autres l'ont souligné, les reméthodes (y compris re.compile) recherchent la chaîne d'expression régulière dans un cache d'expressions précédemment compilées. Par conséquent, dans le cas normal, le coût supplémentaire de l'utilisation des reméthodes est simplement le coût de la recherche de cache.
Cependant, l'examen du code montre que le cache est limité à 100 expressions. Cela soulève la question, à quel point est-il douloureux de déborder le cache? Le code contient une interface interne au compilateur d'expression régulière, re.sre_compile.compile. Si nous l'appelons, nous contournons le cache. Il s'avère être environ deux ordres de grandeur plus lent pour une expression régulière de base, telle que r'\w+\s+([0-9_]+)\s+\w*'.
Voici mon test:
#!/usr/bin/env pythonimport re
import time
def timed(func):def wrapper(*args):
t = time.time()
result = func(*args)
t = time.time()- t
print'%s took %.3f seconds.'%(func.func_name, t)return result
return wrapper
regularExpression = r'\w+\s+([0-9_]+)\s+\w*'
testString ="average 2 never"@timeddef noncompiled():
a =0for x in xrange(1000000):
m = re.match(regularExpression, testString)
a += int(m.group(1))return a
@timeddef compiled():
a =0
rgx = re.compile(regularExpression)for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef reallyCompiled():
a =0
rgx = re.sre_compile.compile(regularExpression)for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef compiledInLoop():
a =0for x in xrange(1000000):
rgx = re.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef reallyCompiledInLoop():
a =0for x in xrange(10000):
rgx = re.sre_compile.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))return a
r1 = noncompiled()
r2 = compiled()
r3 = reallyCompiled()
r4 = compiledInLoop()
r5 = reallyCompiledInLoop()print"r1 = ", r1
print"r2 = ", r2
print"r3 = ", r3
print"r4 = ", r4
print"r5 = ", r5
</pre>And here is the output on my machine:<pre>
$ regexTest.py
noncompiled took 4.555 seconds.
compiled took 2.323 seconds.
reallyCompiled took 2.325 seconds.
compiledInLoop took 4.620 seconds.
reallyCompiledInLoop took 4.074 seconds.
r1 =2000000
r2 =2000000
r3 =2000000
r4 =2000000
r5 =20000
Les méthodes 'reallyCompiled' utilisent l'interface interne, qui contourne le cache. Notez que celui qui compile à chaque itération de boucle n'est itéré que 10 000 fois, pas un million.
Je suis d'accord avec vous que les expressions régulières compilées s'exécutent beaucoup plus rapidement que les expressions non compilées. J'ai exécuté plus de 10000 phrases et y ai fait une boucle pour itérer les expressions régulières lorsque les expressions régulières n'étaient pas compilées et ont été calculées chaque fois que la prédiction d'une exécution complète était de 8 heures, après avoir créé un dictionnaire selon l'index avec les modèles d'expression régulière compilés que j'exécute le tout pendant 2 minutes. Je ne comprends pas les réponses ci-dessus ...
Eli Borodach
12
Je suis d'accord avec l'honnête Abe que les match(...)exemples donnés sont différents. Ce ne sont pas des comparaisons individuelles et, par conséquent, les résultats varient. Pour simplifier ma réponse, j'utilise A, B, C, D pour les fonctions en question. Oh oui, nous avons affaire à 4 fonctions au re.pylieu de 3.
Exécuter ce morceau de code:
h = re.compile('hello')# (A)
h.match('hello world')# (B)
revient à exécuter ce code:
re.match('hello','hello world')# (C)
Parce que, quand on regarde dans la source re.py, (A + B) signifie:
h = re._compile('hello')# (D)
h.match('hello world')
et (C) est en fait:
re._compile('hello').match('hello world')
Donc, (C) n'est pas la même chose que (B). En fait, (C) appelle (B) après avoir appelé (D) qui est également appelé par (A). En d' autres termes, (C) = (A) + (B). Par conséquent, comparer (A + B) à l'intérieur d'une boucle a le même résultat que (C) à l'intérieur d'une boucle.
George's nous l'a regexTest.pyprouvé.
noncompiled took 4.555 seconds.# (C) in a loop
compiledInLoop took 4.620 seconds.# (A + B) in a loop
compiled took 2.323 seconds.# (A) once + (B) in a loop
L'intérêt de tous est de savoir comment obtenir le résultat de 2,323 secondes. Afin de s'assurer de compile(...)n'être appelé qu'une seule fois, nous devons stocker l'objet regex compilé en mémoire. Si nous utilisons une classe, nous pourrions stocker l'objet et le réutiliser à chaque fois que notre fonction est appelée.
Si nous n'utilisons pas de classe (ce qui est ma demande aujourd'hui), alors je n'ai aucun commentaire. J'apprends toujours à utiliser la variable globale en Python, et je sais que la variable globale est une mauvaise chose.
Un dernier point, je pense que l'utilisation de l' (A) + (B)approche a un avantage . Voici quelques faits que j'ai observés (veuillez me corriger si je me trompe):
Appelle A une fois, il fera une recherche dans le _cachesuivi d'un sre_compile.compile()pour créer un objet regex. Appelle A deux fois, il fera deux recherches et une compilation (car l'objet regex est mis en cache).
Si l' _cacheobjet est vidé entre les deux, l'objet regex est libéré de la mémoire et Python doit à nouveau être compilé. (quelqu'un suggère que Python ne se recompilera pas.)
Si nous gardons l'objet regex en utilisant (A), l'objet regex va toujours entrer dans _cache et être vidé d'une manière ou d'une autre. Mais notre code garde une référence dessus et l'objet regex ne sera pas libéré de la mémoire. Ceux-ci, Python n'a pas besoin de compiler à nouveau.
Les différences de 2 secondes dans le test de George compiledInLoop vs compiled sont principalement le temps requis pour construire la clé et rechercher le _cache. Cela ne signifie pas le temps de compilation de l'expression régulière.
Le test vraiment de compilation de George montre ce qui se passe s'il refait vraiment la compilation à chaque fois: ce sera 100 fois plus lent (il a réduit la boucle de 1 000 000 à 10 000).
Voici les seuls cas où (A + B) est meilleur que (C):
Si nous pouvons mettre en cache une référence de l'objet regex à l'intérieur d'une classe.
Si nous avons besoin d'appeler (B) à plusieurs reprises (à l'intérieur d'une boucle ou plusieurs fois), nous devons mettre en cache la référence à l'objet regex en dehors de la boucle.
Cas où (C) est assez bon:
Nous ne pouvons pas mettre en cache une référence.
Nous ne l'utilisons que de temps en temps.
Dans l'ensemble, nous n'avons pas trop de regex (supposons que celui compilé ne soit jamais vidé)
Juste un récapitulatif, voici l'ABC:
h = re.compile('hello')# (A)
h.match('hello world')# (B)
re.match('hello','hello world')# (C)
Généralement, il y a peu de différence que vous utilisiez re.compile ou non. En interne, toutes les fonctions sont implémentées en termes d'étape de compilation:
De plus, re.compile () contourne la logique supplémentaire d'indirection et de mise en cache:
_cache ={}
_pattern_type = type(sre_compile.compile("",0))
_MAXCACHE =512def _compile(pattern, flags):# internal: compile patterntry:
p, loc = _cache[type(pattern), pattern, flags]if loc isNoneor loc == _locale.setlocale(_locale.LC_CTYPE):return p
exceptKeyError:passif isinstance(pattern, _pattern_type):if flags:raiseValueError("cannot process flags argument with a compiled pattern")return pattern
ifnot sre_compile.isstring(pattern):raiseTypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)ifnot(flags & DEBUG):if len(_cache)>= _MAXCACHE:
_cache.clear()if p.flags & LOCALE:ifnot _locale:return p
loc = _locale.setlocale(_locale.LC_CTYPE)else:
loc =None
_cache[type(pattern), pattern, flags]= p, loc
return p
En plus du faible avantage de vitesse de l'utilisation de re.compile , les utilisateurs apprécient également la lisibilité qui résulte de la dénomination des spécifications de modèle potentiellement complexes et de leur séparation de la logique métier où elles sont appliquées:
Notez qu'un autre répondant croyait à tort que les fichiers pyc stockaient directement les modèles compilés; cependant, en réalité, ils sont reconstruits à chaque fois que le PYC est chargé:
est le "dans def search(pattern, string, flags=0):"une faute de frappe?
phuclv
1
Notez que si patternest déjà un modèle compilé, la surcharge de mise en cache devient importante: le hachage de a SRE_Patternest coûteux et le modèle n'est jamais écrit dans le cache, donc la recherche échoue à chaque fois avec a KeyError.
Eric Duminil
5
En général, je trouve qu'il est plus facile d'utiliser des indicateurs (du moins plus faciles à retenir), comme re.Ilors de la compilation de modèles, que d'utiliser des indicateurs en ligne.
Le deuxième paramètre facultatif pos donne un index dans la chaîne où la recherche doit commencer; sa valeur par défaut est 0. Ce n'est pas complètement équivalent à trancher la chaîne; le '^'caractère du motif correspond au début réel de la chaîne et aux positions juste après une nouvelle ligne, mais pas nécessairement à l'index où la recherche doit commencer.
endpos
Le paramètre optionnel endpos limite l' étendue de la recherche dans la chaîne; ce sera comme si la chaîne était longue de caractères de fin, donc seuls les caractères de pos à endpos - 1seront recherchés pour une correspondance. Si endpos est inférieur à pos , aucune correspondance ne sera trouvée; sinon, si rx est un objet d'expression régulière compilé, rx.search(string, 0,
50)est équivalent à rx.search(string[:50], 0).
Les méthodes search , findall et finditer de l' objet regex prennent également en charge ces paramètres.
re.match(pattern, string, flags=0)ne les prend pas en charge comme vous pouvez le voir,
pas plus que ses homologues de recherche , findall et finditer .
Un objet match possède des attributs qui complètent ces paramètres:
match.pos
La valeur de pos qui a été transmise à la méthode search () ou match () d'un objet regex. Il s'agit de l'index de la chaîne à laquelle le moteur RE a commencé à rechercher une correspondance.
match.endpos
La valeur de endpos qui a été transmise à la méthode search () ou match () d'un objet regex. Il s'agit de l'index de la chaîne au-delà duquel le moteur RE n'ira pas.
Un objet regex possède deux attributs uniques, éventuellement utiles:
regex.groups
Le nombre de groupes de capture dans le modèle.
regex.groupindex
Un dictionnaire mappant tout nom de groupe symbolique défini par (? P) aux numéros de groupe. Le dictionnaire est vide si aucun groupe symbolique n'a été utilisé dans le modèle.
Mis à part la différence de performances, l'utilisation de re.compile et l'utilisation de l'objet d'expression régulière compilé pour effectuer la correspondance (quelles que soient les opérations liées aux expressions régulières) rendent la sémantique plus claire pour l'exécution Python.
J'ai eu une expérience douloureuse de débogage de code simple:
compare =lambda s, p: re.match(p, s)
et plus tard j'utiliserais comparer dans
[x for x in data if compare(patternPhrases, x[columnIndex])]
où patternPhrasesest censé être une variable contenant une chaîne d'expression régulière, x[columnIndex]est une variable contenant une chaîne.
J'ai eu des problèmes qui patternPhrasesne correspondaient pas à une chaîne attendue!
Mais si j'ai utilisé le formulaire re.compile:
compare =lambda s, p: p.match(s)
puis dans
[x for x in data if compare(patternPhrases, x[columnIndex])]
Python se serait plaint que "la chaîne n'a pas d'attribut de correspondance", comme par mappage d'arguments positionnels dans compare, x[columnIndex]est utilisé comme expression régulière !, quand je voulais dire
compare =lambda p, s: p.match(s)
Dans mon cas, l'utilisation de re.compile est plus explicite du but de l'expression régulière, lorsque sa valeur est cachée aux yeux nus, donc je pourrais obtenir plus d'aide de la vérification au moment de l'exécution de Python.
Donc, la morale de ma leçon est que lorsque l'expression régulière n'est pas seulement une chaîne littérale, je dois utiliser re.compile pour laisser Python m'aider à affirmer mon hypothèse.
Il y a un avantage supplémentaire à utiliser re.compile (), sous la forme d'ajouter des commentaires à mes modèles d'expression régulière à l'aide de re.VERBOSE
pattern ='''
hello[ ]world # Some info on my pattern logic. [ ] to recognize space
'''
re.search(pattern,'hello world', re.VERBOSE)
Bien que cela n'affecte pas la vitesse d'exécution de votre code, j'aime le faire de cette façon car cela fait partie de mon habitude de commenter. Je n'aime pas du tout passer du temps à essayer de me souvenir de la logique qui est derrière mon code 2 mois plus tard lorsque je veux apporter des modifications.
J'ai édité votre réponse. Je pense que la mention re.VERBOSEvaut la peine, et cela ajoute quelque chose que les autres réponses semblent avoir omises. Cependant, le fait de mener votre réponse par "Je poste ici parce que je ne peux pas encore commenter" est sûr de la supprimer. Veuillez ne pas utiliser la boîte de réponses pour autre chose que des réponses. Vous n'êtes qu'à une ou deux bonnes réponses de pouvoir commenter n'importe où (50 répétitions), alors soyez patient. Mettre des commentaires dans des boîtes de réponse lorsque vous savez que vous ne devriez pas y arriver plus rapidement. Il vous obtiendra des downvotes et des réponses supprimées.
prog = re.compile(pattern)
result = prog.match(string)
est équivalent à
result = re.match(pattern, string)
mais l'utilisation re.compile()et l'enregistrement de l'objet d'expression régulière résultant pour une réutilisation est plus efficace lorsque l'expression sera utilisée plusieurs fois dans un seul programme.
Donc, ma conclusion est que si vous allez faire correspondre le même modèle pour de nombreux textes différents, vous feriez mieux de le précompiler.
Fait intéressant, la compilation s'avère plus efficace pour moi (Python 2.5.2 sur Win XP):
import re
import time
rgx = re.compile('(\w+)\s+[0-9_]?\s+\w*')
str ="average 2 never"
a =0
t = time.time()for i in xrange(1000000):if re.match('(\w+)\s+[0-9_]?\s+\w*', str):#~ if rgx.match(str):
a +=1print time.time()- t
Exécuter le code ci-dessus une fois tel ifquel , et une fois avec les deux lignes commentées dans l'autre sens, l'expression régulière compilée est deux fois plus rapide
Même problème qu'avec la comparaison des performances de dF. Ce n'est pas vraiment juste sauf si vous incluez le coût de performance de l'instruction de compilation elle-même.
Carl Meyer
6
Carl, je ne suis pas d'accord. La compilation n'est exécutée qu'une seule fois, tandis que la boucle de correspondance est exécutée un million de fois
Eli Bendersky
@eliben: Je suis d'accord avec Carl Meyer. La compilation a lieu dans les deux cas. Triptych mentionne que la mise en cache est impliquée, donc dans un cas optimal (reste dans le cache), les deux approches sont O (n + 1), bien que la partie +1 soit un peu cachée lorsque vous n'utilisez pas explicitement re.compile.
paprika
1
N'écrivez pas votre propre code d'analyse comparative. Apprenez à utiliser timeit.py, qui est inclus dans la distribution standard.
jemfinch
Pendant combien de temps recréez-vous la chaîne de motif dans la boucle for. Cette surcharge ne peut pas être triviale.
IceArdor
3
J'ai effectué ce test avant de tomber sur la discussion ici. Cependant, après l'avoir exécuté, j'ai pensé que je publierais au moins mes résultats.
J'ai volé et bâtardé l'exemple dans "Mastering Regular Expressions" de Jeff Friedl. C'est sur un macbook fonctionnant sous OSX 10.6 (2Ghz intel core 2 duo, 4GB ram). La version Python est 2.6.1.
Exécuter 1 - en utilisant re.compile
import re
import time
import fpformat
Regex1= re.compile('^(a|b|c|d|e|f|g)+$')Regex2= re.compile('^[a-g]+$')TimesToDo=1000TestString=""for i in range(1000):TestString+="abababdedfg"StartTime= time.time()for i in range(TimesToDo):Regex1.search(TestString)Seconds= time.time()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):Regex2.search(TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.299 seconds
CharacterClass takes 0.107 seconds
Exécuter 2 - Ne pas utiliser re.compile
import re
import time
import fpformat
TimesToDo=1000TestString=""for i in range(1000):TestString+="abababdedfg"StartTime= time.time()for i in range(TimesToDo):
re.search('^(a|b|c|d|e|f|g)+$',TestString)Seconds= time.time()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):
re.search('^[a-g]+$',TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.508 seconds
CharacterClass takes 0.109 seconds
Cette réponse pourrait arriver en retard, mais c'est une découverte intéressante. L'utilisation de la compilation peut vraiment vous faire gagner du temps si vous prévoyez d'utiliser plusieurs fois l'expression régulière (cela est également mentionné dans la documentation). Ci-dessous, vous pouvez voir que l'utilisation d'une expression régulière compilée est la plus rapide lorsque la méthode de correspondance est directement appelée dessus. passer un regex compilé à re.match le rend encore plus lent et passer re.match avec la chaîne de patter est quelque part au milieu.
En complément, j'ai fait une feuille de triche exhaustive du module repour votre référence.
regex ={'brackets':{'single_character':['[]','.',{'negate':'^'}],'capturing_group':['()','(?:)','(?!)''|','\\','backreferences and named group'],'repetition':['{}','*?','+?','??','greedy v.s. lazy ?']},'lookaround':{'lookahead':['(?=...)','(?!...)'],'lookbehind':['(?<=...)','(?<!...)'],'caputuring':['(?P<name>...)','(?P=name)','(?:)'],},'escapes':{'anchor':['^','\b','$'],'non_printable':['\n','\t','\r','\f','\v'],'shorthand':['\d','\w','\s']},'methods':{['search','match','findall','finditer'],['split','sub']},'match_object':['group','groups','groupdict','start','end','span',]}
Je respecte vraiment toutes les réponses ci-dessus. De mon avis Oui! Pour sûr, il vaut la peine d'utiliser re.compile au lieu de compiler le regex, encore et encore, à chaque fois.
L'utilisation de re.compile rend votre code plus dynamique, car vous pouvez appeler l'expression régulière déjà compilée, au lieu de compiler à nouveau et de nouveau. Cette chose vous profite dans les cas:
Efforts du processeur
Complexité temporelle.
Rend le regex universel. (Peut être utilisé dans findall, search, match)
Et rend votre programme cool.
Exemple :
example_string ="The room number of her room is 26A7B."
find_alpha_numeric_string = re.compile(r"\b\w+\b")
Utilisation dans Findall
find_alpha_numeric_string.findall(example_string)
Utilisation dans la recherche
find_alpha_numeric_string.search(example_string)
De même, vous pouvez l'utiliser pour: Match and Substitute
C'est une bonne question. Vous voyez souvent des gens utiliser re.compile sans raison. Cela diminue la lisibilité. Mais bien sûr, il est souvent nécessaire de précompiler l'expression. Comme lorsque vous l'utilisez plusieurs fois dans une boucle ou quelque chose du genre.
C'est comme tout sur la programmation (tout dans la vie en fait). Faites preuve de bon sens.
Pour autant que je puisse en juger par mon bref survol, Python en bref ne mentionne pas l'utilisation sans re.compile (), ce qui m'a rendu curieux.
Mat
L'objet regex ajoute un objet supplémentaire au contexte. Comme je l'ai dit, il existe de nombreuses situations où re.compile () a sa place. L'exemple donné par l'OP n'en fait pas partie.
PEZ
1
(des mois plus tard), il est facile d'ajouter votre propre cache autour de re.match, ou toute autre chose d'ailleurs -
J'ai beaucoup d'expérience en exécutant une expression régulière compilée 1000 fois par rapport à la compilation à la volée, et je n'ai remarqué aucune différence perceptible
Les votes sur la réponse acceptée conduisent à supposer que ce que dit @Triptych est vrai pour tous les cas. Ce n'est pas nécessairement vrai. Une grande différence est quand vous devez décider d'accepter une chaîne d'expression régulière ou un objet d'expression régulière compilé en tant que paramètre d'une fonction:
>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: x.match(y) # accepts compiled regex as parameter
... h=re.compile('hello')
... """, stmt="f(h, 'hello world')")0.32881879806518555>>> timeit.timeit(setup="""
... import re
... f=lambda x, y: re.compile(x).match(y) # compiles when called
... """, stmt="f('hello', 'hello world')")0.809190034866333
Il est toujours préférable de compiler vos expressions régulières au cas où vous auriez besoin de les réutiliser.
Notez que l'exemple dans le temps ci-dessus simule la création d'un objet regex compilé une fois au moment de l'importation par rapport à "à la volée" lorsque cela est requis pour une correspondance.
Comme réponse alternative, comme je vois que cela n'a pas été mentionné auparavant, je vais continuer et citer les documents Python 3 :
Devriez-vous utiliser ces fonctions au niveau du module, ou devriez-vous obtenir le modèle et appeler ses méthodes vous-même? Si vous accédez à une expression régulière dans une boucle, la précompilation économisera quelques appels de fonction. En dehors des boucles, il n'y a pas beaucoup de différence grâce au cache interne.
Voici un exemple où l'utilisation re.compileest plus de 50 fois plus rapide, comme demandé .
Le point est le même que ce que j'ai fait dans le commentaire ci-dessus, à savoir, l'utilisation re.compilepeut être un avantage significatif lorsque votre utilisation est telle qu'elle ne bénéficie pas beaucoup du cache de compilation. Cela se produit au moins dans un cas particulier (que j'ai rencontré en pratique), à savoir lorsque toutes les conditions suivantes sont vraies:
Vous avez beaucoup de modèles d'expression régulière (plus que re._MAXCACHE, dont la valeur par défaut est actuellement 512), et
vous utilisez ces expressions régulières beaucoup de fois, et
vous les utilisations consécutives du même modèle sont séparés par plus que d' re._MAXCACHEautres regex entre les deux, de sorte que chacun est vidé du cache entre les utilisations consécutives.
import re
import time
def setup(N=1000):# Patterns 'a.*a', 'a.*b', ..., 'z.*z'
patterns =[chr(i)+'.*'+ chr(j)for i in range(ord('a'), ord('z')+1)for j in range(ord('a'), ord('z')+1)]# If this assertion below fails, just add more (distinct) patterns.# assert(re._MAXCACHE < len(patterns))# N strings. Increase N for larger effect.
strings =['abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz']* N
return(patterns, strings)def without_compile():print('Without re.compile:')
patterns, strings = setup()print('searching')
count =0for s in strings:for pat in patterns:
count += bool(re.search(pat, s))return count
def without_compile_cache_friendly():print('Without re.compile, cache-friendly order:')
patterns, strings = setup()print('searching')
count =0for pat in patterns:for s in strings:
count += bool(re.search(pat, s))return count
def with_compile():print('With re.compile:')
patterns, strings = setup()print('compiling')
compiled =[re.compile(pattern)for pattern in patterns]print('searching')
count =0for s in strings:for regex in compiled:
count += bool(regex.search(s))return count
start = time.time()print(with_compile())
d1 = time.time()- start
print(f'-- That took {d1:.2f} seconds.\n')
start = time.time()print(without_compile_cache_friendly())
d2 = time.time()- start
print(f'-- That took {d2:.2f} seconds.\n')
start = time.time()print(without_compile())
d3 = time.time()- start
print(f'-- That took {d3:.2f} seconds.\n')print(f'Ratio: {d3/d1:.2f}')
Exemple de sortie que j'obtiens sur mon ordinateur portable (Python 3.7.7):
With re.compile:
compiling
searching
676000
-- That took 0.33 seconds.
Without re.compile, cache-friendly order:
searching
676000
-- That took 0.67 seconds.
Without re.compile:
searching
676000
-- That took 23.54 seconds.
Ratio: 70.89
Je n'ai pas pris la peine timeitcar la différence est si flagrante, mais j'obtiens des chiffres qualitativement similaires à chaque fois. Notez que même sans re.compile, utiliser plusieurs fois la même expression régulière et passer à la suivante n'était pas si mal (seulement environ 2 fois plus lent qu'avec re.compile), mais dans l'autre ordre (en passant par plusieurs expressions régulières), c'est bien pire , comme prévu. En outre, l' augmentation de la taille du cache fonctionne aussi: simple réglage re._MAXCACHE = len(patterns)en setup()haut (bien sûr , je ne recommande pas de faire des choses dans la production des noms avec des underscores sont classiquement « privée ») laisse tomber les ~ 23 secondes vers le bas à ~ 0,7 secondes, ce qui a également correspond à notre compréhension.
PS: si j'utilise seulement 3 modèles regex dans tout mon code, chacun d'eux étant utilisé (sans ordre spécifique) des centaines de fois, le cache regex gardera automatiquement le regex précompilé, n'est-ce pas?
Basj
@Basj Je pense que vous pouvez simplement l'essayer et voir :) Mais la réponse, j'en suis presque sûr, est oui: le seul coût supplémentaire dans ce cas, AFAICT est simplement celui de simplement rechercher le modèle dans le cache . Notez également que le cache est global (au niveau du module), donc en principe, vous pourriez avoir une bibliothèque de dépendances faisant des recherches d'expressions régulières entre les vôtres, il est donc difficile d'être entièrement sûr que votre programme n'utilise que 3 (ou n'importe quel nombre) d'expressions régulières modèles, mais ce serait assez bizarre d'être autrement :)
ShreevatsaR
0
Les expressions régulières sont compilées avant d'être utilisées lors de l'utilisation de la deuxième version. Si vous allez l'exécuter plusieurs fois, il est certainement préférable de le compiler en premier. Si vous ne compilez pas à chaque fois que vous correspondez à un seul, c'est bien.
Pour moi, le principal avantage est que je n'ai besoin que de me souvenir et de lire une forme de la syntaxe compliquée de l'API regex - la <compiled_pattern>.method(xxx)forme plutôt que cela et lare.func(<pattern>, xxx) forme.
le re.compile(<pattern>) un peu de passe-partout supplémentaire, c'est vrai.
Mais en ce qui concerne les regex, cette étape de compilation supplémentaire est peu susceptible d'être une grande cause de charge cognitive. Et en fait, sur des modèles compliqués, vous pouvez même gagner en clarté en séparant la déclaration de la méthode regex que vous invoquez ensuite.
J'ai tendance à régler d'abord les modèles compliqués dans un site Web comme Regex101, ou même dans un script de test minimal séparé, puis à les intégrer dans mon code, donc séparer la déclaration de son utilisation correspond également à mon flux de travail.
je voudrais motiver que la pré-compilation est à la fois conceptuellement et «littéralement» (comme dans la «programmation alphabétisée») avantageuse. jetez un oeil à cet extrait de code:
from re import compile as_Reclass TYPO:def text_has_foobar( self, text ):return self._text_has_foobar_re_search( text )isnotNone
_text_has_foobar_re_search =_Re( r"""(?i)foobar""").search
TYPO = TYPO()
dans votre candidature, vous écririez:
from TYPO import TYPO
print( TYPO.text_has_foobar('FOObar ) )
c'est à peu près aussi simple en termes de fonctionnalités que possible. parce que cet exemple est si court, j'ai confondu le chemin pour obtenir _text_has_foobar_re_searchtout en une seule ligne. l'inconvénient de ce code est qu'il occupe un peu de mémoire quelle que soit la durée de vie de l' TYPOobjet bibliothèque; l'avantage est que lorsque vous effectuez une recherche foobar, vous vous en sortirez avec deux appels de fonction et deux recherches de dictionnaire de classe. combien d'expressions régulières sont mises en cache parre et les frais généraux de ce cache ne sont pas pertinents ici.
comparer cela avec le style plus habituel, ci-dessous:
import re
classTypo:def text_has_foobar( self, text ):return re.compile( r"""(?i)foobar""").search( text )isnotNone
J'admets volontiers que mon style est très inhabituel pour le python, peut-être même discutable. cependant, dans l'exemple qui correspond le mieux à la façon dont python est principalement utilisé, pour effectuer une seule correspondance, nous devons instancier un objet, effectuer trois recherches de dictionnaire d'instance et effectuer trois appels de fonction; en outre, nous pouvons rencontrer des reproblèmes de mise en cache lors de l'utilisation de plus de 100 expressions régulières. aussi, l'expression régulière est cachée à l'intérieur du corps de la méthode, ce qui la plupart du temps n'est pas une si bonne idée.
qu'il soit dit que chaque sous-ensemble de mesures --- déclarations d'importation ciblées et aliasées; méthodes aliasées, le cas échéant; réduction des appels de fonction et des recherches de dictionnaire d'objets --- peut aider à réduire la complexité de calcul et de conception.
WTF. Non seulement vous avez déterré une vieille question à laquelle vous avez répondu. Votre code est également non idiomatique et erroné à de nombreux niveaux - (ab) en utilisant des classes comme espaces de noms où un module est suffisant, en capitalisant les noms de classe, etc ... Voir pastebin.com/iTAXAWen pour de meilleures implémentations. Sans parler de l'expression régulière que vous utilisez est également cassée. Global, -1
2
coupable. c'est une vieille question, mais cela ne me dérange pas d'être n ° 100 dans une conversation ralentie. la question n'est pas close. j'ai averti que mon code pouvait être un adversaire à certains goûts. je pense que si vous pouviez le voir comme une simple démonstration de ce qui est faisable en python, comme: si nous prenons tout, tout ce que nous croyons, comme facultatif, puis bricolons ensemble de quelle manière, à quoi ressemblent les choses que nous pouvons avoir? je suis sûr que vous pouvez discerner les avantages et les inconvénients de cette solution et vous plaindre de manière plus articulée. sinon, je dois conclure que votre allégation d'irrégularité ne repose que sur PEP008
flux
2
Non, il ne s'agit pas de PEP8. Ce ne sont que des conventions de dénomination, et je ne reverrais jamais pour ne pas les avoir suivies. Je vous ai rétrogradé parce que le code que vous avez montré est tout simplement mal écrit. Il défie les conventions et les idiomes sans aucune raison, et est une incarnation de l'optimisation de la permature: vous devez optimiser la lumière du jour vivante de tout autre code pour que cela devienne un goulot d'étranglement, et même dans ce cas, la troisième réécriture que j'ai proposée est plus courte, plus idiomatique et tout aussi rapide par votre raisonnement (même nombre d'accès aux attributs).
"mal écrit" - comme pourquoi exactement? "défie les conventions et les idiomes" - je vous ai prévenu. "sans raison" - oui j'ai une raison: simplifier là où la complexité ne sert à rien; "incarnation de l'optimisation prématurée" - je suis très favorable à un style de programmation qui choisit un équilibre entre lisibilité et efficacité; OP a demandé des explications sur les "avantages de l'utilisation de re.compile", ce que je comprends comme une question d'efficacité. "(ab) utiliser des classes comme espaces de noms" - ce sont vos mots qui sont abusifs. la classe est là, donc vous avez un point de référence "auto". j'ai essayé d'utiliser des modules à cet effet, les classes fonctionnent mieux.
flow
"capitaliser les noms de classe", "Non, ce n'est pas à propos de PEP8" - vous êtes apparemment tellement en colère que vous ne pouvez même pas dire quoi se chamailler en premier. "WTF", " faux " --- voyez à quel point vous êtes émotif? plus d'objectivité et moins de mousse s'il vous plaît.
flow
-5
Je crois comprendre que ces deux exemples sont effectivement équivalents. La seule différence est que dans le premier, vous pouvez réutiliser l'expression régulière compilée ailleurs sans provoquer sa recompilation.
L'appel de la fonction de recherche de l'objet de modèle compilé avec la chaîne «M» revient à appeler re.search avec l'expression régulière et la chaîne «M». Seulement beaucoup, beaucoup plus vite. (En fait, la fonction re.search compile simplement l'expression régulière et appelle pour vous la méthode de recherche de l'objet de motif résultant.)
re.sub
ne prendra pas d'argument de drapeaux ...re.compile
donné une amélioration de 10 à 50 fois. La morale est que si vous avez beaucoup de regex (plus de MAXCACHE = 100) et que vous les utilisez beaucoup de fois chacune (et séparées par plus de regex de MAXCACHE entre les deux, de sorte que chacun soit vidé du cache: donc en utilisant le même nombre de fois, puis passer au suivant ne compte pas), alors cela aiderait certainement à les compiler. Sinon, cela ne fait aucune différence.in
test de sous-chaîne de chaîne est BEAUCOUP plus rapide:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop
>python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Réponses:
J'ai beaucoup d'expérience en exécutant une expression régulière compilée 1000 fois par rapport à la compilation à la volée, et je n'ai remarqué aucune différence perceptible. Évidemment, c'est anecdotique, et certainement pas un bon argument contre la compilation, mais j'ai trouvé la différence négligeable.
EDIT: Après un rapide coup d'œil au code de la bibliothèque Python 2.5, je vois que Python compile et met en cache les expressions rationnelles chaque fois que vous les utilisez de toute façon (y compris les appels à
re.match()
), donc vous ne changez vraiment QUAND le regex est compilé, et ne devrait pas t gagner beaucoup de temps - seulement le temps qu'il faut pour vérifier le cache (une recherche de clé sur undict
type interne ).Du module re.py (les commentaires sont les miens):
Je précompile encore souvent les expressions régulières, mais uniquement pour les lier à un joli nom réutilisable, pas pour un gain de performance attendu.
la source
Pour moi, le plus grand avantage
re.compile
est de pouvoir séparer la définition de l'expression régulière de son utilisation.Même une simple expression telle que
0|[1-9][0-9]*
(entier en base 10 sans zéros non significatifs) peut être suffisamment complexe pour que vous préfériez ne pas avoir à la retaper, vérifier si vous avez fait des fautes de frappe, puis vérifier à nouveau s'il y a des fautes de frappe lorsque vous commencez le débogage . De plus, il est plus agréable d'utiliser un nom de variable tel que num ou num_b10 que0|[1-9][0-9]*
.Il est certainement possible de stocker des chaînes et de les transmettre à re.match; cependant, c'est moins lisible:
Vers la compilation:
Bien qu'elle soit assez proche, la dernière ligne de la seconde semble plus naturelle et plus simple lorsqu'elle est utilisée à plusieurs reprises.
la source
us_phone_number
ousocial_security_number
etc.FWIW:
donc, si vous utilisez beaucoup la même expression régulière, cela en vaut peut-être la peine
re.compile
(en particulier pour les expressions régulières plus complexes).Les arguments standard contre l'optimisation prématurée s'appliquent, mais je ne pense pas que vous perdiez vraiment beaucoup de clarté /
re.compile
simplicité en utilisant si vous pensez que vos regexps peuvent devenir un goulot d'étranglement des performances.Mise à jour:
Sous Python 3.6 (je soupçonne que les synchronisations ci-dessus ont été effectuées en utilisant Python 2.x) et le matériel 2018 (MacBook Pro), j'obtiens maintenant les synchronisations suivantes:
J'ai également ajouté un cas (notez les différences entre guillemets entre les deux dernières exécutions) qui montre qu'il
re.match(x, ...)
est littéralement [à peu près] équivalentre.compile(x).match(...)
, c'est-à-dire qu'aucune mise en cache en arrière-plan de la représentation compilée ne semble se produire.la source
import re
il retirer la configuration? Tout dépend de l'endroit où vous voulez mesurer. Si j'exécute un script python plusieurs fois, leimport re
temps sera atteint. Lors de la comparaison des deux, il est important de séparer les deux lignes pour le timing. Oui, comme vous dites, c'est quand vous aurez le temps de frapper. La comparaison montre que soit vous prenez le coup de temps une fois et répétez le coup de temps moindre en compilant, soit vous prenez le coup à chaque fois en supposant que le cache est vidé entre les appels, ce qui, comme cela a été souligné, pourrait arriver. L'ajout d'un calendrierh=re.compile('hello')
aiderait à clarifier.Voici un cas de test simple:
avec re.compile:
Il semblerait donc que la compilation soit plus rapide avec ce cas simple, même si vous ne correspondez qu'une seule fois .
la source
Je viens de l'essayer moi-même. Pour le cas simple de l'analyse d'un nombre d'une chaîne et de sa sommation, l'utilisation d'un objet d'expression régulière compilé est environ deux fois plus rapide que l'utilisation des
re
méthodes.Comme d'autres l'ont souligné, les
re
méthodes (y comprisre.compile
) recherchent la chaîne d'expression régulière dans un cache d'expressions précédemment compilées. Par conséquent, dans le cas normal, le coût supplémentaire de l'utilisation desre
méthodes est simplement le coût de la recherche de cache.Cependant, l'examen du code montre que le cache est limité à 100 expressions. Cela soulève la question, à quel point est-il douloureux de déborder le cache? Le code contient une interface interne au compilateur d'expression régulière,
re.sre_compile.compile
. Si nous l'appelons, nous contournons le cache. Il s'avère être environ deux ordres de grandeur plus lent pour une expression régulière de base, telle quer'\w+\s+([0-9_]+)\s+\w*'
.Voici mon test:
Les méthodes 'reallyCompiled' utilisent l'interface interne, qui contourne le cache. Notez que celui qui compile à chaque itération de boucle n'est itéré que 10 000 fois, pas un million.
la source
Je suis d'accord avec l'honnête Abe que les
match(...)
exemples donnés sont différents. Ce ne sont pas des comparaisons individuelles et, par conséquent, les résultats varient. Pour simplifier ma réponse, j'utilise A, B, C, D pour les fonctions en question. Oh oui, nous avons affaire à 4 fonctions aure.py
lieu de 3.Exécuter ce morceau de code:
revient à exécuter ce code:
Parce que, quand on regarde dans la source
re.py
, (A + B) signifie:et (C) est en fait:
Donc, (C) n'est pas la même chose que (B). En fait, (C) appelle (B) après avoir appelé (D) qui est également appelé par (A). En d' autres termes,
(C) = (A) + (B)
. Par conséquent, comparer (A + B) à l'intérieur d'une boucle a le même résultat que (C) à l'intérieur d'une boucle.George's nous l'a
regexTest.py
prouvé.L'intérêt de tous est de savoir comment obtenir le résultat de 2,323 secondes. Afin de s'assurer de
compile(...)
n'être appelé qu'une seule fois, nous devons stocker l'objet regex compilé en mémoire. Si nous utilisons une classe, nous pourrions stocker l'objet et le réutiliser à chaque fois que notre fonction est appelée.Si nous n'utilisons pas de classe (ce qui est ma demande aujourd'hui), alors je n'ai aucun commentaire. J'apprends toujours à utiliser la variable globale en Python, et je sais que la variable globale est une mauvaise chose.
Un dernier point, je pense que l'utilisation de l'
(A) + (B)
approche a un avantage . Voici quelques faits que j'ai observés (veuillez me corriger si je me trompe):Appelle A une fois, il fera une recherche dans le
_cache
suivi d'unsre_compile.compile()
pour créer un objet regex. Appelle A deux fois, il fera deux recherches et une compilation (car l'objet regex est mis en cache).Si l'
_cache
objet est vidé entre les deux, l'objet regex est libéré de la mémoire et Python doit à nouveau être compilé. (quelqu'un suggère que Python ne se recompilera pas.)Si nous gardons l'objet regex en utilisant (A), l'objet regex va toujours entrer dans _cache et être vidé d'une manière ou d'une autre. Mais notre code garde une référence dessus et l'objet regex ne sera pas libéré de la mémoire. Ceux-ci, Python n'a pas besoin de compiler à nouveau.
Les différences de 2 secondes dans le test de George compiledInLoop vs compiled sont principalement le temps requis pour construire la clé et rechercher le _cache. Cela ne signifie pas le temps de compilation de l'expression régulière.
Le test vraiment de compilation de George montre ce qui se passe s'il refait vraiment la compilation à chaque fois: ce sera 100 fois plus lent (il a réduit la boucle de 1 000 000 à 10 000).
Voici les seuls cas où (A + B) est meilleur que (C):
Cas où (C) est assez bon:
Juste un récapitulatif, voici l'ABC:
Merci d'avoir lu.
la source
Généralement, il y a peu de différence que vous utilisiez re.compile ou non. En interne, toutes les fonctions sont implémentées en termes d'étape de compilation:
De plus, re.compile () contourne la logique supplémentaire d'indirection et de mise en cache:
En plus du faible avantage de vitesse de l'utilisation de re.compile , les utilisateurs apprécient également la lisibilité qui résulte de la dénomination des spécifications de modèle potentiellement complexes et de leur séparation de la logique métier où elles sont appliquées:
Notez qu'un autre répondant croyait à tort que les fichiers pyc stockaient directement les modèles compilés; cependant, en réalité, ils sont reconstruits à chaque fois que le PYC est chargé:
Le démontage ci-dessus provient du fichier PYC pour un
tmp.py
contenant:la source
"
dansdef search(pattern, string, flags=0):"
une faute de frappe?pattern
est déjà un modèle compilé, la surcharge de mise en cache devient importante: le hachage de aSRE_Pattern
est coûteux et le modèle n'est jamais écrit dans le cache, donc la recherche échoue à chaque fois avec aKeyError
.En général, je trouve qu'il est plus facile d'utiliser des indicateurs (du moins plus faciles à retenir), comme
re.I
lors de la compilation de modèles, que d'utiliser des indicateurs en ligne.contre
la source
re.findall
.En utilisant les exemples donnés:
La méthode de correspondance dans l'exemple ci-dessus n'est pas la même que celle utilisée ci-dessous:
re.compile () renvoie un objet d'expression régulière , ce qui signifie qu'il
h
s'agit d'un objet regex.L'objet regex a sa propre méthode de correspondance avec les paramètres facultatifs pos et endpos :
regex.match(string[, pos[, endpos]])
pos
endpos
Les méthodes search , findall et finditer de l' objet regex prennent également en charge ces paramètres.
re.match(pattern, string, flags=0)
ne les prend pas en charge comme vous pouvez le voir,pas plus que ses homologues de recherche , findall et finditer .
Un objet match possède des attributs qui complètent ces paramètres:
match.pos
match.endpos
Un objet regex possède deux attributs uniques, éventuellement utiles:
regex.groups
regex.groupindex
Et enfin, un objet match a cet attribut:
match.re
la source
Mis à part la différence de performances, l'utilisation de re.compile et l'utilisation de l'objet d'expression régulière compilé pour effectuer la correspondance (quelles que soient les opérations liées aux expressions régulières) rendent la sémantique plus claire pour l'exécution Python.
J'ai eu une expérience douloureuse de débogage de code simple:
et plus tard j'utiliserais comparer dans
où
patternPhrases
est censé être une variable contenant une chaîne d'expression régulière,x[columnIndex]
est une variable contenant une chaîne.J'ai eu des problèmes qui
patternPhrases
ne correspondaient pas à une chaîne attendue!Mais si j'ai utilisé le formulaire re.compile:
puis dans
Python se serait plaint que "la chaîne n'a pas d'attribut de correspondance", comme par mappage d'arguments positionnels dans
compare
,x[columnIndex]
est utilisé comme expression régulière !, quand je voulais direDans mon cas, l'utilisation de re.compile est plus explicite du but de l'expression régulière, lorsque sa valeur est cachée aux yeux nus, donc je pourrais obtenir plus d'aide de la vérification au moment de l'exécution de Python.
Donc, la morale de ma leçon est que lorsque l'expression régulière n'est pas seulement une chaîne littérale, je dois utiliser re.compile pour laisser Python m'aider à affirmer mon hypothèse.
la source
Il y a un avantage supplémentaire à utiliser re.compile (), sous la forme d'ajouter des commentaires à mes modèles d'expression régulière à l'aide de re.VERBOSE
Bien que cela n'affecte pas la vitesse d'exécution de votre code, j'aime le faire de cette façon car cela fait partie de mon habitude de commenter. Je n'aime pas du tout passer du temps à essayer de me souvenir de la logique qui est derrière mon code 2 mois plus tard lorsque je veux apporter des modifications.
la source
re.VERBOSE
vaut la peine, et cela ajoute quelque chose que les autres réponses semblent avoir omises. Cependant, le fait de mener votre réponse par "Je poste ici parce que je ne peux pas encore commenter" est sûr de la supprimer. Veuillez ne pas utiliser la boîte de réponses pour autre chose que des réponses. Vous n'êtes qu'à une ou deux bonnes réponses de pouvoir commenter n'importe où (50 répétitions), alors soyez patient. Mettre des commentaires dans des boîtes de réponse lorsque vous savez que vous ne devriez pas y arriver plus rapidement. Il vous obtiendra des downvotes et des réponses supprimées.Selon la documentation Python :
La séquence
est équivalent à
mais l'utilisation
re.compile()
et l'enregistrement de l'objet d'expression régulière résultant pour une réutilisation est plus efficace lorsque l'expression sera utilisée plusieurs fois dans un seul programme.Donc, ma conclusion est que si vous allez faire correspondre le même modèle pour de nombreux textes différents, vous feriez mieux de le précompiler.
la source
Fait intéressant, la compilation s'avère plus efficace pour moi (Python 2.5.2 sur Win XP):
Exécuter le code ci-dessus une fois tel
if
quel , et une fois avec les deux lignes commentées dans l'autre sens, l'expression régulière compilée est deux fois plus rapidela source
J'ai effectué ce test avant de tomber sur la discussion ici. Cependant, après l'avoir exécuté, j'ai pensé que je publierais au moins mes résultats.
J'ai volé et bâtardé l'exemple dans "Mastering Regular Expressions" de Jeff Friedl. C'est sur un macbook fonctionnant sous OSX 10.6 (2Ghz intel core 2 duo, 4GB ram). La version Python est 2.6.1.
Exécuter 1 - en utilisant re.compile
Exécuter 2 - Ne pas utiliser re.compile
la source
Cette réponse pourrait arriver en retard, mais c'est une découverte intéressante. L'utilisation de la compilation peut vraiment vous faire gagner du temps si vous prévoyez d'utiliser plusieurs fois l'expression régulière (cela est également mentionné dans la documentation). Ci-dessous, vous pouvez voir que l'utilisation d'une expression régulière compilée est la plus rapide lorsque la méthode de correspondance est directement appelée dessus. passer un regex compilé à re.match le rend encore plus lent et passer re.match avec la chaîne de patter est quelque part au milieu.
la source
Outre la performance.
L'utilisation
compile
m'aide à distinguer les concepts de1. module (re) ,
2. objet regex
3. match object
Quand j'ai commencé à apprendre regex
En complément, j'ai fait une feuille de triche exhaustive du module
re
pour votre référence.la source
Je respecte vraiment toutes les réponses ci-dessus. De mon avis Oui! Pour sûr, il vaut la peine d'utiliser re.compile au lieu de compiler le regex, encore et encore, à chaque fois.
Exemple :
Utilisation dans Findall
Utilisation dans la recherche
la source
C'est une bonne question. Vous voyez souvent des gens utiliser re.compile sans raison. Cela diminue la lisibilité. Mais bien sûr, il est souvent nécessaire de précompiler l'expression. Comme lorsque vous l'utilisez plusieurs fois dans une boucle ou quelque chose du genre.
C'est comme tout sur la programmation (tout dans la vie en fait). Faites preuve de bon sens.
la source
(des mois plus tard), il est facile d'ajouter votre propre cache autour de re.match, ou toute autre chose d'ailleurs -
Un wibni, ça ne serait pas bien si: cachehint (size =), cacheinfo () -> size, hits, nclear ...
la source
Les votes sur la réponse acceptée conduisent à supposer que ce que dit @Triptych est vrai pour tous les cas. Ce n'est pas nécessairement vrai. Une grande différence est quand vous devez décider d'accepter une chaîne d'expression régulière ou un objet d'expression régulière compilé en tant que paramètre d'une fonction:
Il est toujours préférable de compiler vos expressions régulières au cas où vous auriez besoin de les réutiliser.
Notez que l'exemple dans le temps ci-dessus simule la création d'un objet regex compilé une fois au moment de l'importation par rapport à "à la volée" lorsque cela est requis pour une correspondance.
la source
Comme réponse alternative, comme je vois que cela n'a pas été mentionné auparavant, je vais continuer et citer les documents Python 3 :
la source
Voici un exemple où l'utilisation
re.compile
est plus de 50 fois plus rapide, comme demandé .Le point est le même que ce que j'ai fait dans le commentaire ci-dessus, à savoir, l'utilisation
re.compile
peut être un avantage significatif lorsque votre utilisation est telle qu'elle ne bénéficie pas beaucoup du cache de compilation. Cela se produit au moins dans un cas particulier (que j'ai rencontré en pratique), à savoir lorsque toutes les conditions suivantes sont vraies:re._MAXCACHE
, dont la valeur par défaut est actuellement 512), etre._MAXCACHE
autres regex entre les deux, de sorte que chacun est vidé du cache entre les utilisations consécutives.Exemple de sortie que j'obtiens sur mon ordinateur portable (Python 3.7.7):
Je n'ai pas pris la peine
timeit
car la différence est si flagrante, mais j'obtiens des chiffres qualitativement similaires à chaque fois. Notez que même sansre.compile
, utiliser plusieurs fois la même expression régulière et passer à la suivante n'était pas si mal (seulement environ 2 fois plus lent qu'avecre.compile
), mais dans l'autre ordre (en passant par plusieurs expressions régulières), c'est bien pire , comme prévu. En outre, l' augmentation de la taille du cache fonctionne aussi: simple réglagere._MAXCACHE = len(patterns)
ensetup()
haut (bien sûr , je ne recommande pas de faire des choses dans la production des noms avec des underscores sont classiquement « privée ») laisse tomber les ~ 23 secondes vers le bas à ~ 0,7 secondes, ce qui a également correspond à notre compréhension.la source
Les expressions régulières sont compilées avant d'être utilisées lors de l'utilisation de la deuxième version. Si vous allez l'exécuter plusieurs fois, il est certainement préférable de le compiler en premier. Si vous ne compilez pas à chaque fois que vous correspondez à un seul, c'est bien.
la source
Lisibilité / préférence de charge cognitive
Pour moi, le principal avantage est que je n'ai besoin que de me souvenir et de lire une forme de la syntaxe compliquée de l'API regex - la
<compiled_pattern>.method(xxx)
forme plutôt que cela et lare.func(<pattern>, xxx)
forme.le
re.compile(<pattern>)
un peu de passe-partout supplémentaire, c'est vrai.Mais en ce qui concerne les regex, cette étape de compilation supplémentaire est peu susceptible d'être une grande cause de charge cognitive. Et en fait, sur des modèles compliqués, vous pouvez même gagner en clarté en séparant la déclaration de la méthode regex que vous invoquez ensuite.
J'ai tendance à régler d'abord les modèles compliqués dans un site Web comme Regex101, ou même dans un script de test minimal séparé, puis à les intégrer dans mon code, donc séparer la déclaration de son utilisation correspond également à mon flux de travail.
la source
je voudrais motiver que la pré-compilation est à la fois conceptuellement et «littéralement» (comme dans la «programmation alphabétisée») avantageuse. jetez un oeil à cet extrait de code:
dans votre candidature, vous écririez:
c'est à peu près aussi simple en termes de fonctionnalités que possible. parce que cet exemple est si court, j'ai confondu le chemin pour obtenir
_text_has_foobar_re_search
tout en une seule ligne. l'inconvénient de ce code est qu'il occupe un peu de mémoire quelle que soit la durée de vie de l'TYPO
objet bibliothèque; l'avantage est que lorsque vous effectuez une recherche foobar, vous vous en sortirez avec deux appels de fonction et deux recherches de dictionnaire de classe. combien d'expressions régulières sont mises en cache parre
et les frais généraux de ce cache ne sont pas pertinents ici.comparer cela avec le style plus habituel, ci-dessous:
Dans l'application:
J'admets volontiers que mon style est très inhabituel pour le python, peut-être même discutable. cependant, dans l'exemple qui correspond le mieux à la façon dont python est principalement utilisé, pour effectuer une seule correspondance, nous devons instancier un objet, effectuer trois recherches de dictionnaire d'instance et effectuer trois appels de fonction; en outre, nous pouvons rencontrer des
re
problèmes de mise en cache lors de l'utilisation de plus de 100 expressions régulières. aussi, l'expression régulière est cachée à l'intérieur du corps de la méthode, ce qui la plupart du temps n'est pas une si bonne idée.qu'il soit dit que chaque sous-ensemble de mesures --- déclarations d'importation ciblées et aliasées; méthodes aliasées, le cas échéant; réduction des appels de fonction et des recherches de dictionnaire d'objets --- peut aider à réduire la complexité de calcul et de conception.
la source
Je crois comprendre que ces deux exemples sont effectivement équivalents. La seule différence est que dans le premier, vous pouvez réutiliser l'expression régulière compilée ailleurs sans provoquer sa recompilation.
Voici une référence pour vous: http://diveintopython3.ep.io/refactoring.html
la source