Lohnt es sich, Pythons re.compile zu verwenden?


461

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

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

vs.

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

8
re.sub
Abgesehen von

58
Ich bin gerade auf einen Fall re.compilegestoßen, bei dem die Verwendung eine 10-50-fache Verbesserung ergab. Die Moral ist, dass wenn Sie viele reguläre Ausdrücke haben (mehr als MAXCACHE = 100) und diese jeweils häufig verwenden (und dazwischen durch mehr als MAXCACHE-reguläre Ausdrücke getrennt sind, so dass jeder aus dem Cache gelöscht wird: Verwenden Sie also das gleiche oft und dann zum nächsten überzugehen zählt nicht), dann würde es definitiv helfen, sie zu kompilieren. Ansonsten macht es keinen Unterschied.
ShreevatsaR

8
Eine kleine Sache zu beachten ist, dass für Zeichenfolgen, die keinen in>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
regulären Ausdruck

@ShreevatsaR Interessant! Können Sie eine Antwort mit einem Beispiel posten, das eine 10x-50x Verbesserung zeigt? Die meisten hier gegebenen Antworten zeigen tatsächlich eine dreifache Verbesserung in einigen präzisen Fällen und in anderen Fällen fast keine Verbesserung.
Basj

1
@Basj Fertig, habe eine Antwort gepostet . Ich habe mir nicht die Mühe gemacht, herauszufinden, wofür ich Python im Dezember 2013 verwendet habe, aber das erste, was ich versucht habe, zeigt dasselbe Verhalten.
ShreevatsaR

Antworten:


436

Ich habe viel Erfahrung damit, einen kompilierten regulären Ausdruck tausende Male im Vergleich zum spontanen Kompilieren auszuführen, und habe keinen erkennbaren Unterschied bemerkt. Offensichtlich ist dies anekdotisch und sicherlich kein gutes Argument gegen das 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 Regexe intern kompiliert UND CACHT, wann immer Sie sie verwenden (einschließlich Aufrufe von re.match()), sodass Sie sich wirklich nur ändern, WENN der Regex kompiliert wird und nicht sollte. Sie sparen überhaupt nicht viel Zeit - nur die Zeit, die zum Überprüfen des Caches benötigt wird (eine Schlüsselsuche für einen internen dictTyp).

Aus dem Modul re.py (Kommentare sind meine):

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 häufig reguläre Ausdrücke vor, aber nur, um sie an einen schönen, wiederverwendbaren Namen zu binden, nicht für einen erwarteten Leistungsgewinn.


12
Ihre Schlussfolgerung stimmt nicht mit Ihrer Antwort überein. Wenn Regexs automatisch kompiliert und gespeichert werden, ist dies in den meisten Fällen nicht von Hand erforderlich.
JFS

84
JF Sebastian, es dient dem Programmierer als Signal, dass der betreffende reguläre Ausdruck häufig verwendet wird und nicht als Wegwerfartikel gedacht ist.
Kaleissin

40
Darüber hinaus würde ich sagen, dass Sie, wenn Sie den Kompilierungs- und Cache-Treffer in einem leistungskritischen Teil Ihrer Anwendung nicht erleiden möchten, diese am besten vorab in einem nicht kritischen Teil Ihrer Anwendung kompilieren sollten .
Eddie Parker

20
Ich sehe den Hauptvorteil bei der Verwendung von kompiliertem regulären Ausdruck, wenn Sie denselben regulären Ausdruck mehrmals verwenden, wodurch die Möglichkeit von Tippfehlern verringert wird. Wenn Sie es nur einmal aufrufen, ist unkompiliert besser lesbar.
Mönch

18
Der Hauptunterschied besteht also darin, dass Sie viele verschiedene reguläre Ausdrücke (mehr als _MAXCACHE) verwenden, einige nur einmal und andere oft ... dann ist es wichtig, dass Sie Ihre kompilierten Ausdrücke für diejenigen behalten, die häufiger verwendet werden werden nicht aus dem Cache geleert, wenn es voll ist.
Fortan

133

Für mich besteht der größte Vorteil darin re.compile, die Definition des regulären Ausdrucks von seiner Verwendung trennen zu können.

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

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 eng ist, fühlt sich die letzte Zeile der zweiten bei wiederholter Verwendung natürlicher und einfacher an.


5
Ich stimme dieser Antwort zu. Die Verwendung von re.compile führt häufig zu mehr und nicht weniger lesbarem Code.
Carl Meyer

