web-dev-qa-db-fra.com

Vaut-il la peine d’utiliser la recompilation de Python?

L'utilisation de la compilation pour les expressions régulières en Python présente-t-elle un avantage?

h = re.compile('hello')
h.match('hello world')

contre

re.match('hello', 'hello world')
406
Mat

J'ai beaucoup d'expérience avec des expressions rationnelles compilées des milliers de fois par rapport à la compilation à la volée, et je n'ai pas remarqué de différence perceptible. Évidemment, ceci est anecdotique, et certainement pas un bon argument contre compiler, mais j’ai trouvé que la différence était négligeable.

EDIT: Après un rapide coup d’œil sur le code de la bibliothèque Python 2.5, je constate que Python compile en interne AND re-syntaxes CACHES lorsque vous les utilisez quand même (y compris les appels à re.match()) Ainsi, vous ne changez que QUAND la regex est compilée et vous ne devriez pas gagner beaucoup de temps, mais seulement le temps nécessaire pour vérifier le cache (une recherche de clé sur un type _ dict 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 is not None: return p

    # ...
    # Does actual compilation on cache miss
    # ...

    # Caches compiled regex
    if len(_cache) >= _MAXCACHE:
        _cache.clear()
    _cache[cachekey] = p
    return p

Je compile encore souvent des expressions régulières, mais uniquement pour les lier à un joli nom réutilisable, pas pour un gain de performance attendu.

394
Triptych

Pour moi, le plus grand avantage de re.compile est de pouvoir séparer la définition de l'expression rationnelle de son utilisation.

Même une expression simple telle que 0|[1-9][0-9]* (entier en base 10 sans zéros non majuscules) peut être assez complexe pour ne pas avoir à la retaper, à vérifier si vous avez tapé des fautes de frappe et à devoir vérifier à nouveau s'il y a des des fautes de frappe lorsque vous commencez à déboguer. De plus, il est préférable d'utiliser un nom de variable tel que num ou num_b10 plutôt 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.

119
Roger Pate

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 les regex same , cela peut valoir la peine de faire re.compile (surtout pour les regex plus complexes).

Les arguments standard contre l'optimisation prématurée s'appliquent, mais je ne pense pas que vous perdez vraiment beaucoup de clarté/simplicité en utilisant re.compile si vous pensez que vos expressions rationnelles risquent de devenir un goulot d'étranglement en termes de performances.

Mise à jour:

Sous Python 3.6 (je soupçonne que les minutages ci-dessus ont été effectués à l'aide de Python 2.x) et de matériel 2018 (MacBook Pro), je reçois maintenant les minutages suivants:

% 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
Python 3.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 que re.match(x, ...) est littéralement [approximativement] équivalent à re.compile(x).match(...), c'est-à-dire qu'il ne cache pas en arrière-plan la représentation compilée. semble se produire.

55
dF.

Voici un cas de test simple:

~$ for x in 1 10 100 1000 10000 100000 1000000; 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 in 1 10 100 1000 10000 100000 1000000; 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

Donc, il semblerait que la compilation soit plus rapide avec ce cas simple, même si vous ne faites correspondre qu'une seule fois.

39
david king

Je viens d'essayer cela moi-même. Pour le cas simple d'analyser un nombre d'une chaîne et de le sommer, utiliser un objet d'expression régulière compilé est environ deux fois plus rapide que d'utiliser les méthodes re.

Comme d'autres l'ont fait remarquer, les méthodes re (y compris re.compile) recherchent la chaîne d'expression régulière dans un cache d'expressions compilées précédemment. Par conséquent, dans les cas normaux, le coût supplémentaire lié à l'utilisation des méthodes re correspond simplement au coût de la recherche dans le cache.

Cependant, l'examen du code , montre que le cache est limité à 100 expressions. Cela soulève la question suivante: à quel point est-il douloureux de saturer la mémoire cache? Le code contient une interface interne au compilateur d'expressions régulières, re.sre_compile.compile. Si nous l'appelons, nous contournons le cache. Une expression régulière de base, telle que r'\w+\s+([0-9_]+)\s+\w*', s'avère être environ deux ordres de grandeur plus lente.

Voici mon test:

#!/usr/bin/env python
import 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"

@timed
def noncompiled():
    a = 0
    for x in xrange(1000000):
        m = re.match(regularExpression, testString)
        a += int(m.group(1))
    return a

@timed
def compiled():
    a = 0
    rgx = re.compile(regularExpression)
    for x in xrange(1000000):
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def 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


@timed
def compiledInLoop():
    a = 0
    for x in xrange(1000000):
        rgx = re.compile(regularExpression)
        m = rgx.match(testString)
        a += int(m.group(1))
    return a

@timed
def reallyCompiledInLoop():
    a = 0
    for 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 'réellement compilées' utilisent l'interface interne, qui contourne le cache. Notez que celui qui est compilé à chaque itération de boucle n'est itéré que 10 000 fois, pas un million.

14
George

Je conviens avec Honest Abe que les match(...) dans les 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 4 fonctions dans re.py au lieu de 3.

Lancer ce morceau de code:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)

est identique à l'exécution de 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 réalité:

re._compile('hello').match('hello world')

Donc, (C) n'est pas la même chose que (B). En fait, (C) appelle (B) après l'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.

Le regexTest.py de George l'a prouvé.

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 chacun est de savoir comment obtenir le résultat de 2,323 secondes. Afin de nous assurer que compile(...) ne soit appelé qu'une seule fois, nous devons stocker l'objet regex compilé en mémoire. Si nous utilisons une classe, nous pouvons stocker l'objet et le réutiliser chaque fois que notre fonction est appelée.

class Foo:
    regex = re.compile('hello')
    def my_function(text)
        return regex.match(text)

Si nous n'utilisons pas la classe (qui est ma demande aujourd'hui), alors je n'ai pas de commentaire. J'apprends encore à 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’approche (A) + (B) a un avantage. Voici quelques faits que j'ai observés (corrigez-moi si je me trompe):

  1. Appels Une fois, il effectuera une recherche dans le _cache suivi par un sre_compile.compile() pour créer un objet regex. Appelle A deux fois, il effectuera deux recherches et une compilation (car l'objet regex est mis en cache).

  2. Si le _cache est vidé entre les deux, l'objet regex est libéré de la mémoire et Python doit être compilé à nouveau. (quelqu'un suggère que Python ne sera pas recompilé.)

  3. Si nous conservons l'objet regex en utilisant (A), l'objet regex entrera toujours dans _cache et sera 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'ont pas besoin de compiler à nouveau.

  4. Les différences de 2 secondes dans le test de CompiledInLoop vs compilé de George sont principalement dues au temps nécessaire à la création de la clé et à la recherche dans _cache. Cela ne signifie pas le temps de compilation de regex.

  5. Le test de vraiment compilation de George montre ce qui se passe si la compilation est vraiment refaite à 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):

  1. Si nous pouvons mettre en cache une référence de l'objet regex à l'intérieur d'une classe.
  2. Si nous devons appeler (B) à plusieurs reprises (dans une boucle ou plusieurs fois), nous devons mettre en cache la référence à l'objet regex en dehors de la boucle.

