Czy warto używać re.compile Pythona?


461

Czy korzystanie z kompilacji wyrażeń regularnych w Pythonie jest korzystne?

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

vs

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

8
Poza tym fakt, że w 2.6 re.subnie przyjmie argumentu flagi ...
new123456

58
Właśnie natrafiłem na przypadek, w którym użycie re.compiledało poprawę 10-50x. Morał jest taki , że jeśli masz dużo wyrażeń regularnych (więcej niż MAXCACHE = 100) i używasz ich wiele razy (i oddzielonych więcej niż MAXCACHE wyrażeń pośrednich pomiędzy nimi, tak że każdy z nich jest usuwany z pamięci podręcznej: więc używając ten sam wiele razy, a następnie przeniósł się do następnego jeden nie liczy), wtedy to na pewno pomaga je skompilować. W przeciwnym razie nie ma znaczenia.
ShreevatsaR

8
Jedną małą rzeczą do zapamiętania jest to, że w przypadku ciągów, które nie wymagają wyrażenia regularnego, intest podciągów jest znacznie DUŻO:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop >python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Gamrix 1'15

@ShreevatsaR Ciekawe! Czy możesz opublikować odpowiedź z przykładem, który pokazuje poprawę 10x-50x? Większość podanych tutaj odpowiedzi w niektórych przypadkach wykazuje 3-krotną poprawę, aw innych prawie brak poprawy.
Basj

1
@Basj Gotowe, opublikował odpowiedź . W grudniu 2013 roku nie zadałem sobie trudu wykopania tego, do czego używałem Pythona, ale pierwsza prosta rzecz, którą próbowałem, wykazuje to samo zachowanie.
ShreevatsaR

Odpowiedzi:


436

Miałem duże doświadczenie w tworzeniu skompilowanego wyrażenia regularnego 1000 razy w porównaniu do kompilacji w locie i nie zauważyłem żadnej zauważalnej różnicy. Oczywiście jest to anegdota i na pewno nie jest to świetny argument przeciwko kompilacji, ale uznałem różnicę za nieistotną.

EDYCJA: Po szybkim spojrzeniu na rzeczywisty kod biblioteki Python 2.5, widzę, że Python kompiluje wewnętrznie, a CACHES regexes za każdym razem, gdy ich używasz (w tym wywołania re.match()), więc tak naprawdę zmieniasz się tylko, gdy regex zostanie skompilowany i nie powinien t w ogóle oszczędza dużo czasu - tylko czas potrzebny na sprawdzenie pamięci podręcznej (wyszukiwanie klucza na dicttypie wewnętrznym ).

Z modułu re.py (komentarze są moje):

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

Nadal często kompiluję wyrażenia regularne, ale tylko po to, by powiązać je z ładną nazwą wielokrotnego użytku, a nie w celu uzyskania oczekiwanego wzrostu wydajności.


12
Twój wniosek jest niezgodny z odpowiedzią. Jeśli wyrażenia regularne są kompilowane i przechowywane automatycznie, w większości przypadków nie trzeba tego robić ręcznie.
jfs

84
JF Sebastian, służy jako sygnał dla programisty, że dana regexp będzie często używana i nie ma być wyrzuceniem.
kaleissin

40
Co więcej, powiedziałbym, że jeśli nie chcesz ucierpieć w wyniku kompilacji i pamięci podręcznej niektórych krytycznych pod względem wydajności części aplikacji, najlepiej skompiluj je przed przekazaniem w niekrytycznej części aplikacji .
Eddie Parker,

20
Widzę główną zaletę używania skompilowanego wyrażenia regularnego, jeśli ponownie używasz tego samego wyrażenia regularnego wiele razy, zmniejszając w ten sposób możliwość literówek. Jeśli po prostu wywołujesz go raz, wówczas nieskompilowany jest bardziej czytelny.
monkut

18
Główną różnicą będzie to, że używasz wielu różnych wyrażeń regularnych (więcej niż _MAXCACHE), niektóre z nich tylko raz, a inne wiele razy ... wtedy ważne jest, aby zachować skompilowane wyrażenia dla tych, które są używane częściej, aby nie są wypłukiwane z pamięci podręcznej, gdy jest pełna.
fortran

133

Dla mnie największą korzyścią re.compilejest możliwość oddzielenia definicji wyrażenia regularnego od jego użycia.

Nawet proste wyrażenie, takie jak 0|[1-9][0-9]*(liczba całkowita w bazie 10 bez zer wiodących) może być na tyle złożone, że wolisz nie musieć go wpisywać, sprawdzać, czy napisałeś jakieś literówki, a później musisz ponownie sprawdzić, czy są literówki podczas rozpoczynania debugowania . Ponadto, lepiej jest użyć nazwy zmiennej, takiej jak num lub num_b10 niż 0|[1-9][0-9]*.

Z pewnością możliwe jest przechowywanie ciągów i przekazywanie ich do ponownego dopasowania; jest to jednak mniej czytelne:

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

W porównaniu z kompilacją:

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