1
Manchmal ist jedoch das Gegenteil der Fall - z. B. wenn Sie den regulären Ausdruck an einer Stelle definieren und die entsprechenden Gruppen an einer anderen weit entfernten Stelle verwenden.
Ken Williams

1
@ KenWilliams Nicht unbedingt sollte ein gut benannter Regex für einen bestimmten Zweck klar sein, auch wenn er weit von der ursprünglichen Definition entfernt verwendet wird. Zum Beispiel us_phone_numberoder social_security_numberetc.
Brian M. Sheldon

2
@ BrianM.Sheldon, der den regulären Ausdruck gut benennt, hilft Ihnen nicht wirklich zu wissen, was seine verschiedenen Erfassungsgruppen darstellen.
Ken Williams

68

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 denselbenre.compile regulären Ausdruck verwenden , kann es sich lohnen, dies zu tun (insbesondere bei komplexeren regulären Ausdrücken).

Es gelten die Standardargumente gegen vorzeitige Optimierung, aber ich glaube nicht, dass Sie durch die Verwendung wirklich viel Klarheit / re.compileUnkompliziertheit verlieren, wenn Sie den Verdacht haben, dass Ihre regulären Ausdrücke zu einem Leistungsengpass werden könnten.

Aktualisieren:

Unter Python 3.6 (ich vermute, dass die oben genannten Timings mit Python 2.x durchgeführt wurden) und 2018-Hardware (MacBook Pro) 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 Läufen), der zeigt, dass dies re.match(x, ...)buchstäblich [ungefähr] äquivalent ist re.compile(x).match(...), dh es scheint kein Caching der kompilierten Darstellung hinter den Kulissen zu passieren.


5
Hauptprobleme mit Ihrer Methodik hier, da das Setup-Argument NICHT im Timing enthalten ist. Daher haben Sie die Kompilierungszeit aus dem zweiten Beispiel entfernt und im ersten Beispiel nur gemittelt. Dies bedeutet nicht, dass das erste Beispiel jedes Mal kompiliert wird.
Triptychon

1
Ja, ich stimme zu, dass dies kein fairer Vergleich der beiden Fälle ist.
Kiv

7
Ich verstehe, was Sie meinen, aber ist das nicht genau das, was in einer tatsächlichen Anwendung passieren würde, in der der reguläre Ausdruck oft verwendet wird?
dF.

26
@Triptych, @Kiv: Der springende Punkt beim Kompilieren von regulären Ausdrücken, die von der Verwendung getrennt sind, besteht darin , die Kompilierung zu minimieren. Das Entfernen aus dem Timing ist genau das, was dF hätte tun sollen, da es den realen Gebrauch am genauesten darstellt. Die Kompilierungszeit ist besonders irrelevant für die Art und Weise, wie timeit.py hier seine Timings ausführt. Es führt mehrere Läufe aus und meldet nur den kürzesten. Zu diesem Zeitpunkt wird der kompilierte reguläre Ausdruck zwischengespeichert. Die zusätzlichen Kosten, die Sie hier sehen, sind nicht die Kosten für das Kompilieren des regulären Ausdrucks, sondern die Kosten für das Nachschlagen im kompilierten regulären Ausdrucks-Cache (einem Wörterbuch).
Jemfinch

3
@Triptych Sollte das import reSetup verschoben werden? Es geht darum, wo Sie messen möchten. Wenn ich ein Python-Skript mehrmals ausführen würde, hätte es die import reZeit getroffen. Beim Vergleich der beiden ist es wichtig, die beiden Zeilen für das Timing zu trennen. Ja, wie Sie sagen, es ist, wenn Sie die Zeit getroffen haben. Der Vergleich zeigt, dass Sie entweder den Zeittreffer einmal nehmen und den kürzeren Zeittreffer durch Kompilieren wiederholen oder den Treffer jedes Mal nehmen, wenn der Cache zwischen den Aufrufen geleert wird, was, wie bereits erwähnt, passieren könnte. Das Hinzufügen eines Timings von h=re.compile('hello')würde helfen, dies zu klären.
Tom Myddeltyn

39

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

In diesem einfachen Fall scheint das Kompilieren schneller zu sein, selbst wenn Sie nur einmal übereinstimmen .


2
Welche Version von Python ist das?
Kyle Strand

2
Es ist nicht wirklich wichtig, der Punkt ist, den Benchmark in der Umgebung zu versuchen, in der Sie den Code ausführen werden
David King

