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 isnotNone:return p
# ...# Does actual compilation on cache miss# ...# Caches compiled regexif 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.
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.
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
Python3.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.
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 in1101001000100001000001000000;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 in1101001000100001000001000000;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 .
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 pythonimport 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"@timeddef noncompiled():
a =0for x in xrange(1000000):
m = re.match(regularExpression, testString)
a += int(m.group(1))return a
@timeddef compiled():
a =0
rgx = re.compile(regularExpression)for x in xrange(1000000):
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef 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
@timeddef compiledInLoop():
a =0for x in xrange(1000000):
rgx = re.compile(regularExpression)
m = rgx.match(testString)
a += int(m.group(1))return a
@timeddef reallyCompiledInLoop():
a =0for 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.
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ę):
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).
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).
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.
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.
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):
Jeśli możemy buforować odwołanie do obiektu wyrażenia regularnego w klasie.
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:
Nie możemy buforować referencji.
Używamy go tylko raz na jakiś czas.
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)
Ponadto re.compile () omija dodatkową logikę pośrednictwa i buforowania:
_cache ={}
_pattern_type = type(sre_compile.compile("",0))
_MAXCACHE =512def _compile(pattern, flags):# internal: compile patterntry:
p, loc = _cache[type(pattern), pattern, flags]if loc isNoneor loc == _locale.setlocale(_locale.LC_CTYPE):return p
exceptKeyError:passif isinstance(pattern, _pattern_type):if flags:raiseValueError("cannot process flags argument with a compiled pattern")return pattern
ifnot sre_compile.isstring(pattern):raiseTypeError("first argument must be string or compiled pattern")
p = sre_compile.compile(pattern, flags)ifnot(flags & DEBUG):if len(_cache)>= _MAXCACHE:
_cache.clear()if p.flags & LOCALE:ifnot _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:
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:
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.
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.
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.
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.
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.
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.
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.
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.
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 +=1print 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
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=1000TestString=""for i in range(1000):TestString+="abababdedfg"StartTime= time.time()for i in range(TimesToDo):Regex1.search(TestString)Seconds= time.time()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):Regex2.search(TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.299 seconds
CharacterClass takes 0.107 seconds
Uruchom 2 - Nie używa re.compile
import re
import time
import fpformat
TimesToDo=1000TestString=""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()-StartTimeprint"Alternation takes "+ fpformat.fix(Seconds,3)+" seconds"StartTime= time.time()for i in range(TimesToDo):
re.search('^[a-g]+$',TestString)Seconds= time.time()-StartTimeprint"Character Class takes "+ fpformat.fix(Seconds,3)+" seconds"Alternation takes 2.508 seconds
CharacterClass takes 0.109 seconds
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.
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',]}
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:
Wysiłki procesora
Złożoność czasu.
Sprawia, że regex Universal. (Może być użyty do szukania, wyszukiwania, dopasowania)
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")
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 -
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.
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.
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 =0for 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 =0for 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 =0for 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.
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.
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_Reclass TYPO:def text_has_foobar( self, text ):return self._text_has_foobar_re_search( text )isnotNone
_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
classTypo:def text_has_foobar( self, text ):return re.compile( r"""(?i)foobar""").search( text )isnotNone
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ą.
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.
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).
re.sub
nie przyjmie argumentu flagi ...re.compile
dał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.in
test 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
Odpowiedzi:
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 nadict
typie wewnętrznym ).Z modułu re.py (komentarze są moje):
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.
źródło
Dla mnie największą korzyścią
re.compile
jest 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:
W porównaniu z kompilacją:
Chociaż jest dość blisko, ostatnia linia drugiego wydaje się bardziej naturalna i prostsza, gdy jest używana wielokrotnie.
źródło
us_phone_number
lubsocial_security_number
itp.FWIW:
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.compile
jeś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:
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żnyre.compile(x).match(...)
, tj. Nie wydaje się, aby buforowanie skompilowanej reprezentacji było za kulisami.źródło
import re
należy usunąć z konfiguracji? Chodzi o to, gdzie chcesz zmierzyć. Gdybym uruchomił skrypt Pythona wiele razy, miałby toimport re
czas. 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 terminuh=re.compile('hello')
pomogłoby wyjaśnić.Oto prosty przypadek testowy:
z re.compile:
Wydaje się, że kompilacja jest szybsza w tym prostym przypadku, nawet jeśli dopasujesz tylko raz .
źródło
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życie
re
metod.Jak zauważyli inni,
re
metody (w tymre.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:
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.
źródło
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 funkcjamire.py
zamiast 3.Uruchamianie tego fragmentu kodu:
jest taki sam jak uruchomienie tego kodu:
Ponieważ, gdy spojrzymy na źródło
re.py
, (A + B) oznacza:a (C) to w rzeczywistości:
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.py
udowodnił nam to.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.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ę):Połączenia A raz wykona jedno wyszukiwanie,
_cache
a 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).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).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.
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.
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):
Sprawa, że (C) jest wystarczająco dobry:
Podsumowując, oto ABC:
Dziękuje za przeczytanie.
źródło
Przeważnie nie ma różnicy, czy używasz re.compile, czy nie. Wewnętrznie wszystkie funkcje są realizowane w ramach kroku kompilacji:
Ponadto re.compile () omija dodatkową logikę pośrednictwa i buforowania:
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:
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:
Powyższy demontaż pochodzi z pliku PYC
tmp.py
zawierającego:źródło
"
wdef search(pattern, string, flags=0):"
literówka?pattern
jest już skompilowanym wzorcem, narzut buforowania staje się znaczący: haszowanie aSRE_Pattern
jest kosztowne, a wzorzec nigdy nie jest zapisywany w pamięci podręcznej, więc wyszukiwanie kończy się niepowodzeniem za pomocą aKeyError
.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.I
podczas kompilowania wzorców, niż używać flag inline.vs
źródło
re.findall
każdym razie możesz użyć flag jako trzeciego argumentu .Korzystając z podanych przykładów:
Metoda dopasowania w powyższym przykładzie nie jest taka sama, jak zastosowana poniżej:
Funkcja re.compile () zwraca obiekt wyrażenia regularnego , co oznacza, że
h
jest obiektem wyrażenia regularnego.Obiekt regex ma własną metodę dopasowania z opcjonalnymi parametrami pos i endpos :
regex.match(string[, pos[, endpos]])
poz
końcówki
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
match.endpos
Obiekt wyrażenia regularnego ma dwa unikalne, być może przydatne, atrybuty:
regex.groups
regex.groupindex
I wreszcie obiekt dopasowania ma ten atrybut:
match.re
źródło
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:
a później skorzystam z porównania
gdzie
patternPhrases
powinna być zmienna zawierająca ciąg wyrażeń regularnych,x[columnIndex]
jest zmienną zawierającą ciąg znaków.Miałem problemy, które
patternPhrases
nie pasowały do oczekiwanego ciągu!Ale jeśli użyłem formularza re.compile:
potem w
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łoW 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.
źródło
Istnieje jeden dodatkowy dodatek za pomocą re.compile (), w postaci dodawania komentarzy do moich wzorców wyrażeń regularnych za pomocą 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.
źródło
re.VERBOSE
warto 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.Zgodnie z dokumentacją w języku Python :
Sekwencja
jest równa
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.
źródło
Co ciekawe, kompilacja okazuje się dla mnie bardziej wydajna (Python 2.5.2 na Win XP):
Uruchomienie powyższego kodu raz tak, jak jest, a raz z
if
komentarzem dwóch wierszy na odwrót, skompilowane wyrażenie regularne jest dwa razy szybszeźródło
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
Uruchom 2 - Nie używa re.compile
źródło
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.
źródło
Oprócz wydajności.
Używanie
compile
pomaga mi rozróżnić pojęcia1. modułu (re) ,
2. obiektu regex
3. dopasowania obiektu
Kiedy zacząłem uczyć się regex
Jako uzupełnienie, stworzyłem wyczerpującą ściągę modułu
re
dla twojego odniesienia.źródło
Naprawdę szanuję wszystkie powyższe odpowiedzi. Moim zdaniem Tak! Na pewno warto używać re.compile zamiast kompilować regex za każdym razem.
Przykład:
Używanie w Findall
Używanie w wyszukiwaniu
źródło
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.
źródło
(miesiące później) łatwo jest dodać własną pamięć podręczną wokół re.match lub cokolwiek innego -
Wibni, czy nie byłoby miło, gdyby: cachehint (size =), cacheinfo () -> size, hits, nclear ...
źródło
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:
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.
źródło
Jako alternatywną odpowiedź, ponieważ widzę, że nie zostało to wspomniane wcześniej, zacznę cytować dokumenty Python 3 :
źródło
Oto przykład, w którym użycie
re.compile
jest ponad 50 razy szybsze, zgodnie z żądaniem .Chodzi o to samo, co zrobiłem w powyższym komentarzu, a mianowicie używanie
re.compile
moż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:re._MAXCACHE
, których domyślne jest 512) ire._MAXCACHE
innymi 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.Przykładowe dane wyjściowe, które otrzymuję na moim laptopie (Python 3.7.7):
Nie zawracałem sobie głowy,
timeit
ponieważ różnica jest tak wyraźna, ale za każdym razem otrzymuję jakościowo podobne liczby. Zauważ, że nawet bezre.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ż przyre.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 ustawieniere._MAXCACHE = len(patterns)
wsetup()
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.źródło
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.
źródło
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.
źródło
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:
we wniosku piszesz:
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_search
wszystko w jednym wierszu. wadą tego kodu jest to, że zajmuje on trochę pamięci przez cały okres istnieniaTYPO
obiektu 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:
W aplikacji:
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ą.
źródło
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
źródło