webentwicklung-frage-antwort-db.com.de

Lohnt es sich, Pythons re.compile zu verwenden?

Hat die Verwendung von compile für reguläre Ausdrücke in Python einen Vorteil?

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

vs

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

Ich habe viel Erfahrung darin, einen kompilierten Regex 1000-mal im Vergleich zum fliegenden Kompilieren auszuführen, und habe keinen wahrnehmbaren Unterschied bemerkt. Offensichtlich ist dies anekdotisch und sicherlich kein gutes Argument gegen Kompilieren, aber ich habe festgestellt, dass der Unterschied vernachlässigbar ist.

BEARBEITEN: Nach einem kurzen Blick auf den tatsächlichen Python 2.5 - Bibliothekscode sehe ich, dass Python intern kompiliert und CACHES reguliert, wann immer Sie sie trotzdem verwenden (einschließlich Aufrufen von re.match()), also ändern Sie wirklich nur, WENN der reguläre Ausdruck kompiliert wird, und sollten nicht viel Zeit sparen - nur die Zeit, die zum Überprüfen des Caches benötigt wird (eine Schlüsselsuche in einem internen dict type).

Aus dem Modul re.py (Kommentare gehören 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 noch oft reguläre Ausdrücke vor, aber nur, um sie an einen schönen, wiederverwendbaren Namen zu binden, nicht für einen erwarteten Leistungsgewinn.

394
Triptych

Für mich ist der größte Vorteil von re.compile, Die Definition des regulären Ausdrucks von seiner Verwendung zu trennen.

Sogar ein einfacher Ausdruck wie 0|[1-9][0-9]* (Ganzzahl in der Basis 10 ohne führende Nullen) kann so komplex sein, dass Sie ihn lieber nicht erneut eingeben, überprüfen, ob Sie Tippfehler gemacht haben, und später erneut prüfen müssen, ob diese vorhanden sind sind Tippfehler, wenn Sie mit dem Debuggen beginnen. Außerdem ist es besser, einen Variablennamen wie num oder num_b10 als 0|[1-9][0-9]* Zu verwenden.

Es ist sicherlich möglich, Zeichenfolgen zu speichern und an Re-Match zu übergeben. Das ist jedoch weniger lesbar:

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

Versus Kompilieren:

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

Obwohl es ziemlich nah ist, fühlt sich die letzte Zeile der zweiten natürlicher und einfacher an, wenn sie wiederholt verwendet wird.

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

wenn Sie also häufig die gleiche Regex verwenden, kann es sich lohnen, re.compile zu tun (insbesondere für komplexere reguläre Ausdrücke).

Die Standardargumente gegen vorzeitige Optimierung gelten, aber ich denke nicht, dass Sie durch die Verwendung von re.compile Wirklich an Klarheit/Unkompliziertheit verlieren, wenn Sie den Verdacht haben, dass Ihre regulären Ausdrücke zu einem Leistungsengpass werden könnten.

pdate:

Unter Python 3.6 (Ich vermute, dass die obigen Timings mit Python 2.x) und 2018-Hardware (MacBook Pro) durchgeführt wurden, erhalte ich jetzt die folgenden Timings:

% 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, ...) buchstäblich [ungefähr] äquivalent zu re.compile(x).match(...) ist, dh kein Caching hinter den Kulissen der kompilierten Darstellung scheint zu passieren.

55
dF.

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, als ob das Kompilieren mit diesem einfachen Fall schneller ist auch wenn Sie nur einmal übereinstimmen.

39
david king

Ich habe es einfach selbst versucht. Für den einfachen Fall, dass eine Zahl aus einer Zeichenfolge analysiert und summiert wird, ist die Verwendung eines kompilierten Objekts für reguläre Ausdrücke ungefähr doppelt so schnell wie die Verwendung der re -Methoden.

Wie bereits erwähnt, schlagen die Methoden re (einschließlich re.compile) Die Zeichenfolge für reguläre Ausdrücke in einem Cache mit zuvor kompilierten Ausdrücken nach. Daher sind die zusätzlichen Kosten für die Verwendung der re -Methoden im Normalfall einfach die Kosten für die Cache-Suche.