1
Für mich ist die Leistung für 1000 Loops oder mehr fast genau gleich. Die kompilierte Version ist für 1-100 Schleifen schneller. (Auf beiden Pythons 2.7 und 3.4).
Zitrax

2
Bei meinem Python 2.7.3-Setup gibt es kaum einen Unterschied. Manchmal ist das Kompilieren schneller, manchmal ist es langsamer. Die Differenz beträgt immer <5%, daher zähle ich die Differenz als Messunsicherheit, da das Gerät nur eine CPU hat.
Dakkaron

1
In Python 3.4.3 in zwei getrennten Läufen: Die Verwendung von compiled war sogar langsamer als nicht kompiliert.
Zelphir Kaltstahl

17

Ich habe es einfach selbst versucht. Für den einfachen Fall, eine Zahl aus einer Zeichenfolge zu analysieren und zu summieren, ist die Verwendung eines kompilierten Objekts mit regulären Ausdrücken etwa doppelt so schnell wie die Verwendung der reMethoden.

Wie andere bereits betont haben, suchen die reMethoden (einschließlich re.compile) die Zeichenfolge für reguläre Ausdrücke in einem Cache mit zuvor kompilierten Ausdrücken. Daher sind im Normalfall die zusätzlichen Kosten für die Verwendung der reMethoden einfach die Kosten für die Cache-Suche.

Die Prüfung des Codes zeigt jedoch, dass der Cache auf 100 Ausdrücke beschränkt 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 es für einen grundlegenden regulären Ausdruck etwa zwei Größenordnungen langsamer ist, wie zr'\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, die den Cache umgeht. Beachten Sie, dass diejenige, die bei jeder Schleifeniteration kompiliert wird, nur 10.000 Mal iteriert wird, nicht eine Million.


Ich stimme Ihnen zu, dass kompilierte reguläre Ausdrücke viel schneller ausgeführt werden als nicht kompilierte. Ich habe über 10.000 Sätze ausgeführt und eine Schleife erstellt, um nach regulären Ausdrücken zu suchen, wenn die regulären Ausdrücke nicht kompiliert wurden. Jedes Mal, wenn die Vorhersage eines vollständigen Laufs 8 Stunden betrug, wurde ein Wörterbuch gemäß dem Index mit den von mir ausgeführten kompilierten regulären Ausführungsmustern erstellt das Ganze für 2 Minuten. Ich kann die obigen Antworten nicht verstehen ...
Eli Borodach

12

Ich stimme Honest Abe zu, 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 die betreffenden Funktionen. Oh ja, wir haben es mit 4 Funktionen in zu tunre.py statt mit 3 zu .

Ausführen dieses Codes:

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

entspricht dem Ausführen dieses Codes:

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

Denn wenn man in die Quelle schaut re.py, bedeutet (A + B):

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

und (C) ist tatsächlich:

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

Also ist (C) nicht dasselbe wie (B). Tatsächlich ruft (C) (B) nach dem Aufruf von (D) auf, was auch von (A) aufgerufen wird. Mit anderen Worten (C) = (A) + (B). Daher hat der Vergleich von (A + B) innerhalb einer Schleife das gleiche Ergebnis wie der Vergleich von (C) innerhalb einer Schleife.

George hat regexTest.pydas 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

Jeder interessiert sich dafür, 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 speichern. 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 verwenden (was heute meine Anfrage ist), 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 A einmal auf, führt eine Suche _cachedurch, gefolgt von einer sre_compile.compile(), um ein Regex-Objekt zu erstellen. Ruft A zweimal auf, führt es zwei Suchvorgänge und eine Kompilierung durch (da das Regex-Objekt zwischengespeichert ist).

  2. Wenn das _cachedazwischen geleert 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 immer noch in den _cache und wird irgendwie geleert. Unser Code enthält jedoch einen Verweis darauf, und das Regex-Objekt wird nicht aus dem Speicher freigegeben. Diese muss Python nicht erneut kompilieren.

  4. Die 2-Sekunden-Unterschiede zwischen Georges Test compiledInLoop und compiled sind hauptsächlich die Zeit, die erforderlich ist, um den Schlüssel zu erstellen und den _cache zu durchsuchen. Dies bedeutet nicht die Kompilierungszeit von Regex.

  5. Georges wirklich kompilierter Test zeigt, was passiert, wenn die Kompilierung jedes Mal erneut durchgeführt wird: Sie ist 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 innerhalb einer Klasse zwischenspeichern können.
  2. Wenn wir (B) wiederholt aufrufen müssen (innerhalb einer Schleife oder mehrmals), müssen wir die Referenz auf ein Regex-Objekt außerhalb der Schleife zwischenspeichern.