Chociaż jest dość blisko, ostatnia linia drugiego wydaje się bardziej naturalna i prostsza, gdy jest używana wielokrotnie.


5
Zgadzam się z tą odpowiedzią; często użycie re.compile powoduje, że kod jest mniej czytelny.
Carl Meyer

1
Czasami jest odwrotnie - np. Jeśli zdefiniujesz regex w jednym miejscu i użyjesz pasujących grup w innym odległym miejscu.
Ken Williams

1
@KenWilliams Niekoniecznie dobrze wyrażona regex dla określonego celu powinna być wyraźna, nawet jeśli jest używana daleko od oryginalnej definicji. Na przykład us_phone_numberlub social_security_numberitp.
Brian M. Sheldon

2
@ BrianM.Sheldon nazywanie dobrze wyrażenia regularnego tak naprawdę nie pomaga wiedzieć, co reprezentują różne grupy przechwytywania.
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

więc jeśli zamierzasz używać często samego wyrażenia regularnego, warto to zrobić re.compile(szczególnie w przypadku bardziej złożonych wyrażeń regularnych).

Obowiązują standardowe argumenty przeciwko przedwczesnej optymalizacji, ale nie sądzę, abyś naprawdę stracił dużo przejrzystości / prostoty, re.compilejeśli podejrzewasz, że wyrażenia regularne mogą stać się wąskim gardłem wydajności.

Aktualizacja:

Pod Pythonem 3.6 (podejrzewam, że powyższe czasy zostały wykonane przy użyciu Python 2.x) i sprzętu 2018 (MacBook Pro), teraz otrzymuję następujące czasy:

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

Dodałem także przypadek (zauważ różnice w cudzysłowie między dwoma ostatnimi biegami), który pokazuje, że re.match(x, ...)jest dosłownie [z grubsza] równoważny re.compile(x).match(...), tj. Nie wydaje się, aby buforowanie skompilowanej reprezentacji było za kulisami.


5
Poważne problemy z twoją metodologią tutaj, ponieważ argument instalacyjny NIE zawiera czasu. W ten sposób usunąłeś czas kompilacji z drugiego przykładu i po prostu uśredniłeś go w pierwszym przykładzie. Nie oznacza to, że pierwszy przykład kompiluje się za każdym razem.
Tryptyk

1
Tak, zgadzam się, że nie jest to uczciwe porównanie tych dwóch przypadków.
Kiv

7
Rozumiem, co masz na myśli, ale czy nie jest to dokładnie to, co stałoby się w rzeczywistej aplikacji, w której wyrażenie regularne jest używane wiele razy?
dF.

26
@Triptych, @Kiv: Celem kompilacji wyrażeń regularnych oddzielnie od użycia jest zminimalizowanie kompilacji; usunięcie go z timingu jest dokładnie tym, co powinien zrobić dF, ponieważ najdokładniej reprezentuje rzeczywiste użycie. Czas kompilacji jest szczególnie nieistotny ze względu na sposób, w jaki timeit.py robi tutaj swoje czasy; wykonuje kilka przebiegów i zgłasza tylko najkrótszy, w którym to momencie skompilowane wyrażenie regularne jest buforowane. Dodatkowym kosztem, który tu widzisz, nie jest koszt skompilowania wyrażenia regularnego, ale koszt wyszukania go w skompilowanej pamięci podręcznej wyrażenia regularnego (słowniku).
jemfinch

3
@Triptych Czy import renależy usunąć z konfiguracji? Chodzi o to, gdzie chcesz zmierzyć. Gdybym uruchomił skrypt Pythona wiele razy, miałby to import reczas. Porównując oba ważne jest, aby oddzielić dwie linie pomiaru czasu. Tak, jak mówisz, to czas, w którym będziesz miał czas. Porównanie pokazuje, że albo raz bierzesz czas trafienia i powtarzasz mniejszy czas trafienia przez kompilację, albo bierzesz trafienie za każdym razem, zakładając, że pamięć podręczna zostanie wyczyszczona między połączeniami, co, jak już wspomniano, może się zdarzyć. Dodanie terminu h=re.compile('hello')pomogłoby wyjaśnić.
Tom Myddeltyn

39

Oto prosty przypadek testowy:

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

z 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

Wydaje się, że kompilacja jest szybsza w tym prostym przypadku, nawet jeśli dopasujesz tylko raz .


2
Która to wersja Pythona?
Kyle Strand

2
to naprawdę nie ma znaczenia, chodzi o to, aby wypróbować test porównawczy w środowisku, w którym będziesz uruchamiał kod
David King

1
Dla mnie wydajność jest prawie taka sama dla 1000 pętli lub więcej. Skompilowana wersja jest szybsza dla 1-100 pętli. (Na obu pytonach 2.7 i 3.4).
Zitrax,

2
W mojej konfiguracji Python 2.7.3 nie ma prawie żadnej różnicy. Czasami kompilacja jest szybsza, a czasem wolniejsza. Różnica wynosi zawsze <5%, dlatego liczę różnicę jako niepewność pomiaru, ponieważ urządzenie ma tylko jeden procesor.
Dakkaron,