Die Prüfung des Code zeigt jedoch, 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 zum Compiler für reguläre Ausdrücke, re.sre_compile.compile. Wenn wir es nennen, umgehen wir den Cache. Es stellt sich heraus, dass ein regulärer Grundausdruck wie r'\w+\s+([0-9_]+)\s+\w*' ungefähr zwei Größenordnungen langsamer ist.

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, die den Cache umgeht. Beachten Sie, dass derjenige, der bei jeder Schleifeniteration kompiliert wird, nur 10.000-mal iteriert wird, nicht eine Million.

14
George

Ich stimme mit Honest Abe überein, dass die match(...) in den angegebenen Beispielen unterschiedlich sind. Sie sind keine Eins-zu-Eins-Vergleiche und daher variieren die Ergebnisse. Um meine Antwort zu vereinfachen, verwende ich A, B, C, D für diese fraglichen Funktionen. Oh ja, wir haben es mit 4 Funktionen in re.py Statt mit 3 zu tun.

Code ausführen:

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

ist gleichbedeutend mit dem Ausführen dieses Codes:

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

Denn in der Quelle re.py Bedeutet (A + B):

h = re._compile('hello')                  # (D)
h.match('hello world')

und (C) ist eigentlich:

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

Also ist (C) nicht dasselbe wie (B). Tatsächlich ruft (C) (B) nach dem Aufruf von (D) auf, der auch von (A) aufgerufen wird. Mit anderen Worten, (C) = (A) + (B). Daher führt der Vergleich von (A + B) in einer Schleife zum gleichen Ergebnis wie der Vergleich von (C) in einer Schleife.

Georges regexTest.py Hat dies für uns bewiesen.

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

Das Interesse aller ist, wie man das Ergebnis von 2,323 Sekunden erhält. Um sicherzustellen, dass compile(...) nur einmal aufgerufen wird, müssen wir das kompilierte Regex-Objekt im Speicher ablegen. Wenn wir eine Klasse verwenden, können wir das Objekt speichern und bei jedem Aufruf unserer Funktion wiederverwenden.

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

Wenn wir keine Klasse benutzen (was heute meine Bitte ist), dann habe ich keinen Kommentar. Ich lerne immer noch, globale Variablen in Python zu verwenden, und ich weiß, dass globale Variablen eine schlechte Sache sind.

Noch ein Punkt, ich glaube, dass die Verwendung des (A) + (B) - Ansatzes die Oberhand hat. Hier sind einige Fakten, die ich beobachtet habe (bitte korrigieren Sie mich, wenn ich falsch liege):

  1. Ruft man A einmal auf, wird eine Suche im _cache Gefolgt von einem sre_compile.compile() durchgeführt, um ein reguläres Objekt zu erstellen. Ruft A zweimal auf, werden zwei Suchvorgänge und eine Kompilierung ausgeführt (da das Regex-Objekt zwischengespeichert wird).

  2. Wenn das _cache Zwischengespeichert wird, wird das Regex-Objekt aus dem Speicher freigegeben und Python muss erneut kompiliert werden. (Jemand schlägt vor, dass Python nicht neu kompiliert wird.)

  3. Wenn wir das Regex-Objekt mit (A) behalten, gelangt das Regex-Objekt trotzdem in den _cache und wird irgendwie geleert. Unser Code enthält jedoch eine Referenz, und das Regex-Objekt wird nicht aus dem Speicher freigegeben. Die, Python müssen nicht erneut kompiliert werden.

  4. Die 2-Sekunden-Unterschiede zwischen dem compiledInLoop-Test und dem compilierten Test von George bestehen hauptsächlich in der Zeit, die zum Erstellen des Schlüssels und Durchsuchen des _cache erforderlich ist. Dies bedeutet nicht die Kompilierungszeit von Regex.

  5. Georges Realcompile-Test zeigt, was passiert, wenn der Compiler jedes Mal wirklich neu kompiliert wird: Er wird 100-mal langsamer (er hat die Schleife von 1.000.000 auf 10.000 reduziert).

Hier sind die einzigen Fälle, in denen (A + B) besser ist als (C):

  1. Wenn wir eine Referenz des Regex-Objekts in einer Klasse zwischenspeichern können.
  2. Wenn wir (B) wiederholt aufrufen müssen (innerhalb einer Schleife oder mehrmals), müssen wir die Referenz für das Regex-Objekt außerhalb der Schleife zwischenspeichern.