Fall, dass (C) gut genug ist:

  1. Wir können eine Referenz nicht zwischenspeichern.
  2. Wir benutzen es nur ab und zu.
  3. Insgesamt haben wir nicht zu viele Regex (nehmen wir an, dass die kompilierte nie gelöscht wird)

Nur eine Zusammenfassung, hier ist das ABC:

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

Danke fürs Lesen.


8

Meist gibt es kaum einen Unterschied, ob Sie re.compile verwenden oder nicht. Intern werden alle Funktionen in Form eines Kompilierungsschritts 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 dem geringen Geschwindigkeitsvorteil der Verwendung von re.compile gefällt den Benutzern auch die Lesbarkeit, die sich aus der Benennung potenziell komplexer Musterspezifikationen und deren Trennung von der Geschäftslogik ergibt, in der 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 glaubte, dass in pyc- Dateien kompilierte Muster direkt gespeichert wurden. 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 Folgendes tmp.py:

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

1
ist das "in def search(pattern, string, flags=0):"einem Tippfehler?
Phuclv

1
Beachten Sie, dass, wenn patternes sich bereits um ein kompiliertes Muster handelt, der Caching-Overhead erheblich wird: Das Hashing von a SRE_Patternist teuer und das Muster wird niemals in den Cache geschrieben, sodass die Suche jedes Mal mit a fehlschlägt KeyError.
Eric Duminil

5

Im Allgemeinen finde ich es einfacher, Flags zu verwenden (zumindest leichter zu merken, wie), wie re.Ibeim Kompilieren von Mustern, als 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']

Sie könnten re.findallsowieso auch Flags als drittes Argument von verwenden .
Aderchox

5

Anhand der angegebenen Beispiele:

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

Die Übereinstimmungsmethode im obigen Beispiel ist nicht die gleiche wie die unten verwendete:

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

re.compile () gibt ein Objekt mit regulären Ausdrücken zurück , dh esh handelt sich um ein Regex-Objekt.

Das Regex-Objekt verfügt über eine eigene Übereinstimmungsmethode mit den optionalen Parametern pos und endpos :

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

pos

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

Endpos

Der optionale Parameter endpos begrenzt, wie weit die Zeichenfolge durchsucht wird. Es ist so, als ob die Zeichenfolge Endpos- Zeichen lang ist, sodass nur die Zeichen von pos bis endpos - 1nach einer Übereinstimmung durchsucht werden. Wenn endpos kleiner als pos ist , wird keine Übereinstimmung gefunden. Andernfalls entspricht rx , wenn es sich um ein kompiliertes Objekt mit regulären Ausdrücken rx.search(string, 0, 50)handelt, äquivalent zu rx.search(string[:50], 0).

Die Such- , Findall- und Finditer- Methoden des Regex-Objekts unterstützen diese Parameter ebenfalls.

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

Ein Übereinstimmungsobjekt verfügt über 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 in der Zeichenfolge, an 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 durch (? P) definierten symbolischen Gruppennamen Gruppennummern zuordnet. Das Wörterbuch ist leer, wenn im Muster keine symbolischen Gruppen verwendet wurden.


Und schließlich hat ein Übereinstimmungsobjekt dieses Attribut:

match.re

Das Objekt mit regulärem Ausdruck, dessen Methode match () oder search () diese Übereinstimmungsinstanz erzeugt hat.


4

Abgesehen von Leistungsunterschieden macht die Verwendung von re.compile und die Verwendung des kompilierten Objekts für reguläre Ausdrücke (unabhängig von Operationen mit regulären Ausdrücken) die Semantik für die Python-Laufzeit 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 compare in verwenden

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

wo patternPhrasessoll eine Variable sein, die reguläre Ausdruckszeichenfolge enthält,x[columnIndex] eine Variable, die eine Zeichenfolge enthält.

Ich hatte Probleme, patternPhrasesdie nicht mit der erwarteten Saite übereinstimmten!

Aber wenn ich das re.compile-Formular verwendet habe:

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

dann in

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

Python hätte sich beschwert, dass "Zeichenfolge hat kein Übereinstimmungsattribut", wie durch Zuordnung von Positionsargumenten in compare, x[columnIndex]als regulärer Ausdruck verwendet wird!, Als ich das 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, sodass ich mehr Hilfe von der Python-Laufzeitprüfung erhalten könnte.

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


4