Affaire que (C) est assez bon:

  1. Nous ne pouvons pas mettre en cache une référence.
  2. Nous ne l'utilisons que de temps en temps.
  3. Globalement, nous n’avons pas trop de regex (supposons que celui-ci ne soit jamais vidé)

Juste un rappel, voici le A B C:

h = re.compile('hello')                   # (A)
h.match('hello world')                    # (B)
re.match('hello', 'hello world')          # (C)

Merci d'avoir lu.

10
John Pang

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 sous la forme d’une étape de compilation:

def match(pattern, string, flags=0):
    return _compile(pattern, flags).match(string)

def fullmatch(pattern, string, flags=0):
    return _compile(pattern, flags).fullmatch(string)

def search(pattern, string, flags=0):
    return _compile(pattern, flags).search(string)

def sub(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).sub(repl, string, count)

def subn(pattern, repl, string, count=0, flags=0):
    return _compile(pattern, flags).subn(repl, string, count)

def split(pattern, string, maxsplit=0, flags=0):
    return _compile(pattern, flags).split(string, maxsplit)

def findall(pattern, string, flags=0):
    return _compile(pattern, flags).findall(string)

def finditer(pattern, string, flags=0):
    return _compile(pattern, flags).finditer(string)

En outre, re.compile () contourne la logique de mise en cache et d’indirection supplémentaire:

_cache = {}

_pattern_type = type(sre_compile.compile("", 0))

