632 Stimmen

Lohnt es sich, Pythons re.compile zu verwenden?

Gibt es einen Vorteil bei der Verwendung von Kompilieren für reguläre Ausdrücke in Python?

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

gegen

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

9 Stimmen

Mit Ausnahme der Tatsache, dass in 2.6 re.sub lässt sich nicht auf ein Fahnenargument ein...

84 Stimmen

Ich bin gerade auf einen Fall gestoßen, bei dem die Verwendung von re.compile führte zu einer 10-50fachen Verbesserung. Die Moral ist, dass wenn Sie haben sehr viele Regexe (mehr als MAXCACHE = 100) y Sie verwenden sie jeweils sehr oft (und zwar mit mehr als MAXCACHE Regexen dazwischen, so dass jede einzelne aus dem Cache gelöscht wird: wenn Sie also dieselbe Regex sehr oft verwenden und dann zur nächsten übergehen, zählt das nicht), dann wäre es sicherlich hilfreich, sie zusammenzustellen. Ansonsten macht es keinen Unterschied.

19 Stimmen

Eine kleine Anmerkung ist, dass für Strings, die keinen Regex benötigen, die in string substring test ist VIEL schneller: >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

545voto

Kenan Banks Punkte 196831

Ich habe viele Erfahrungen damit gemacht, eine kompilierte Regex 1000-mal im Vergleich zur On-the-Fly-Kompilierung auszuführen, und ich habe keinen erkennbaren Unterschied festgestellt. Offensichtlich ist dies anekdotisch, und sicherlich nicht ein großes Argument gegen Kompilieren, aber ich habe festgestellt, dass der Unterschied vernachlässigbar ist.

EDIT: Nach einem kurzen Blick auf den aktuellen Python 2.5 Bibliothekscode sehe ich, dass Python intern Regexe kompiliert UND zwischenspeichert, wann immer man sie verwendet (einschließlich Aufrufen von re.match() ), so dass Sie wirklich nur ändern, WANN die Regex kompiliert wird, und sollte nicht viel Zeit überhaupt sparen - nur die Zeit, die es dauert, um den Cache zu überprüfen (ein Schlüssel Lookup auf eine interne dict Typ).

Aus dem Modul re.py (Kommentare sind von mir):

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

Ich kompiliere immer noch oft reguläre Ausdrücke vor, aber nur, um sie an einen schönen, wiederverwendbaren Namen zu binden, nicht um einen Leistungsgewinn zu erwarten.

175voto

Für mich ist der größte Vorteil der re.compile ist die Möglichkeit, die Definition der Regex von ihrer Verwendung zu trennen.

Selbst ein einfacher Ausdruck wie 0|[1-9][0-9]* (Ganzzahl zur Basis 10 ohne führende Nullen) kann so komplex sein, dass man sie lieber nicht noch einmal eingeben und überprüfen möchte, ob man sich vertippt hat, und später bei der Fehlersuche erneut prüfen muss, ob es Tippfehler gibt. Außerdem ist es netter, einen Variablennamen wie num oder num_b10 zu verwenden als 0|[1-9][0-9]* .

Es ist sicherlich möglich, Zeichenketten zu speichern und sie an re.match zu übergeben; allerdings ist das weniger lesbar:

num = "..."
# then, much later:
m = re.match(num, input)

Im Gegensatz zum Kompilieren:

num = re.compile("...")
# then, much later:
m = num.match(input)

Obwohl sie ziemlich nahe beieinander liegt, wirkt die letzte Zeile der zweiten bei wiederholter Verwendung natürlicher und einfacher.

101voto

dF. Punkte 70587

NUR SO ZUM SPASS:

$ 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

Wenn Sie also die dieselbe regex häufig verwenden, kann es sich lohnen, die re.compile (insbesondere für komplexere Regexe).

Es gelten die Standardargumente gegen eine vorzeitige Optimierung, aber ich glaube nicht, dass man wirklich viel Klarheit und Geradlinigkeit verliert, wenn man re.compile wenn Sie vermuten, dass Ihre Regexps zu einem Leistungsengpass werden könnten.

Aktualisierung:

Unter Python 3.6 (ich vermute, dass die obigen Zeitmessungen mit Python 2.x durchgeführt wurden) und 2018er Hardware (MacBook Pro) erhalte ich nun die folgenden Zeitwerte:

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

Ich habe auch einen Fall hinzugefügt (beachten Sie die Anführungszeichenunterschiede zwischen den letzten beiden Durchläufen), der zeigt, dass re.match(x, ...) ist wörtlich [ungefähr] gleichbedeutend mit re.compile(x).match(...) d.h. es scheint keine Zwischenspeicherung der kompilierten Darstellung hinter den Kulissen zu geben.

45voto

david king Punkte 728

Hier ist ein einfacher Testfall:

~$ 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

mit 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

Es scheint also, dass das Kompilieren in diesem einfachen Fall schneller geht, auch wenn Sie nur einmal übereinstimmen .

23voto

George Punkte 2748

Ich habe das gerade selbst ausprobiert. Für den einfachen Fall, eine Zahl aus einer Zeichenkette zu analysieren und zu summieren, ist die Verwendung eines kompilierten Objekts mit regulärem Ausdruck etwa doppelt so schnell wie die Verwendung der re Methoden.

Wie andere bereits festgestellt haben, ist die re Methoden (einschließlich re.compile ) die Zeichenkette des regulären Ausdrucks in einem Zwischenspeicher von zuvor kompilierten Ausdrücken nachschlagen. Daher sind im Normalfall die zusätzlichen Kosten für die Verwendung der re Methoden sind einfach die Kosten für die Cache-Suche.

Die Untersuchung der code zeigt, dass der Cache auf 100 Ausdrücke begrenzt ist. Dies wirft die Frage auf, wie schmerzhaft es ist, den Cache zu überlaufen. Der Code enthält eine interne Schnittstelle für den Compiler für reguläre Ausdrücke, re.sre_compile.compile . Wenn wir ihn aufrufen, umgehen wir den Cache. Es stellt sich heraus, dass es etwa zwei Größenordnungen langsamer für einen einfachen regulären Ausdruck ist, wie r'\w+\s+([0-9_]+)\s+\w*' .

Hier ist mein 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

Die "reallyCompiled"-Methoden verwenden die interne Schnittstelle, mit der der Cache umgangen wird. Beachten Sie, dass die Methode, die bei jeder Schleifeniteration kompiliert, nur 10.000 Mal iteriert wird, nicht eine Million Mal.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X