Fall, dass (C) gut genug ist:

  1. Wir können keine Referenz zwischenspeichern.
  2. Wir benutzen es nur einmal in einer Weile.
  3. Insgesamt haben wir nicht zu viele reguläre Ausdrücke (davon ausgehen, dass der kompilierte niemals gelöscht wird)

Nur ein Rückblick, hier sind die ABC:

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

Danke fürs Lesen.

10
John Pang

Meistens gibt es kaum einen Unterschied, ob Sie re.compile verwenden oder nicht. Intern werden alle Funktionen in einem Kompilierungsschritt implementiert:

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)

Darüber hinaus umgeht re.compile () die zusätzliche Indirektions- und Caching-Logik:

_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

Neben der geringen Geschwindigkeit, die durch die Verwendung von re.compile erzielt wird, gefällt den Leuten auch die Lesbarkeit, die sich aus der Benennung potenziell komplexer Musterspezifikationen und der Trennung von diesen von der Geschäftslogik ergibt, wenn sie angewendet werden:

#### 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()

Beachten Sie, dass ein anderer Befragter fälschlicherweise geglaubt hat, dass pyc Dateien kompilierte Muster direkt gespeichert haben; In Wirklichkeit werden sie jedoch jedes Mal neu erstellt, wenn der PYC geladen wird:

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

Die obige Demontage stammt aus der PYC-Datei für ein tmp.py mit:

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

Im Allgemeinen finde ich es einfacher, Flags zu verwenden (zumindest leichter zu merken, wie), wie re.I beim Kompilieren von Mustern als um Flags inline zu verwenden.

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

vs

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

Anhand der angegebenen Beispiele:

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

Die Methode match im obigen Beispiel ist nicht dieselbe wie die unten verwendete:

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

re.compile () gibt ein Objekt mit regulären Ausdrücken zurück, was bedeutet, dass h ein reguläres Objekt ist.

Das Regex-Objekt hat eine eigene match Methode mit den optionalen pos und endpos Parametern:

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

pos

Der optionale zweite Parameter pos gibt einen Index in der Zeichenfolge an, bei der die Suche beginnen soll; Der Standardwert ist 0. Dies entspricht nicht vollständig dem Aufteilen der Zeichenfolge. Das Musterzeichen '^' stimmt am tatsächlichen Anfang der Zeichenfolge und an Positionen unmittelbar nach einer Zeilenumbruchzeile überein, jedoch nicht unbedingt an der Stelle, an der die Suche beginnen soll.

Endpos

Der optionale Parameter endpos begrenzt, wie weit der String durchsucht werden soll. Es sieht so aus, als ob die Zeichenfolge endpos lang ist, sodass nur die Zeichen von pos bis endpos - 1 nach einer Übereinstimmung durchsucht werden. Wenn endpos kleiner als pos ist, wird keine Übereinstimmung gefunden; Wenn andernfalls rx ein kompiliertes Objekt mit regulären Ausdrücken ist, entspricht rx.search(string, 0, 50)rx.search(string[:50], 0).

Die Methoden search, findall und finditer des Regex-Objekts unterstützen diese Parameter ebenfalls.

re.match(pattern, string, flags=0) unterstützt sie nicht, wie Sie sehen können,
noch seine Suche, Findall und Finditer Gegenstücke.

Ein Match-Objekt hat Attribute, die diese Parameter ergänzen:

match.pos

Der Wert von pos, der an die search () - oder match () -Methode eines Regex-Objekts übergeben wurde. Dies ist der Index der Zeichenfolge, bei der die RE-Engine nach einer Übereinstimmung gesucht hat.

match.endpos

Der Wert von Endpos, der an die search () - oder match () -Methode eines Regex-Objekts übergeben wurde. Dies ist der Index in der Zeichenfolge, über den die RE-Engine nicht hinausgeht.


Ein Regex-Objekt hat zwei eindeutige, möglicherweise nützliche Attribute:

regex.groups

Die Anzahl der Erfassungsgruppen im Muster.

regex.groupindex

Ein Wörterbuch, das alle symbolischen Gruppennamen, die durch (? P) definiert sind, Gruppennummern zuordnet. Das Wörterbuch ist leer, wenn im Muster keine symbolischen Gruppen verwendet wurden.


Und schließlich hat ein Match-Objekt dieses Attribut:

match.re