1
W Pythonie 3.4.3 widać w dwóch osobnych uruchomieniach: użycie skompilowanego było nawet wolniejsze niż nie skompilowane.
Zelphir Kaltstahl

17

Właśnie tego próbowałem. W prostym przypadku parsowania liczby z ciągu i sumowania jej użycie skompilowanego obiektu wyrażenia regularnego jest około dwa razy szybsze niż użyciere metod.

Jak zauważyli inni, remetody (w tym re.compile) sprawdzają ciąg wyrażeń regularnych w pamięci podręcznej wcześniej skompilowanych wyrażeń. Dlatego w normalnym przypadku dodatkowy koszt korzystania zre metod jest po prostu koszt wyszukiwania pamięci podręcznej.

Jednak analiza kodu pokazuje, że pamięć podręczna jest ograniczona do 100 wyrażeń. To nasuwa pytanie, jak bolesne jest przepełnienie pamięci podręcznej? Kod zawiera wewnętrzny interfejs do zwykłego kompilatora ekspresyjnego re.sre_compile.compile. Jeśli to nazwiemy, pomijamy pamięć podręczną. Okazuje się, że jest około dwa rzędy wielkości wolniejsze dla podstawowego wyrażenia regularnego, takiego jakr'\w+\s+([0-9_]+)\s+\w*' .

Oto mój 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

Metody „naprawdę skompilowane” wykorzystują wewnętrzny interfejs, który omija pamięć podręczną. Zauważ, że ten, który kompiluje się przy każdej iteracji pętli, jest iterowany tylko 10 000 razy, a nie milion.


Zgadzam się z tobą, że skompilowane wyrażenia regularne działają znacznie szybciej niż nieskompilowane. Uruchomiłem ponad 10 000 zdań i utworzyłem w nich pętlę do iteracji wyrażeń regularnych, gdy wyrażenia regularne nie były kompilowane i były obliczane za każdym razem, gdy przewidywanie pełnego przebiegu wynosiło 8 godzin, po utworzeniu słownika według indeksu ze skompilowanymi wzorcami wyrażeń regularnych uruchamiam całość przez 2 minuty. Nie rozumiem powyższych odpowiedzi ...
Eli Borodach,

12

Zgadzam się z uczciwym Abe, że match(...)podane przykłady są różne. Nie są to porównania bezpośrednie, dlatego wyniki są różne. Aby uprościć moją odpowiedź, używam A, B, C, D dla tych funkcji. O tak, mamy do czynienia z 4 funkcjami re.pyzamiast 3.

Uruchamianie tego fragmentu kodu:

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

jest taki sam jak uruchomienie tego kodu:

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

Ponieważ, gdy spojrzymy na źródło re.py, (A + B) oznacza:

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

a (C) to w rzeczywistości:

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

Zatem (C) to nie to samo co (B). W rzeczywistości (C) wywołuje (B) po wywołaniu (D), które jest również wywoływane przez (A). Innymi słowy (C) = (A) + (B). Dlatego porównanie (A + B) w pętli daje taki sam wynik jak (C) w pętli.

George regexTest.pyudowodnił nam to.

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

Interesuje wszystkich, jak uzyskać wynik 2,323 sekundy. Aby mieć pewność, że compile(...)zostaniemy wywołani tylko raz, musimy zapisać skompilowany obiekt wyrażenia regularnego w pamięci. Jeśli korzystamy z klasy, możemy zapisać obiekt i użyć go ponownie przy każdym wywołaniu naszej funkcji.

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

Jeśli nie korzystamy z klasy (co dzisiaj jest moją prośbą), nie mam komentarza. Wciąż uczę się używać zmiennej globalnej w Pythonie i wiem, że zmienna globalna jest złą rzeczą.

Jeszcze jeden punkt, uważam, że stosowanie (A) + (B)podejścia ma przewagę. Oto kilka faktów, które zauważyłem (popraw mnie, jeśli się mylę):

  1. Połączenia A raz wykona jedno wyszukiwanie, _cachea następnie jednosre_compile.compile() aby utworzyć obiekt wyrażenia regularnego. Wywołuje A dwa razy, wykona dwa wyszukiwania i jedno skompiluje (ponieważ obiekt regex jest buforowany).

  2. Jeśli… _cache get zostanie przepłukany pomiędzy nimi, obiekt wyrażenia regularnego zostanie zwolniony z pamięci i Python musi się ponownie skompilować. (ktoś sugeruje, że Python się nie skompiluje).

  3. Jeśli zachowamy obiekt wyrażenia regularnego za pomocą (A), obiekt wyrażenia regularnego nadal dostanie się do _cache i zostanie jakoś opróżniony. Ale nasz kod zachowuje na nim odwołanie, a obiekt wyrażenia regularnego nie zostanie zwolniony z pamięci. Te Python nie musi się kompilować ponownie.

  4. 2-sekundowe różnice w kompilowanym teście George'a skompilowanym InLoop vs skompilowanym to głównie czas potrzebny na zbudowanie klucza i przeszukanie _cache. Nie oznacza to czasu kompilacji wyrażenia regularnego.

  5. Test reallycompile George'a pokazuje, co się stanie, jeśli naprawdę za każdym razem ponownie kompiluje: będzie 100 razy wolniejszy (zmniejszył pętlę z 1 000 000 do 10 000).