Es gibt einen zusätzlichen Vorteil bei der Verwendung von re.compile () in Form des Hinzufügens 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 hat, mit der Ihr Code ausgeführt wird, mache ich das gerne so, da dies Teil meiner Gewohnheit ist, Kommentare abzugeben. Ich mag es überhaupt nicht, Zeit damit zu verbringen, mich an die Logik zu erinnern, die 2 Monate später hinter meinem Code steckt, wenn ich Änderungen vornehmen möchte.


1
Ich habe deine Antwort bearbeitet. Ich denke, re.VERBOSEes lohnt sich zu erwähnen , und es fügt etwas hinzu, das die anderen Antworten ausgelassen zu haben scheinen. Wenn Sie Ihre Antwort jedoch mit "Ich poste hier, weil ich noch keinen Kommentar abgeben kann" führen, wird sie mit Sicherheit gelöscht. Bitte verwenden Sie das Antwortfeld nur für Antworten. Sie sind nur ein oder zwei gute Antworten davon entfernt, irgendwo etwas kommentieren zu können (50 Wiederholungen). Seien Sie also bitte geduldig. Wenn Sie Kommentare in Antwortfelder einfügen, wenn Sie wissen, dass Sie dies nicht tun sollten, gelangen Sie nicht schneller dorthin. Sie erhalten Abstimmungen und gelöschte Antworten.
skrrgwasme

4

Laut der Python- Dokumentation :

Die Sequenz

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

ist äquivalent zu

result = re.match(pattern, string)

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

Mein Fazit lautet also: Wenn Sie für viele verschiedene Texte dasselbe Muster verwenden möchten, sollten Sie es besser vorkompilieren.


3

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 einmal, wenn die beiden ifZeilen umgekehrt kommentiert sind, ist der kompilierte reguläre Ausdruck doppelt so schnell


2
Gleiches Problem wie beim Leistungsvergleich von dF. Es ist nicht wirklich fair, wenn Sie nicht die Leistungskosten der Kompilierungsanweisung selbst angeben.
Carl Meyer

6
Carl, ich bin anderer Meinung. Die Kompilierung wird nur einmal ausgeführt, während die Matching-Schleife millionenfach ausgeführt wird
Eli Bendersky

@eliben: Ich stimme Carl Meyer zu. Die Zusammenstellung erfolgt in beiden Fällen. Triptychon erwähnt, dass Caching beteiligt ist. In einem optimalen Fall (bleibt im Cache) sind beide Ansätze O ​​(n + 1), obwohl der Teil +1 irgendwie ausgeblendet ist, wenn Sie re.compile nicht explizit verwenden.
Paprika

1
Schreiben Sie keinen eigenen Benchmarking-Code. Erfahren Sie, wie Sie timeit.py verwenden, das in der Standarddistribution enthalten ist.
Jemfinch

Wie viel Zeit benötigen Sie, um die Musterzeichenfolge in der for-Schleife neu zu erstellen? Dieser Aufwand kann nicht trivial sein.
IceArdor

3

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 verfälscht. Dies ist auf einem MacBook mit OSX 10.6 (2 GHz Intel Core 2 Duo, 4 GB RAM). Die Python-Version ist 2.6.1.

Führen Sie 1 aus - mit 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

Führen Sie 2 aus - Verwenden Sie re.compile nicht

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

Diese Antwort kommt möglicherweise zu spät, ist aber ein interessanter Fund. Die Verwendung von compile kann Ihnen wirklich Zeit sparen, wenn Sie die Regex 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 Übereinstimmungsmethode direkt darauf aufgerufen wird. Das Übergeben eines kompilierten regulären Ausdrucks an re.match macht es noch langsamer und das Übergeben von re.match mit der Musterzeichenfolge befindet sich irgendwo in der Mitte.

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

Neben der Leistung.

Die Verwendung compilehilft mir, die Konzepte von
1. Modul (re) ,
2. Regex-Objekt
3. Match-Objekt zu unterscheiden.
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 reals 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',]
}

2

Ich respektiere wirklich alle oben genannten Antworten. Meiner Meinung nach ja! Natürlich lohnt es sich, re.compile zu verwenden, anstatt den regulären Ausdruck jedes Mal neu zu kompilieren.

Durch die Verwendung von re.compile wird Ihr Code dynamischer, da Sie den bereits kompilierten regulären Ausdruck aufrufen können, anstatt ihn erneut zu kompilieren. Dieses Ding 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")

Verwenden in Findall

 find_alpha_numeric_string.findall(example_string)