_MAXCACHE = 512
def _compile(pattern, flags):
    # internal: compile pattern
    try:
        p, loc = _cache[type(pattern), pattern, flags]
        if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
            return p
    except KeyError:
        pass
    if isinstance(pattern, _pattern_type):
        if flags:
            raise ValueError(
                "cannot process flags argument with a compiled pattern")
        return pattern
    if not sre_compile.isstring(pattern):
        raise TypeError("first argument must be string or compiled pattern")
    p = sre_compile.compile(pattern, flags)
    if not (flags & DEBUG):
        if len(_cache) >= _MAXCACHE:
            _cache.clear()
        if p.flags & LOCALE:
            if not _locale:
                return p
            loc = _locale.setlocale(_locale.LC_CTYPE)
        else:
            loc = None
        _cache[type(pattern), pattern, flags] = p, loc
    return p

Outre le faible avantage de l’utilisation de re.compile, les utilisateurs apprécient également la lisibilité qui découle du fait de nommer des spécifications de modèle potentiellement complexes et de les séparer de la logique applicative où elles sont appliquées:

#### Patterns ############################################################
number_pattern = re.compile(r'\d+(\.\d*)?')    # Integer or decimal number
assign_pattern = re.compile(r':=')             # Assignment operator
identifier_pattern = re.compile(r'[A-Za-z]+')  # Identifiers
whitespace_pattern = re.compile(r'[\t ]+')     # Spaces and tabs

#### Applications ########################################################

if whitespace_pattern.match(s): business_logic_rule_1()
if assign_pattern.match(s): business_logic_rule_2()

Remarque, un autre répondant a cru à tort que pyc fichiers stockait directement les motifs compilés; Cependant, en réalité, ils sont reconstruits à chaque fois que le PYC est chargé:

>>> from dis import dis
>>> with open('tmp.pyc', 'rb') as f:
        f.read(8)
        dis(marshal.load(f))

  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (re)
              9 STORE_NAME               0 (re)

  3          12 LOAD_NAME                0 (re)
             15 LOAD_ATTR                1 (compile)
             18 LOAD_CONST               2 ('[aeiou]{2,5}')
             21 CALL_FUNCTION            1
             24 STORE_NAME               2 (lc_vowels)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE

Le désassemblage ci-dessus provient du fichier PYC pour un tmp.py contenant:

import re
lc_vowels = re.compile(r'[aeiou]{2,5}')
7
Raymond Hettinger

En général, je trouve qu'il est plus facile d'utiliser des indicateurs (du moins plus facile à mémoriser), comme re.I lors de la compilation de modèles plutôt que d'utiliser des indicateurs en ligne.

>>> foo_pat = re.compile('foo',re.I)
>>> foo_pat.findall('some string FoO bar')
['FoO']

contre

>>> re.findall('(?i)foo','some string FoO bar')
['FoO']
5
ptone

En utilisant les exemples donnés:

h = re.compile('hello')
h.match('hello world')

La méthode match dans l'exemple ci-dessus n'est pas la même que celle utilisée ci-dessous:

re.match('hello', 'hello world')

re.compile () renvoie un objet d'expression régulière , ce qui signifie que h est un objet d'expression régulière.

L'objet regex a sa propre méthode match == avec la méthode facultative pos et endpos paramètres:

regex.match(string[, pos[, endpos]])

pos

Le second paramètre facultatif pos donne un index dans la chaîne où la recherche doit commencer; La valeur par défaut est 0. Ce n'est pas complètement équivalent à trancher la chaîne; le caractère de modèle '^' 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 facultatif endpos limite la distance de recherche de la chaîne; ce sera comme si la chaîne était endpos caractères, ainsi seuls les caractères de pos à endpos - 1 seront cherché 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 (# ++ find) == (= de) == (= ++) == (=) == supportent également ces paramètres .