Oto jedyne przypadki, w których (A + B) jest lepszy niż (C):

  1. Jeśli możemy buforować odwołanie do obiektu wyrażenia regularnego w klasie.
  2. Jeśli musimy wielokrotnie wywoływać (B) (w pętli lub wiele razy), musimy buforować odwołanie do wyrażenia regularnego poza pętlą.

Sprawa, że ​​(C) jest wystarczająco dobry:

  1. Nie możemy buforować referencji.
  2. Używamy go tylko raz na jakiś czas.
  3. Ogólnie rzecz biorąc, nie mamy zbyt wielu wyrażeń regularnych (zakładając, że skompilowany nigdy nie zostanie opróżniony)

Podsumowując, oto ABC:

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

Dziękuje za przeczytanie.


8

Przeważnie nie ma różnicy, czy używasz re.compile, czy nie. Wewnętrznie wszystkie funkcje są realizowane w ramach kroku kompilacji:

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)

Ponadto re.compile () omija dodatkową logikę pośrednictwa i buforowania:

_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

Oprócz niewielkiej prędkości korzystania z re.kompilacji , ludziom podoba się również czytelność wynikająca z nazywania potencjalnie złożonych specyfikacji wzorców i oddzielania ich od logiki biznesowej, w której są stosowane:

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

Uwaga, jeden inny respondent błędnie w to wierzył pliki pyc bezpośrednio przechowują skompilowane wzorce; jednak w rzeczywistości są one przebudowywane za każdym razem, gdy ładowane jest PYC:

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

Powyższy demontaż pochodzi z pliku PYC tmp.pyzawierającego:

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

1
jest "w def search(pattern, string, flags=0):"literówka?
phuclv

1
Zauważ, że jeśli patternjest już skompilowanym wzorcem, narzut buforowania staje się znaczący: haszowanie a SRE_Patternjest kosztowne, a wzorzec nigdy nie jest zapisywany w pamięci podręcznej, więc wyszukiwanie kończy się niepowodzeniem za pomocą a KeyError.
Eric Duminil,

5

Ogólnie rzecz biorąc, uważam, że łatwiej jest używać flag (przynajmniej łatwiej zapamiętać, w jaki sposób), na przykład re.Ipodczas kompilowania wzorców, niż używać flag inline.

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

W re.findallkażdym razie możesz użyć flag jako trzeciego argumentu .
aderchox

5

Korzystając z podanych przykładów:

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

Metoda dopasowania w powyższym przykładzie nie jest taka sama, jak zastosowana poniżej:

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

Funkcja re.compile () zwraca obiekt wyrażenia regularnego , co oznacza, że hjest obiektem wyrażenia regularnego.

Obiekt regex ma własną metodę dopasowania z opcjonalnymi parametrami pos i endpos :

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

poz

Opcjonalny drugi parametr pos podaje indeks w ciągu znaków, od którego ma się rozpocząć wyszukiwanie; domyślnie wynosi 0. Nie jest to całkowicie równoważne z krojeniem łańcucha; '^'charakter wzorzec pasuje na prawdziwym początku łańcucha i na stanowiskach tuż po nowej linii, ale niekoniecznie w tym indeksie, gdzie ma się rozpocząć wyszukiwanie.

końcówki

Opcjonalny parametr endpos ogranicza zasięg wyszukiwania ciągu; będzie tak, jakby ciąg znaków zawierał znaki końcowe , więc tylko znaki od pos do endpos - 1będą wyszukiwane w celu dopasowania. Jeśli endpos jest mniejszy niż pos , nie zostanie znalezione dopasowanie; w przeciwnym razie, jeśli rx jest skompilowanym obiektem wyrażeń regularnych, rx.search(string, 0, 50)jest równoważne z rx.search(string[:50], 0).

Metody wyszukiwania , znajdowania i znajdowania obiektu regex również obsługują te parametry.

re.match(pattern, string, flags=0)nie obsługuje ich jak widać,
ani też jej szukania , FindAll i finditer .

Obiekt dopasowania ma atrybuty, które uzupełniają następujące parametry:

match.pos

Wartość pos, która została przekazana do metody search () lub match () obiektu wyrażenia regularnego. Jest to indeks w ciągu, w którym silnik RE zaczął szukać dopasowania.

match.endpos

Wartość endpos, która została przekazana do metody search () lub match () obiektu regex. Jest to indeks w ciągu znaków, poza którym silnik RE nie pójdzie.


Obiekt wyrażenia regularnego ma dwa unikalne, być może przydatne, atrybuty:

regex.groups

Liczba grup przechwytywania we wzorcu.

regex.groupindex

Słownik mapujący dowolne symboliczne nazwy grup zdefiniowane przez (? P) na numery grup. Słownik jest pusty, jeśli we wzorcu nie użyto żadnych grup symbolicznych.