Das reguläre Ausdrucksobjekt, dessen match () - oder search () -Methode diese Übereinstimmungsinstanz erzeugt hat.

4
Honest Abe

Es gibt einen zusätzlichen Vorteil der Verwendung von re.compile () in Form von Kommentaren zu meinen Regex-Mustern mit re.VERBOSE

pattern = '''
hello[ ]world    # Some info on my pattern logic. [ ] to recognize space
'''

re.search(pattern, 'hello world', re.VERBOSE)

Obwohl dies keinen Einfluss auf die Geschwindigkeit der Ausführung Ihres Codes hat, mache ich es gerne so, da es Teil meiner Kommentargewohnheit ist. Ich mag es nicht, Zeit damit zu verbringen, mich an die Logik zu erinnern, die 2 Monate später hinter meinem Code stand, wenn ich Änderungen vornehmen möchte.

4
cyneo

Interessanterweise erweist sich das Kompilieren für mich als effizienter (Python 2.5.2 unter 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

Wenn Sie den obigen Code einmal so ausführen, wie er ist, und wenn Sie die beiden if -Zeilen einmal andersherum kommentiert haben, ist der kompilierte reguläre Ausdruck doppelt so schnell

3
Eli Bendersky

Diese Antwort könnte zu spät eintreffen, ist aber ein interessanter Fund. Durch die Verwendung von compile können Sie Zeit sparen, wenn Sie die reguläre Ausdrucksweise mehrmals verwenden möchten (dies wird auch in den Dokumenten erwähnt). Unten sehen Sie, dass die Verwendung eines kompilierten regulären Ausdrucks am schnellsten ist, wenn die Match-Methode direkt aufgerufen wird. Wenn Sie einen kompilierten regulären Ausdruck an re.match übergeben, wird dieser noch langsamer.

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

Ich habe diesen Test durchgeführt, bevor ich auf die Diskussion hier gestoßen bin. Nachdem ich es ausgeführt hatte, dachte ich, ich würde zumindest meine Ergebnisse veröffentlichen.

Ich habe das Beispiel in Jeff Friedls "Mastering Regular Expressions" gestohlen und bastardisiert. Dies ist auf einem MacBook mit OSX 10.6 (2 GHz Intel Core 2 Duo, 4 GB RAM). Python Version ist 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

Run 2 - Not using 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

Abgesehen von Leistungsunterschieden macht die Verwendung von re.compile und der Verwendung des kompilierten Objekts für reguläre Ausdrücke, um eine Übereinstimmung zu erzielen (unabhängig von den Operationen, die sich auf reguläre Ausdrücke beziehen), die Semantik für die Laufzeit Python) klarer.

Ich hatte einige schmerzhafte Erfahrungen mit dem Debuggen von einfachem Code:

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

und später würde ich vergleichen in verwenden

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

wobei patternPhrases eine Variable sein soll, die einen regulären Ausdruck enthält, x[columnIndex] ist eine Variable, die einen String enthält.

Ich hatte Probleme damit, dass patternPhrases nicht mit einer erwarteten Zeichenfolge übereinstimmt!

Aber wenn ich das re.compile Formular benutzt habe:

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

dann in

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

Python hätte sich darüber beschwert, dass "String kein Attribut der Übereinstimmung hat", wie durch Positionsargument-Mapping in compare, x[columnIndex] wird als regulärer Ausdruck verwendet !, als ich es eigentlich meinte

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

In meinem Fall ist die Verwendung von re.compile expliziter für den Zweck des regulären Ausdrucks, wenn sein Wert mit bloßem Auge verborgen ist. Daher kann die Laufzeitprüfung in Python=) mehr Hilfe leisten.

Die Moral meiner Lektion lautet also: Wenn der reguläre Ausdruck nicht nur eine wörtliche Zeichenfolge ist, sollte ich re.compile verwenden, um Python zuzulassen, damit ich meine Annahme durchsetzen kann.

3
Yu Shen

Neben der Leistung.

Die Verwendung von compile hilft mir, die Konzepte von zu unterscheiden
1. Modul (neu),
2. Regex-Objekt
. Match-Objekt
Als ich anfing, Regex zu lernen

#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'

Als Ergänzung habe ich ein ausführliches Cheatsheet des Moduls re als Referenz erstellt.

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