re.match(pattern, string, flags=0) ne les supporte pas comme vous pouvez le voir,
ni == (# ++ search
, findall , et finditer contreparties.

Un objet correspondant a des attributs qui complètent ces paramètres:

match.pos

La valeur de pos transmise à la méthode search () ou match () d'un objet regex. Il s'agit de l'index dans la chaîne à laquelle le moteur RE a commencé à rechercher une correspondance.

match.endpos

Valeur de endpos transmise à la méthode search () ou match () d'un objet regex. Il s'agit de l'index dans 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 motif.

regex.groupindex

Un dictionnaire mappant tous les noms de groupes symboliques définis par (? P) avec des numéros de groupe. Le dictionnaire est vide si aucun groupe de symboles n'a été utilisé dans le motif.


Et enfin, un match object a cet attribut:

match.re

L'objet d'expression régulière dont la méthode match () ou search () a généré cette occurrence de correspondance.

4
Honest Abe

Il y a un avantage supplémentaire à utiliser re.compile (), sous la forme d'ajouter des commentaires à mes modèles de regex à 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 bien le faire de cette façon, car cela fait partie de mes habitudes de commentaire. Je n'aime pas du tout passer du temps à essayer de me souvenir de la logique qui a présidé à mon code deux mois plus tard, lorsque je souhaite apporter des modifications.

4
cyneo

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 += 1

print time.time() - t

Exécuter le code ci-dessus une fois tel quel, et une fois avec les deux lignes if commentées dans l'autre sens, la regex compilée est deux fois plus rapide

3
Eli Bendersky

Cette réponse pourrait arriver tard mais c’est une découverte intéressante. Utiliser la compilation peut réellement vous faire gagner du temps si vous prévoyez d’utiliser plusieurs fois la regex (ceci est également mentionné dans la documentation). Vous pouvez voir ci-dessous que l'utilisation d'une expression rationnelle compilée est la plus rapide lorsque la méthode de correspondance est directement appelée. passer une regex compilée à re.match le rend encore plus lent et passer re.match avec la chaîne de patter se situe quelque part au milieu.

>>> ipr = r'\D+((([0-2][0-5]?[0-5]?)\.){3}([0-2][0-5]?[0-5]?))\D+'
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.5077415757028423
>>> ipr = re.compile(ipr)
>>> average(*timeit.repeat("re.match(ipr, 'abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
1.8324008992184038
>>> average(*timeit.repeat("ipr.match('abcd100.10.255.255 ')", globals={'ipr': ipr, 're': re}))
0.9187896518778871
3
Akilesh

J'ai effectué ce test avant de tomber sur la discussion ici. Cependant, après l'avoir lancé, je pensais au moins poster mes résultats.

J'ai volé et bâti l'exemple de Jeff Friedl dans "Mastering Regular Expressions". Ceci est sur un macbook sous OSX 10.6 (2Ghz Intel Core 2 Duo, 4 Go de RAM). Python version est la 2.6.1.

Run 1 - using re.compile

import re 
import time 
import fpformat
Regex1 = re.compile('^(a|b|c|d|e|f|g)+$') 
Regex2 = re.compile('^[a-g]+$')
TimesToDo = 1000
TestString = "" 
for i in range(1000):
    TestString += "abababdedfg"
StartTime = time.time() 
for i in range(TimesToDo):
    Regex1.search(TestString) 
Seconds = time.time() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    Regex2.search(TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.299 seconds
Character Class takes 0.107 seconds

Exécution 2 - Ne pas utiliser re.compile

import re 
import time 
import fpformat

TimesToDo = 1000
TestString = "" 
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() - StartTime 
print "Alternation takes " + fpformat.fix(Seconds,3) + " seconds"

StartTime = time.time() 
for i in range(TimesToDo):
    re.search('^[a-g]+$',TestString) 
Seconds = time.time() - StartTime 
print "Character Class takes " + fpformat.fix(Seconds,3) + " seconds"

Alternation takes 2.508 seconds
Character Class takes 0.109 seconds
3
netricate

La différence de performances mise à part, l'utilisation de re.compile et de l'utilisation de l'expression régulière compilée pour faire correspondre (quelles que soient les opérations associées aux expressions régulières) rend la sémantique plus claire pour Python d'exécution.

J'ai eu une expérience pénible de déboguer un code simple:

compare = lambda s, p: re.match(p, s)

et plus tard, j'utiliserais comparer

[x for x in data if compare(patternPhrases, x[columnIndex])]

patternPhrases est supposé être une variable contenant une chaîne d'expression régulière, x[columnIndex] est une variable contenant une chaîne.

J'ai eu du mal à ce que patternPhrases ne corresponde pas à une chaîne attendue!

Mais si j’utilisais 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'argument positionnel dans compare, x[columnIndex] est utilisé comme expression régulière!, Alors que je voulais dire

compare = lambda p, s: p.match(s)

Dans mon cas, utiliser re.compile est plus explicite sur le but de l'expression régulière, lorsque sa valeur est cachée à l'œil nu, je pourrais donc obtenir davantage d'aide de la part de la vérification de l'exécution [Python].

La morale de ma leçon est donc que lorsque l'expression régulière n'est pas une chaîne littérale, je devrais utiliser re.compile pour laisser Python m'aider à affirmer mon hypothèse.

3
Yu Shen

Outre la performance.

Utiliser compile me permet de distinguer les concepts de
1. module (re),
2. Objet regex
. match match
Quand j'ai commencé à apprendre le regex

#regex object
regex_object = re.compile(r'[a-zA-Z]+')
#match object
match_object = regex_object.search('1.Hello')
#matching content
match_object.group()
output:
Out[60]: 'Hello'
V.S.
re.search(r'[a-zA-Z]+','1.Hello').group()
Out[61]: 'Hello'

En complément, j’ai réalisé une feuille de triche exhaustive du module re pour 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',]
}
3
Algebra

C'est une bonne question. Vous voyez souvent des gens utiliser re.compile sans raison. Cela diminue la lisibilité. Mais bien sûr, il y a beaucoup de fois où la pré-compilation de l'expression est appelée. Par exemple, lorsque vous l'utilisez plusieurs fois dans une boucle ou une autre.

C'est comme tout ce qui concerne la programmation (tout dans la vie en fait). Appliquez le bon sens.

2
PEZ

Je respecte vraiment toutes les réponses ci-dessus. De mon avis oui! Bien sûr, il vaut la peine d'utiliser re.compile au lieu de compiler l'expression régulière, encore et encore, à chaque fois.

Utiliser re.compile rend votre code plus dynamique, car vous pouvez appeler la regex déjà compilée, au lieu de compiler à nouveau et encore une fois. Cette chose vous profite dans les cas suivants:

  1. Efforts du processeur
  2. La complexité du temps.
  3. Rend regex Universal. (Peut être utilisé dans findall, search, match)
  4. 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)

Utiliser dans la recherche

  find_alpha_numeric_string.search(example_string)

De même, vous pouvez l'utiliser pour: Match and Substitute

2
The Gr8 Adakron

Selon le Python documentation :

La séquence

prog = re.compile(pattern)
result = prog.match(string)

est équivalent à

result = re.match(pattern, string)

toutefois, il est plus efficace d'utiliser re.compile() et d'enregistrer l'objet d'expression régulière résultant pour le réutiliser, car l'expression sera utilisée plusieurs fois dans un même programme.

Donc, ma conclusion est la suivante: si vous voulez appliquer le même motif pour de nombreux textes différents, vous feriez mieux de le précompiler.

2
WuDuhRen

Comme réponse alternative, comme je vois que cela n’a pas été mentionné auparavant, je vais citer les docs Python :

Devez-vous utiliser ces fonctions au niveau du module ou devriez-vous obtenir le modèle et appeler vous-même ses méthodes? Si vous accédez à une expression rationnelle dans une boucle, la pré-compilation enregistre quelques appels de fonction. En dehors des boucles, il n’ya pas beaucoup de différence grâce au cache interne.

1
Michael Kiros

(Des mois plus tard), il est facile d’ajouter votre propre cache autour de re.match, ou quoi que ce soit d’autre -

""" Re.py: Re.match = re.match + cache  
    efficiency: re.py does this already (but what's _MAXCACHE ?)
    readability, inline / separate: matter of taste
"""

import re

cache = {}
_re_type = type( re.compile( "" ))

def match( pattern, str, *opt ):
    """ Re.match = re.match + cache re.compile( pattern ) 
    """
    if type(pattern) == _re_type:
        cpat = pattern
    Elif pattern in cache:
        cpat = cache[pattern]
    else:
        cpat = cache[pattern] = re.compile( pattern, *opt )
    return cpat.match( str )

# def search ...

Un wibni, ça ne serait pas Nice si: cachehint (size =), cacheinfo () -> taille, hits, nclear ...

1
denis

J'ai eu beaucoup d'expérience avec des regex compilées des milliers de fois par rapport à la compilation à la volée, et je n'ai pas remarqué de différence perceptible

Les votes sur la réponse acceptée mènent à l’hypothèse que ce que dit @Triptych est vrai dans tous les cas. Ce n'est pas forcément vrai. Une grande différence réside dans le fait que vous devez décider d'accepter une chaîne regex ou un objet regex 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 rationnelles au cas où vous auriez besoin de les réutiliser.

Notez que l'exemple ci-dessus simule la création d'un objet regex compilé une fois au moment de l'importation, contre "à la volée" lorsque cela est nécessaire pour une correspondance.

1
lonetwin

Les expressions régulières sont compilées avant d'être utilisées avec 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 faites une correspondance, c'est bien.

0
Adam Peck