I wreszcie obiekt dopasowania ma ten atrybut:

match.re

Obiekt wyrażenia regularnego, którego metoda match () lub search () wygenerowała tę instancję dopasowania.


4

Pomijając różnicę wydajności, użycie re.compile i użycie skompilowanego obiektu wyrażeń regularnych w celu dopasowania (niezależnie od operacji związanych z wyrażeniami regularnymi) sprawia, że ​​semantyka jest bardziej przejrzysta w czasie wykonywania Pythona.

Miałem trochę bolesnych doświadczeń z debugowaniem prostego kodu:

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

a później skorzystam z porównania

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

gdzie patternPhrasespowinna być zmienna zawierająca ciąg wyrażeń regularnych,x[columnIndex] jest zmienną zawierającą ciąg znaków.

Miałem problemy, które patternPhrasesnie pasowały do ​​oczekiwanego ciągu!

Ale jeśli użyłem formularza re.compile:

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

potem w

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

Python by narzekali, że „ciąg nie posiada atrybut meczu”, jak przez pozycyjnym argumentu w odwzorowaniu compare, x[columnIndex]stosowany jest jako wyrażenie regularne !, kiedy faktycznie oznaczało

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

W moim przypadku użycie re.compile jest bardziej jednoznaczne z celem wyrażenia regularnego, gdy jego wartość jest ukryta gołym okiem, dzięki czemu mogę uzyskać więcej pomocy ze sprawdzania czasu wykonania Pythona.

Morał mojej lekcji polega na tym, że gdy wyrażenie regularne nie jest tylko ciągiem literalnym, powinienem użyć re.compile, aby pozwolić Pythonowi na potwierdzenie mojego założenia.


4

Istnieje jeden dodatkowy dodatek za pomocą re.compile (), w postaci dodawania komentarzy do moich wzorców wyrażeń regularnych za pomocą re.VERBOSE

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

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

Chociaż nie wpływa to na szybkość uruchamiania twojego kodu, lubię to robić w ten sposób, ponieważ jest to część mojego zwyczaju komentowania. Naprawdę nie lubię spędzać czasu próbując zapamiętać logikę, która poszła za moim kodem 2 miesiące później, gdy chcę wprowadzić modyfikacje.


1
Zredagowałem twoją odpowiedź. Myślę, że re.VERBOSEwarto wspomnieć o tym i dodać coś, co wydaje się pominięte w innych odpowiedziach. Jednak wprowadzenie odpowiedzi „Publikuję tutaj, ponieważ nie mogę jeszcze komentować” z pewnością spowoduje jej usunięcie. Proszę nie używać pola odpowiedzi do niczego innego niż odpowiedzi. Masz tylko jedną lub dwie dobre odpowiedzi, aby móc komentować w dowolnym miejscu (50 powtórzeń), więc bądź cierpliwy. Umieszczanie komentarzy w polach odpowiedzi, gdy wiesz, że nie powinieneś, nie dotrze tam szybciej. Otrzymasz downvotes i usunięte odpowiedzi.
skrrgwasme

4

Zgodnie z dokumentacją w języku Python :

Sekwencja

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

jest równa

result = re.match(pattern, string)

ale za pomocą re.compile() i zapisanie wynikowego obiektu wyrażenia regularnego do ponownego użycia jest bardziej wydajne, gdy wyrażenie będzie używane kilka razy w jednym programie.

Mój wniosek jest taki, że jeśli zamierzasz dopasować ten sam wzór dla wielu różnych tekstów, lepiej go wstępnie skompiluj.


3