Verwenden bei der Suche

  find_alpha_numeric_string.search(example_string)

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


1

Das ist eine gute Frage. Sie sehen oft, dass Leute re.compile ohne Grund verwenden. Es verringert die Lesbarkeit. Aber es gibt viele Male, in denen das Vorkompilieren des Ausdrucks erforderlich ist. Zum Beispiel, wenn Sie es wiederholt in einer Schleife oder ähnlichem verwenden.

Es ist wie alles am Programmieren (eigentlich alles im Leben). Wenden Sie gesunden Menschenverstand an.


Soweit ich aus meinem kurzen Durchblättern ersehen kann, erwähnt Python in a Nutshell die Verwendung ohne re.compile () nicht, was mich neugierig machte.
Mat

Das Regex-Objekt fügt dem Kontext ein weiteres Objekt hinzu. Wie gesagt, es gibt viele Situationen, in denen re.compile () seinen Platz hat. Das Beispiel des OP ist keines davon.
PEZ

1

(Monate später) Es ist einfach, einen 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 ...

Ein Wibni, wäre es nicht schön, wenn: Cachehint (Größe =), Cacheinfo () -> Größe, Treffer, Kern ...


1

Ich habe viel Erfahrung damit, einen kompilierten regulären Ausdruck tausende Male im Vergleich zum spontanen Kompilieren auszuführen, und habe keinen erkennbaren Unterschied bemerkt

Die Abstimmung über die akzeptierte Antwort führt 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 eine Regex-Zeichenfolge oder ein kompiliertes Regex-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 das Beispiel in der obigen Zeit die Erstellung eines kompilierten Regex-Objekts einmal zum Zeitpunkt des Imports im Vergleich zu "on-the-fly" simuliert, wenn dies für eine Übereinstimmung erforderlich ist.


1

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

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 Vorkompilieren einige Funktionsaufrufe gespeichert. Außerhalb von Schleifen gibt es dank des internen Caches keinen großen Unterschied.


1

Hier ist ein Beispiel, bei dem die Verwendung je re.compilenach Anforderung über 50-mal schneller ist .

Der Punkt ist genau der gleiche wie der, den ich im obigen Kommentar gemacht habe, nämlich, dass die Verwendung re.compileein erheblicher Vorteil sein kann, wenn Ihre Verwendung so ist, dass sie nicht viel vom Kompilierungscache profitiert. Dies geschieht zumindest in einem bestimmten Fall (auf den ich in der Praxis gestoßen bin), nämlich wenn alle der folgenden Aussagen zutreffen:

  • Sie haben viele Regex-Muster (mehr als re._MAXCACHEderen Standard derzeit 512 ist) und
  • Sie verwenden diese regulären Ausdrücke häufig und
  • re._MAXCACHEIhre aufeinanderfolgenden Verwendungen desselben Musters werden durch mehr als andere reguläre Ausdrücke dazwischen getrennt, sodass jeder zwischen aufeinanderfolgenden Verwendungen aus dem Cache gelöscht wird.
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 = 0
    for 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 = 0
    for 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 = 0
    for 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}')

Beispielausgabe, die ich auf meinem Laptop erhalte (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

Ich habe mich nicht darum gekümmert, timeitda der Unterschied so groß ist, aber ich bekomme jedes Mal qualitativ ähnliche Zahlen. Beachten Sie, dass es auch ohne re.compiledie mehrfache Verwendung des gleichen regulären Ausdrucks und den Übergang zum nächsten nicht so schlecht war (nur etwa zweimal so langsam wie mit re.compile), aber in der anderen Reihenfolge (Durchlaufen vieler regulärer Ausdrucke) erheblich schlechter ist , wie erwartet. Außerdem funktioniert die Cache - Größe zu erhöhen auch: einfach Einstellung re._MAXCACHE = len(patterns)in setup()oben (ich natürlich nicht tat solche Dinge in der Produktion empfehlen als Name mit Unterstrichen wird konventionell „private“) fällt die ~ 23 Sekunden bis ~ 0,7 Sekunden wieder nach unten, die auch entspricht unserem Verständnis.


PS: Wenn ich in meinem gesamten Code nur 3 Regex-Muster verwende, von denen jedes (ohne eine bestimmte Reihenfolge) hunderte Male verwendet wird, behält der Regex-Cache den vorkompilierten Regex automatisch bei, stimmt das?
Basj

@Basj Ich denke, Sie könnten es einfach versuchen und sehen :) Aber die Antwort, ich bin mir ziemlich sicher, ist ja: Die einzigen zusätzlichen Kosten in diesem Fall AFAICT ist nur das einfache Nachschlagen des Musters im Cache . Beachten Sie auch, dass der Cache global ist (auf Modulebene), sodass Sie im Prinzip eine Abhängigkeitsbibliothek haben können, die zwischen Ihnen Regex-Suchen durchführt. Daher ist es schwierig, sich darauf zu verlassen, dass Ihr Programm immer nur 3 (oder eine beliebige Anzahl von) Regex verwendet Muster, aber es wäre ziemlich seltsam, anders zu sein :)
ShreevatsaR