Das ist eine gute Frage. Oft sieht man Leute, die re.compile ohne Grund verwenden. Es verringert die Lesbarkeit. Aber sicher, es gibt viele Male, in denen das Vorkompilieren des Ausdrucks erforderlich ist. Wie wenn man es in einer Schleife oder so wiederholt.

Es ist wie bei allem, was mit Programmieren zu tun hat (eigentlich alles im Leben). Wenden Sie gesunden Menschenverstand an.

2
PEZ

Ich respektiere wirklich alle obigen Antworten. Aus meiner Sicht ja! Es lohnt sich auf jeden Fall, re.compile zu verwenden, anstatt den regulären Ausdruck jedes Mal neu zu kompilieren.

Die Verwendung von re.compile macht Ihren Code dynamischer, da Sie den bereits kompilierten regulären Ausdruck aufrufen können, anstatt ihn erneut zu kompilieren und zu wiederholen. Diese Sache kommt Ihnen in folgenden Fällen zugute:

  1. Prozessoranstrengungen
  2. Zeitliche Komplexität.
  3. Macht Regex Universal. (Kann in Findall, Search, Match verwendet werden)
  4. Und macht Ihr Programm cool.

Beispiel:

  example_string = "The room number of her room is 26A7B."
  find_alpha_numeric_string = re.compile(r"\b\w+\b")

Verwendung in Findall

 find_alpha_numeric_string.findall(example_string)

Bei der Suche verwenden

  find_alpha_numeric_string.search(example_string)

Ebenso können Sie es verwenden für: Match and Substitute

2
The Gr8 Adakron

Laut der Python Dokumentation :

Die Sequenz

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

ist äquivalent zu

result = re.match(pattern, string)

die Verwendung von re.compile() und das Speichern des resultierenden Objekts für reguläre Ausdrücke zur Wiederverwendung ist jedoch effizienter, wenn der Ausdruck mehrmals in einem einzelnen Programm verwendet wird.

Wenn Sie also für viele verschiedene Texte dasselbe Muster verwenden, sollten Sie es besser vorkompilieren.

2
WuDuhRen

Als alternative Antwort zitiere ich das Python 3-Dokumentation , da ich sehe, dass es noch nicht erwähnt wurde:

Sollten Sie diese Funktionen auf Modulebene verwenden oder sollten Sie das Muster abrufen und seine Methoden selbst aufrufen? Wenn Sie innerhalb einer Schleife auf einen regulären Ausdruck zugreifen, werden durch das Vorkompilieren einige Funktionsaufrufe gespart. Außerhalb von Schleifen gibt es dank des internen Caches keinen großen Unterschied.

1
Michael Kiros

(Monate später) Es ist einfach, Ihren eigenen Cache um re.match oder irgendetwas anderes hinzuzufügen -

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

Eine Wibni, wäre es nicht schön, wenn: cachehint (size =), cacheinfo () -> size, hits, nclear ...

1
denis

Ich habe viel Erfahrung darin, einen kompilierten Regex 1000-mal im Vergleich zum fliegenden Kompilieren auszuführen, und habe keinen wahrnehmbaren Unterschied bemerkt

Die Abstimmungen über die akzeptierte Antwort führen zu der Annahme, dass das, was @Triptych sagt, für alle Fälle gilt. Dies ist nicht unbedingt wahr. Ein großer Unterschied besteht darin, dass Sie entscheiden müssen, ob Sie einen regulären Ausdruck oder ein kompiliertes reguläres Ausdruck-Objekt als Parameter für eine Funktion akzeptieren möchten:

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

Es ist immer besser, Ihre regulären Ausdrücke zu kompilieren, falls Sie sie wiederverwenden müssen.

Beachten Sie, dass im obigen Beispiel die Erstellung eines kompilierten regulären Ausdrucksobjekts zum Zeitpunkt des Imports einmal simuliert wird und nicht "on-the-fly", wenn dies für eine Übereinstimmung erforderlich ist.

1
lonetwin

Reguläre Ausdrücke werden kompiliert, bevor sie in der zweiten Version verwendet werden. Wenn Sie es mehrmals ausführen, ist es definitiv besser, es zuerst zu kompilieren. Wenn Sie nicht jedes Mal kompilieren, wenn Sie einen Treffer erzielen, ist dies in Ordnung.

0
Adam Peck