Co ciekawe, kompilacja okazuje się dla mnie bardziej wydajna (Python 2.5.2 na 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

Uruchomienie powyższego kodu raz tak, jak jest, a raz z ifkomentarzem dwóch wierszy na odwrót, skompilowane wyrażenie regularne jest dwa razy szybsze


2
Ten sam problem jak w porównaniu wydajności dF. To niesprawiedliwe, chyba że podasz koszt wydajności samej instrukcji kompilacji.
Carl Meyer

6
Carl, nie zgadzam się. Kompilacja jest wykonywana tylko raz, a pętla dopasowywania wykonywana jest milion razy
Eli Bendersky

@eliben: Zgadzam się z Carlem Meyerem. Kompilacja odbywa się w obu przypadkach. Tryptyk wspomina, że ​​buforowanie jest zaangażowane, więc w optymalnym przypadku (ponowne pozostanie w pamięci podręcznej) oba podejścia mają wartość O (n + 1), chociaż część +1 jest w pewnym sensie ukryta, gdy nie używa się jawnie kompilacji ponownej.
papryka

1
Nie pisz własnego kodu porównawczego. Naucz się korzystać z timeit.py, który jest zawarty w standardowej dystrybucji.
jemfinch

Ile czasu spędzasz na odtwarzaniu łańcucha wzorca w pętli for. Ten narzut nie może być trywialny.
IceArdor

3

Przeprowadziłem ten test, zanim natknąłem się na dyskusję tutaj. Jednak po uruchomieniu pomyślałem, że przynajmniej opublikuję swoje wyniki.

Ukradłem i podarowałem przykład w „Mastering Regular Expressions” Jeffa Friedla. To jest na MacBooku z systemem OSX 10.6 (2 GHz Intel Core 2 duet, 4 GB pamięci RAM). Wersja Python to 2.6.1.

Uruchom 1 - używając 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

Uruchom 2 - Nie używa 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

Ta odpowiedź może się spóźnić, ale jest ciekawym odkryciem. Korzystanie z kompilacji może naprawdę zaoszczędzić czas, jeśli planujesz używać wyrażenia regularnego wiele razy (jest to również wspomniane w dokumentacji). Poniżej widać, że użycie skompilowanego wyrażenia regularnego jest najszybsze, gdy metoda dopasowania jest bezpośrednio na nim wywołana. przekazywanie skompilowanego wyrażenia regularnego do re.match czyni go jeszcze wolniejszym, a przekazywanie re.match z łańcuchem wzorcowym jest gdzieś pośrodku.

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

Oprócz wydajności.

Używanie compilepomaga mi rozróżnić pojęcia
1. modułu (re) ,
2. obiektu regex
3. dopasowania obiektu
Kiedy zacząłem uczyć się 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'

Jako uzupełnienie, stworzyłem wyczerpującą ściągę modułu redla twojego odniesienia.

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

Naprawdę szanuję wszystkie powyższe odpowiedzi. Moim zdaniem Tak! Na pewno warto używać re.compile zamiast kompilować regex za każdym razem.

Użycie re.compile sprawia, że ​​Twój kod jest bardziej dynamiczny, ponieważ możesz wywoływać już skompilowane wyrażenie regularne, zamiast kompilować ponownie i ponownie. Ta rzecz przynosi korzyści w przypadkach:

  1. Wysiłki procesora
  2. Złożoność czasu.
  3. Sprawia, że ​​regex Universal. (Może być użyty do szukania, wyszukiwania, dopasowania)
  4. I sprawia, że ​​Twój program wygląda świetnie.

Przykład:

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

Używanie w Findall

 find_alpha_numeric_string.findall(example_string)

Używanie w wyszukiwaniu

  find_alpha_numeric_string.search(example_string)

Podobnie możesz go użyć do: Dopasuj i Zastąp


1

To dobre pytanie. Często widzisz, że ludzie używają re.compile bez powodu. Zmniejsza to czytelność. Ale na pewno jest wiele razy, gdy wymagana jest wstępna kompilacja wyrażenia. Na przykład, gdy używasz go wielokrotnie w pętli lub coś takiego.

To jest jak wszystko w programowaniu (właściwie wszystko w życiu). Stosuj zdrowy rozsądek.


O ile mogę stwierdzić po krótkim przejrzeniu, Python w pigułce nie wspomina o użyciu bez re.compile (), co mnie zainteresowało.
Mat

Obiekt regex dodaje jeszcze jeden obiekt do kontekstu. Jak powiedziałem, istnieje wiele sytuacji, w których re.compile () ma swoje miejsce. Przykład podany przez PO nie jest jednym z nich.
PEZ

1

(miesiące później) łatwo jest dodać własną pamięć podręczną wokół re.match lub cokolwiek innego -

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

Wibni, czy nie byłoby miło, gdyby: cachehint (size =), cacheinfo () -> size, hits, nclear ...


1

Miałem duże doświadczenie w wykonywaniu skompilowanego wyrażenia regularnego 1000 razy w porównaniu do kompilacji w locie i nie zauważyłem żadnej zauważalnej różnicy

Głosy na przyjętą odpowiedź prowadzą do założenia, że ​​to, co mówi @Triptych, jest prawdziwe we wszystkich przypadkach. To niekoniecznie jest prawdą. Jedną dużą różnicą jest to, że musisz zdecydować, czy zaakceptować ciąg wyrażenia regularnego, czy skompilowany obiekt wyrażenia regularnego jako parametr funkcji:

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

Zawsze lepiej jest skompilować wyrażenia regularne na wypadek, gdyby trzeba było z nich skorzystać.

Zwróć uwagę na powyższy przykład symulujący utworzenie skompilowanego obiektu wyrażenia regularnego w czasie importu w porównaniu z „w locie”, gdy jest to wymagane dla dopasowania.


1

Jako alternatywną odpowiedź, ponieważ widzę, że nie zostało to wspomniane wcześniej, zacznę cytować dokumenty Python 3 :

Czy powinieneś skorzystać z tych funkcji na poziomie modułu, czy samemu powinieneś uzyskać wzorzec i wywołać jego metody? Jeśli masz dostęp do wyrażenia regularnego w pętli, jego wstępna kompilacja spowoduje zapisanie kilku wywołań funkcji. Poza pętlami nie ma dużej różnicy dzięki wewnętrznej pamięci podręcznej.


1

Oto przykład, w którym użycie re.compilejest ponad 50 razy szybsze, zgodnie z żądaniem .

Chodzi o to samo, co zrobiłem w powyższym komentarzu, a mianowicie używanie re.compilemoże być znaczącą zaletą, gdy twoje użycie nie jest w stanie wiele skorzystać z pamięci podręcznej kompilacji. Dzieje się tak przynajmniej w jednym konkretnym przypadku (na który wpadłem w praktyce), a mianowicie gdy spełnione są wszystkie poniższe warunki:

  • Masz wiele wzorców wyrażeń regularnych (więcej niż re._MAXCACHE, których domyślne jest 512) i
  • często używasz tych wyrażeń regularnych i
  • kolejne użycia tego samego wzorca są oddzielone więcej niż re._MAXCACHEinnymi wyrażeniami regularnymi pomiędzy nimi, dzięki czemu każdy z nich zostaje opróżniony z pamięci podręcznej między kolejnymi zastosowaniami.
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}')