0

Reguläre Ausdrücke werden kompiliert, bevor sie bei Verwendung der zweiten Version verwendet werden. Wenn Sie es mehrmals ausführen möchten, ist es definitiv besser, es zuerst zu kompilieren. Wenn Sie nicht jedes Mal kompilieren, wenn Sie für ein Einzelstück übereinstimmen, ist dies in Ordnung.


0

Lesbarkeit / kognitive Belastungspräferenz

Für mich ist der Hauptgewinn, dass ich mich nur an eine Form der komplizierten Regex-API-Syntax erinnern und diese lesen muss - die <compiled_pattern>.method(xxx)Form und nicht die und diere.func(<pattern>, xxx) Formular.

Das re.compile(<pattern>) ist ein bisschen extra Boilerplate, stimmt.

Bei Regex ist es jedoch unwahrscheinlich, dass dieser zusätzliche Kompilierungsschritt eine große Ursache für die kognitive Belastung darstellt. Bei komplizierten Mustern können Sie sogar Klarheit gewinnen, wenn Sie die Deklaration von der Regex-Methode trennen, die Sie dann aufrufen.

Ich neige dazu, zuerst komplizierte Muster in einer Website wie Regex101 oder sogar in einem separaten Minimaltestskript zu optimieren und sie dann in meinen Code zu integrieren, sodass die Trennung der Deklaration von ihrer Verwendung auch zu meinem Workflow passt.


-1

Ich möchte motivieren, dass das Vorkompilieren sowohl konzeptionell als auch "literarisch" (wie in "literatisches Programmieren") vorteilhaft ist. Schauen Sie sich dieses Code-Snippet an:

from re import compile as _Re

class TYPO:

  def text_has_foobar( self, text ):
    return self._text_has_foobar_re_search( text ) is not None
  _text_has_foobar_re_search = _Re( r"""(?i)foobar""" ).search

TYPO = TYPO()

In Ihrer Bewerbung würden Sie schreiben:

from TYPO import TYPO
print( TYPO.text_has_foobar( 'FOObar ) )

Dies ist in Bezug auf die Funktionalität so einfach wie möglich. weil dieses Beispiel so kurz ist, habe ich den Weg zusammengeführt, um _text_has_foobar_re_searchalles in einer Zeile zu bekommen . Der Nachteil dieses Codes besteht darin, dass er für die gesamte Lebensdauer des TYPOBibliotheksobjekts ein wenig Speicher belegt . Der Vorteil ist, dass Sie bei einer Foobar-Suche zwei Funktionsaufrufe und zwei Klassenwörterbuchsuchen durchführen. Wie viele reguläre Ausdrücke werden von zwischengespeichert?re und der Overhead dieses Caches ist, spielt hier keine Rolle.

Vergleichen Sie dies mit dem üblicheren Stil unten:

import re

class Typo:

  def text_has_foobar( self, text ):
    return re.compile( r"""(?i)foobar""" ).search( text ) is not None

In der Bewerbung:

typo = Typo()
print( typo.text_has_foobar( 'FOObar ) )

Ich gebe gerne zu, dass mein Stil für Python höchst ungewöhnlich ist, vielleicht sogar umstritten. In dem Beispiel, das besser mit der Verwendung von Python übereinstimmt, müssen wir jedoch ein Objekt instanziieren, drei Instanzwörterbuchsuchen durchführen und drei Funktionsaufrufe ausführen, um eine einzelne Übereinstimmung zu erzielen. Außerdem könnten wir uns darauf einlassenre Cachen Probleme , wenn mehr als 100 reguläre Ausdrücke verwenden. Außerdem wird der reguläre Ausdruck im Methodenkörper versteckt, was meistens keine so gute Idee ist.

sei es gesagt, dass jede Teilmenge von Maßnahmen - gezielte, aliasisierte Importanweisungen; gegebenenfalls Alias-Methoden; Die Reduzierung von Funktionsaufrufen und die Suche nach Objektwörterbüchern kann dazu beitragen, die Komplexität von Berechnungen und Konzepten zu reduzieren.


2
WTF. Nicht nur Sie haben eine alte, beantwortete Frage ausgegraben. Ihr Code ist ebenfalls nicht idiomatisch und auf so vielen Ebenen falsch - (ab) Verwenden von Klassen als Namespaces, in denen ein Modul ausreicht, Großschreiben von Klassennamen usw. Weitere Informationen zu besseren Implementierungen finden Sie unter pastebin.com/iTAXAWen . Ganz zu schweigen davon, dass der von Ihnen verwendete Regex ebenfalls fehlerhaft ist. Insgesamt -1

2
schuldig. Dies ist eine alte Frage, aber es macht mir nichts aus, in einem verlangsamten Gespräch die Nummer 100 zu sein. Die Frage wurde nicht geschlossen. Ich habe gewarnt, mein Code könnte einigen Geschmäcken widersprechen. Ich denke, wenn Sie es als bloße Demonstration dessen betrachten könnten, was in Python machbar ist, wie: Wenn wir alles, alles, was wir glauben, als optional betrachten und dann auf irgendeine Weise zusammen basteln, wie sehen die Dinge aus, die wir können erhalten? Ich bin sicher, Sie können Vor- und Nachteile dieser Lösung erkennen und sich klarer beschweren. Andernfalls muss ich zu dem Schluss kommen, dass Ihre Behauptung der Unrichtigkeit auf etwas mehr als PEP008 beruht
Flow

2
Nein, es geht nicht um PEP8. Das sind nur Namenskonventionen, und ich würde niemals dafür stimmen, diese nicht zu befolgen. Ich habe dich herabgestimmt, weil der Code, den du gezeigt hast, einfach schlecht geschrieben ist. Es widersetzt sich ohne Grund Konventionen und Redewendungen und ist eine Inkarnation der Permaturoptimierung: Sie müssten das lebendige Tageslicht aus allen anderen Codes heraus optimieren, damit dies zu einem Engpass wird, und selbst dann ist die dritte von mir angebotene Umschreibung kürzer, mehr idiomatisch und nach Ihren Überlegungen genauso schnell (gleiche Anzahl von Attributzugriffen).

"schlecht geschrieben" - wie genau warum? "trotzt Konventionen und Redewendungen" - ich habe Sie gewarnt. "ohne Grund" - ja, ich habe einen Grund: Vereinfachen, wo Komplexität keinen Zweck erfüllt; "Inkarnation vorzeitiger Optimierung" - ich bin sehr für einen Programmierstil, der ein Gleichgewicht zwischen Lesbarkeit und Effizienz wählt; OP bat um die Erhebung von "Nutzen bei der Verwendung von re.compile", was ich als eine Frage zur Effizienz verstehe. "(ab) Klassen als Namespaces verwenden" - es sind Ihre Worte, die missbräuchlich sind. Klasse ist da, so dass Sie einen "Selbst" -Referenzpunkt haben. Ich habe versucht, Module für diesen Zweck zu verwenden, Klassen funktionieren besser.
Flow

"Großschreibung von Klassennamen", "Nein, es geht nicht um PEP8" - Sie sind anscheinend so empörend wütend, dass Sie nicht einmal sagen können, was Sie zuerst streiten sollen. "WTF", " falsch " --- sehen Sie, wie emotional Sie sind? mehr Objektivität und weniger Schaum bitte.
Flow

-5

Mein Verständnis ist, dass diese beiden Beispiele effektiv gleichwertig sind. Der einzige Unterschied besteht darin, dass Sie den kompilierten regulären Ausdruck im ersten an anderer Stelle wiederverwenden können, ohne ihn erneut kompilieren zu müssen.

Hier ist eine Referenz für Sie: http://diveintopython3.ep.io/refactoring.html

Das Aufrufen der Suchfunktion des kompilierten Musterobjekts mit der Zeichenfolge 'M' bewirkt dasselbe wie das Aufrufen von re.search sowohl mit dem regulären Ausdruck als auch mit der Zeichenfolge 'M'. Nur viel, viel schneller. (Tatsächlich kompiliert die Funktion re.search einfach den regulären Ausdruck und ruft die Suchmethode des resultierenden Musterobjekts für Sie auf.)


1
Ich habe dich nicht abgelehnt, aber technisch gesehen ist das falsch: Python wird sowieso nicht neu kompiliert
Triptychon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.