Przykładowe dane wyjściowe, które otrzymuję na moim laptopie (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

Nie zawracałem sobie głowy, timeitponieważ różnica jest tak wyraźna, ale za każdym razem otrzymuję jakościowo podobne liczby. Zauważ, że nawet bez re.compile, wielokrotne użycie tego samego wyrażenia regularnego i przejście do następnego nie było tak złe (tylko około 2 razy wolniej niż przy re.compile), ale w innej kolejności (zapętlanie wielu wyrażeń regularnych) jest znacznie gorsze , zgodnie z oczekiwaniami. Również zwiększenie rozmiaru pamięci podręcznej działa zbyt: wystarczy ustawienie re._MAXCACHE = len(patterns)w setup()powyżej (oczywiście nie polecam robić takie rzeczy w produkcji jako nazwy podkreślenia są konwencjonalnie „prywatny”) spada ~ 23 sekund z powrotem w dół do ~ 0,7 sekundy, co również odpowiada naszemu zrozumieniu.


PS: jeśli użyję tylko 3 wzorców wyrażeń regularnych w całym kodzie, każdy z nich użyje (bez żadnej konkretnej kolejności) setek razy, pamięć podręczna wyrażeń regularnych automatycznie zachowa wyrażenia regularne, prawda?
Basj

@Basj Myślę, że możesz po prostu spróbować i przekonać się :) Ale jestem pewien, że odpowiedź brzmi tak: jedyny dodatkowy koszt w tym przypadku AFAICT to po prostu sprawdzenie wzorca w pamięci podręcznej . Zauważ też, że pamięć podręczna ma charakter globalny (na poziomie modułu), więc w zasadzie możesz mieć bibliotekę zależności wykonującą wyszukiwania wyrażeń regularnych pomiędzy twoimi, więc trudno być w pełni pewnym, że twój program używa tylko 3 (lub dowolnej liczby) wyrażeń regularnych wzory, ale byłoby dziwnie być inaczej :)
ShreevatsaR

0

Wyrażenia regularne są kompilowane przed użyciem podczas korzystania z drugiej wersji. Jeśli zamierzasz go wykonać wiele razy, zdecydowanie lepiej jest go najpierw skompilować. Jeśli kompilowanie za każdym razem nie jest jednokrotne, jest w porządku.


0

Preferencje czytelności / obciążenia poznawczego

Dla mnie główny zysk jest, że tylko trzeba pamiętać, i czytać, jedna postać skomplikowanej składni regex API - w <compiled_pattern>.method(xxx)formie raczej niż are.func(<pattern>, xxx) formie.

The re.compile(<pattern>) prawda, jest trochę dodatkowej płyty grzewczej.

Ale jeśli chodzi o wyrażenia regularne, ten dodatkowy krok kompilacji raczej nie będzie dużą przyczyną obciążenia poznawczego. W rzeczywistości przy skomplikowanych wzorach możesz nawet uzyskać większą przejrzystość, oddzielając deklarację od dowolnej metody wyrażenia regularnego, którą następnie wywołujesz.

Zazwyczaj stroję skomplikowane wzorce na stronie internetowej takiej jak Regex101, a nawet w osobnym minimalnym skrypcie testowym, a następnie umieszczam je w moim kodzie, więc oddzielenie deklaracji od jej użycia również pasuje do mojego przepływu pracy.


-1

Chciałbym uzasadnić, że kompilacja wstępna jest korzystna zarówno pod względem koncepcyjnym, jak i „dosłownym” (jak w „programowaniu literackim”). spójrz na ten fragment kodu:

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

we wniosku piszesz:

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

jest to tak proste pod względem funkcjonalności, jak to tylko możliwe. ponieważ ten przykład jest tak krótki, połączyłem sposób, aby uzyskać _text_has_foobar_re_searchwszystko w jednym wierszu. wadą tego kodu jest to, że zajmuje on trochę pamięci przez cały okres istnienia TYPOobiektu biblioteki; zaletą jest to, że podczas wyszukiwania foobar unikasz dwóch wywołań funkcji i dwóch wyszukiwań słownika klas. ile wyrażeń regularnych jest buforowanychre a narzut tej pamięci podręcznej nie ma tutaj znaczenia.

porównaj to z bardziej typowym stylem poniżej:

import re

class Typo:

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

W aplikacji:

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

Przyznaję, że mój styl jest bardzo nietypowy dla Pythona, może nawet dyskusyjny. jednak w przykładzie, który ściślej pasuje do tego, w jaki sposób najczęściej używany jest Python, aby wykonać pojedyncze dopasowanie, musimy utworzyć instancję obiektu, wykonać trzy wyszukiwania słownika instancji i wykonać trzy wywołania funkcji; dodatkowo możemy się w to wciągnąćre problemy z buforowaniem przy użyciu ponad 100 wyrażeń regularnych. Wyrażenie regularne ukrywa się również w ciele metody, co przez większość czasu nie jest tak dobrym pomysłem.

powiedzmy, że każdy podzbiór środków --- ukierunkowane, aliasy wyciągów z importu; metody aliasy, w stosownych przypadkach; redukcja wywołań funkcji i wyszukiwania słowników obiektów --- może pomóc zmniejszyć złożoność obliczeniową i koncepcyjną.


2
WTF. Nie tylko wykopałeś stare, odpowiedzi na pytanie. Twój kod nie jest również idiomatyczny i jest niepoprawny na tak wielu poziomach - (ab) używanie klas jako przestrzeni nazw, w których moduł jest wystarczający, pisanie wielkich liter w nazwach klas itp. Zobacz pastebin.com/iTAXAWen, aby uzyskać lepsze implementacje. Nie wspominając o tym, że regex, którego używasz, jest również uszkodzony. Ogólnie -1

2
winny. to stare pytanie, ale nie mam nic przeciwko byciu # 100 w spowolnionej rozmowie. pytanie nie zostało zamknięte. ostrzegłem, że mój kod może być przeciwnikiem do niektórych gustów. myślę, że gdybyś mógł to postrzegać jako zwykłą demonstrację tego, co jest wykonalne w Pythonie, na przykład: jeśli weźmiemy wszystko, wszystko w co wierzymy, jako opcjonalne, a następnie majstrujemy razem w jakikolwiek sposób, jak wyglądają te rzeczy, możemy dostać? jestem pewien, że możesz dostrzec zalety i wady tego rozwiązania i możesz narzekać bardziej wyartykułowane. w przeciwnym razie muszę stwierdzić, że twoje twierdzenie o błędzie opiera się na niewiele więcej niż PEP008
przepływ

2
Nie, nie chodzi o PEP8. To tylko konwencje nazewnictwa i nigdy nie głosowałbym za ich nieprzestrzeganiem. Głosowałem za tobą, ponieważ kod, który pokazałeś, jest po prostu źle napisany. Bez żadnego powodu przeciwstawia się konwencjom i idiomom i jest wcieleniem optymalizacji permature: musiałbyś zoptymalizować żywe światło dzienne ze wszystkich innych kodów, aby stało się to wąskim gardłem, a nawet wtedy trzeci przepisany przeze mnie przepis jest krótszy, więcej idiomatyczny i równie szybki według twojego rozumowania (ta sama liczba dostępu do atrybutów).

„źle napisane” - na przykład dlaczego dokładnie? „przeciwstawia się konwencjom i idiomom” - ostrzegałem cię. „bez powodu” - tak, mam powód: uproszczenie tam, gdzie złożoność nie służy żadnemu celowi; „wcielenie przedwczesnej optymalizacji” - jestem zwolennikiem stylu programowania, który wybiera równowagę czytelności i wydajności; OP poprosił o wywołanie „korzyści z używania re.compile”, co rozumiem jako pytanie o wydajność. „(ab) używanie klas jako przestrzeni nazw” - to twoje obelżywe słowa. klasa jest tam, więc masz punkt odniesienia dla siebie. próbowałem użyć modułów do tego celu, klasy działają lepiej.
przepływ

„wielkie litery klas”, „Nie, nie chodzi o PEP8” - najwyraźniej jesteś tak oburzająco zły, że nie możesz nawet powiedzieć o co się najpierw sprzeczać. „WTF”, „ źle ” - widzisz, jaki jesteś emocjonalny? proszę więcej obiektywizmu i mniej piany.
flow

-5

Rozumiem, że te dwa przykłady są faktycznie równoważne. Jedyna różnica polega na tym, że w pierwszym przypadku można ponownie użyć skompilowanego wyrażenia regularnego w innym miejscu, nie powodując jego ponownej kompilacji.

Oto referencja dla Ciebie: http://diveintopython3.ep.io/refactoring.html

Wywołanie funkcji wyszukiwania skompilowanego obiektu wzorca za pomocą łańcucha „M” osiąga to samo, co wywołanie funkcji re.search zarówno za pomocą wyrażenia regularnego, jak i łańcucha „M”. Tylko o wiele, znacznie szybciej. (W rzeczywistości funkcja re.search po prostu kompiluje wyrażenie regularne i wywołuje dla ciebie metodę wyszukiwania wynikowego obiektu wzorca).


1
nie przegłosowałem, ale technicznie to źle: Python i tak się nie skompiluje
Tryptyk
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.