Có đáng để sử dụng re.compile của Python không?


461

Có bất kỳ lợi ích nào trong việc sử dụng biên dịch cho các biểu thức thông thường trong Python không?

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

đấu với

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

8
Khác với thực tế là trong 2.6 re.subsẽ không tranh luận về cờ ...
new123456

58
Tôi vừa gặp phải trường hợp sử dụng re.compileđã cải thiện 10-50 lần. Đạo đức là nếu bạn có nhiều regex (hơn MAXCACHE = 100) bạn sử dụng chúng rất nhiều lần mỗi lần (và cách nhau nhiều hơn MAXCACHE regexes ở giữa, để mỗi cái được xóa khỏi bộ đệm: vì vậy sử dụng cùng một lần rất nhiều lần và sau đó chuyển sang lần tiếp theo không được tính), sau đó chắc chắn sẽ giúp biên dịch chúng. Mặt khác, nó không tạo ra sự khác biệt.
ShreevatsaR

8
Một điều nhỏ cần lưu ý là đối với các chuỗi không cần regex, inkiểm tra chuỗi con chuỗi nhanh hơn NHIỀU:>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

@ShreevatsaR Thú vị! Bạn có thể đăng câu trả lời với một ví dụ cho thấy sự cải thiện gấp 10 lần không? Hầu hết các câu trả lời được đưa ra ở đây thực sự cho thấy sự cải thiện gấp 3 lần trong một số trường hợp chính xác và trong các trường hợp khác gần như không cải thiện.
Basj

1
@Basj Xong, đăng câu trả lời . Tôi đã không bận tâm đến việc khai thác những gì tôi đã sử dụng Python vào tháng 12 năm 2013, nhưng điều đơn giản đầu tiên tôi đã thử cho thấy hành vi tương tự.
ShreevatsaR

Câu trả lời:


436

Tôi đã có nhiều kinh nghiệm khi chạy regex được biên dịch 1000 lần so với biên dịch nhanh chóng và không nhận thấy bất kỳ sự khác biệt nào có thể nhận thấy. Rõ ràng, đây là giai thoại, và chắc chắn không phải là một cuộc tranh luận tuyệt vời chống lại việc biên dịch, nhưng tôi đã thấy sự khác biệt là không đáng kể.

EDIT: Sau khi lướt qua mã thư viện Python 2.5 thực tế, tôi thấy rằng Python biên dịch nội bộ VÀ CACHES regex bất cứ khi nào bạn sử dụng chúng (bao gồm cả các lệnh gọi re.match()), vì vậy bạn thực sự chỉ thay đổi KHI regex được biên dịch và không nên ' Không tiết kiệm được nhiều thời gian - chỉ mất thời gian để kiểm tra bộ đệm (tra cứu khóa trên một dictloại nội bộ ).

Từ mô-đun re.py (ý kiến ​​là của tôi):

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

Tôi vẫn thường biên dịch trước các biểu thức chính quy, nhưng chỉ để ràng buộc chúng với một tên hay, có thể sử dụng lại, không cho bất kỳ hiệu suất mong đợi nào.


12
Kết luận của bạn không phù hợp với câu trả lời của bạn. Nếu regex được biên dịch và lưu trữ tự động, hầu hết các trường hợp không cần phải làm bằng tay.
jfs

84
JF Sebastian, nó đóng vai trò như một tín hiệu cho lập trình viên rằng regrec trong câu hỏi sẽ được sử dụng rất nhiều và không có nghĩa là một sự vứt bỏ.
kaleissin

40
Hơn thế nữa, tôi muốn nói rằng nếu bạn không muốn bị ảnh hưởng của trình biên dịch & bộ nhớ cache ở một số phần quan trọng về hiệu năng của ứng dụng, thì tốt nhất bạn nên biên dịch chúng trước khi sử dụng một phần không quan trọng trong ứng dụng của bạn .
Eddie Parker

20
Tôi thấy lợi thế chính cho việc sử dụng regex đã biên dịch nếu bạn sử dụng lại cùng một regex nhiều lần, do đó làm giảm khả năng mắc lỗi chính tả. Nếu bạn chỉ gọi nó một lần thì không biên dịch sẽ dễ đọc hơn.
monkut

18
Vì vậy, sự khác biệt chính sẽ là khi bạn đang sử dụng nhiều regex khác nhau (nhiều hơn _MAXCACHE), một số trong số chúng chỉ một lần và một số lần khác ... thì điều quan trọng là giữ các biểu thức đã biên dịch của bạn cho những biểu thức được sử dụng nhiều hơn để chúng được sử dụng nhiều hơn Sẽ không bị xóa khỏi bộ đệm khi nó đầy.
fortran

133

Đối với tôi, lợi ích lớn nhất re.compilelà có thể tách định nghĩa của regex khỏi việc sử dụng nó.

Ngay cả một biểu thức đơn giản như 0|[1-9][0-9]*(số nguyên trong cơ sở 10 không có số 0 đứng đầu) có thể đủ phức tạp để bạn không phải gõ lại nó, kiểm tra xem bạn có mắc lỗi chính tả nào không và sau đó phải kiểm tra lại nếu có lỗi chính tả khi bạn bắt đầu gỡ lỗi . Thêm vào đó, nó tốt hơn để sử dụng một tên biến như num hoặc num_b10 hơn 0|[1-9][0-9]*.

Chắc chắn có thể lưu trữ các chuỗi và chuyển chúng để re.match; tuy nhiên, điều đó ít đọc hơn :

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

Biên dịch Versus:

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

Mặc dù nó khá gần, dòng cuối cùng của thứ hai cảm thấy tự nhiên và đơn giản hơn khi được sử dụng nhiều lần.


5
Tôi đồng ý với câu trả lời này; thông thường sử dụng kết quả re.compile trong mã nhiều hơn, không dễ đọc hơn.
Carl Meyer

1
Tuy nhiên, đôi khi điều ngược lại là đúng - ví dụ: nếu bạn xác định regex ở một nơi và sử dụng các nhóm khớp của nó ở một nơi xa khác.
Ken Williams

1
@KenWilliams Không nhất thiết, một regex được đặt tên tốt cho một mục đích cụ thể phải rõ ràng ngay cả khi được sử dụng cách xa định nghĩa ban đầu. Ví dụ us_phone_numberhoặc social_security_numbervv
Brian M. Sheldon

2
@ BrianM.Sheldon đặt tên cho regex tốt không thực sự giúp bạn biết những nhóm bắt giữ khác nhau của nó đại diện cho cái gì.
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

vì vậy, nếu bạn sẽ sử dụng cùng một regex rất nhiều, nó có thể đáng để làm re.compile(đặc biệt đối với các regex phức tạp hơn).

Các đối số tiêu chuẩn chống lại tối ưu hóa sớm được áp dụng, nhưng tôi không nghĩ rằng bạn thực sự mất nhiều sự rõ ràng / thẳng thắn bằng cách sử dụng re.compilenếu bạn nghi ngờ rằng biểu thức chính của bạn có thể trở thành nút cổ chai hiệu năng.

Cập nhật:

Trong Python 3.6 (Tôi nghi ngờ các thời gian trên đã được thực hiện bằng phần cứng Python 2.x) và 2018 (MacBook Pro), bây giờ tôi nhận được các thời gian sau:

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

Tôi cũng đã thêm một trường hợp (chú ý sự khác biệt của dấu ngoặc kép giữa hai lần chạy trước) cho thấy điều đó re.match(x, ...)có nghĩa đen [đại khái] tương đương với re.compile(x).match(...), tức là không có bộ nhớ đệm phía sau hậu trường của đại diện được biên dịch dường như xảy ra.


5
Các vấn đề chính với phương pháp luận của bạn ở đây, vì đối số thiết lập KHÔNG bao gồm trong thời gian. Vì vậy, bạn đã loại bỏ thời gian biên dịch từ ví dụ thứ hai và chỉ lấy trung bình trong ví dụ đầu tiên. Điều này không có nghĩa là ví dụ đầu tiên biên dịch mỗi lần.
Triptych

1
Vâng, tôi đồng ý rằng đây không phải là một so sánh công bằng của hai trường hợp.
Kiv

7
Tôi hiểu ý của bạn, nhưng đó không phải là chính xác những gì sẽ xảy ra trong một ứng dụng thực tế, nơi regrec được sử dụng nhiều lần?
dF.

26
@Triptych, @Kiv: Toàn bộ điểm biên dịch regexps tách biệt với việc sử dụng để giảm thiểu biên dịch; loại bỏ nó khỏi thời gian là chính xác những gì dF nên làm, bởi vì nó đại diện cho việc sử dụng trong thế giới thực một cách chính xác nhất. Thời gian biên dịch đặc biệt không liên quan với cách tính thời gian của nó tại đây; nó thực hiện một số lần chạy và chỉ báo cáo lần chạy ngắn nhất, tại thời điểm regrec được biên dịch được lưu trữ. Chi phí bổ sung mà bạn thấy ở đây không phải là chi phí biên dịch regrec, mà là chi phí tìm kiếm trong bộ đệm regrec được biên dịch (một từ điển).
jemfinch

3
@Triptych Có nên import rechuyển ra khỏi thiết lập? Đó là tất cả về nơi bạn muốn đo. Nếu tôi chạy một kịch bản python nhiều lần, nó sẽ có import rethời gian. Khi so sánh hai điều quan trọng là phải tách hai dòng cho thời gian. Có như bạn nói đó là khi bạn sẽ có thời gian. So sánh cho thấy rằng bạn mất thời gian nhấn một lần và lặp lại thời gian ít hơn bằng cách biên dịch hoặc bạn thực hiện lần truy cập mỗi lần giả sử bộ đệm bị xóa giữa các cuộc gọi, điều này đã được chỉ ra có thể xảy ra. Thêm một thời gian h=re.compile('hello')sẽ giúp làm rõ.
Tom Mydd ERICn

39

Đây là một trường hợp thử nghiệm đơn giản:

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

với 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

Vì vậy, nó dường như biên dịch nhanh hơn với trường hợp đơn giản này, ngay cả khi bạn chỉ khớp một lần .


2
Phiên bản Python nào đây?
Kyle Strand

2
điều đó không thực sự quan trọng, vấn đề là hãy thử điểm chuẩn trong môi trường nơi bạn sẽ chạy mã
vua david

1
Đối với tôi hiệu suất gần như giống hệt nhau cho 1000 vòng lặp trở lên. Phiên bản được biên dịch nhanh hơn cho 1-100 vòng. (Trên cả hai con trăn 2.7 và 3.4).
Zitrax

2
Trên thiết lập Python 2.7.3 của tôi hầu như không có sự khác biệt. Đôi khi biên dịch nhanh hơn, đôi khi ist chậm hơn. Sự khác biệt luôn luôn <5%, vì vậy tôi tính sự khác biệt là đo lường độ không chắc chắn, vì thiết bị chỉ có một CPU.
Dakkaron

1
Trong Python 3.4.3 được thấy trong hai lần chạy riêng biệt: sử dụng được biên dịch thậm chí còn chậm hơn so với không được biên dịch.
Zelphir Kaltstahl

17

Tôi chỉ thử bản thân mình. Đối với trường hợp đơn giản là phân tích một số ra khỏi chuỗi và tính tổng nó, sử dụng một đối tượng biểu thức chính quy được biên dịch nhanh gấp khoảng hai lần so với sử dụng các rephương thức.

Như những người khác đã chỉ ra, các rephương thức (bao gồm re.compile) tra cứu chuỗi biểu thức chính quy trong bộ đệm của các biểu thức được biên dịch trước đó. Do đó, trong trường hợp bình thường, chi phí thêm khi sử dụng các rephương thức chỉ đơn giản là chi phí tra cứu bộ đệm.

Tuy nhiên, kiểm tra , cho thấy bộ đệm được giới hạn ở 100 biểu thức. Điều này đặt ra câu hỏi, làm thế nào là đau đớn để tràn bộ nhớ cache? Mã này chứa một giao diện nội bộ cho trình biên dịch biểu thức chính quy , re.sre_compile.compile. Nếu chúng ta gọi nó, chúng ta bỏ qua bộ đệm. Hóa ra là chậm hơn khoảng hai bậc cho một biểu thức chính quy cơ bản, chẳng hạn như r'\w+\s+([0-9_]+)\s+\w*'.

Đây là bài kiểm tra của tôi:

#!/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

Các phương thức 'reallyCompiled' sử dụng giao diện bên trong, bỏ qua bộ đệm. Lưu ý cái được biên dịch trên mỗi lần lặp lặp chỉ lặp lại 10.000 lần chứ không phải một triệu.


Tôi đồng ý với bạn rằng regexes biên dịch chạy nhanh hơn nhiều so với không biên dịch. Tôi đã chạy hơn 10.000 câu và tạo một vòng lặp trong đó để lặp lại cho các biểu thức khi các biểu thức không được biên dịch và được tính mỗi lần dự đoán của một lần chạy hoàn chỉnh là 8 giờ, sau khi tạo một từ điển theo chỉ mục với các mẫu biểu thức chính được biên dịch mà tôi chạy toàn bộ trong 2 phút. Tôi không thể hiểu câu trả lời ở trên ...
Eli Borodach

12

Tôi đồng ý với Honest Abe rằng match(...)trong các ví dụ đã cho là khác nhau. Chúng không phải là một so sánh một-một và do đó, kết quả là khác nhau. Để đơn giản hóa câu trả lời của tôi, tôi sử dụng A, B, C, D cho các chức năng được đề cập. Ồ vâng, chúng tôi đang xử lý 4 chức năng re.pythay vì 3.

Chạy đoạn mã này:

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

giống như chạy mã này:

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

Bởi vì, khi nhìn vào nguồn re.py, (A + B) có nghĩa là:

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

và (C) thực sự là:

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

Vì vậy, (C) không giống với (B). Trong thực tế, (C) gọi (B) sau khi gọi (D) cũng được gọi bởi (A). Nói cách khác , (C) = (A) + (B). Do đó, so sánh (A + B) bên trong một vòng lặp có kết quả tương tự như (C) bên trong một vòng lặp.

George regexTest.pyđã chứng minh điều này cho chúng tôi.

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

Sự quan tâm của mọi người là, làm thế nào để có được kết quả 2,323 giây. Để đảm bảo compile(...)chỉ được gọi một lần, chúng ta cần lưu trữ đối tượng regex đã biên dịch trong bộ nhớ. Nếu chúng ta đang sử dụng một lớp, chúng ta có thể lưu trữ đối tượng và sử dụng lại mỗi khi hàm của chúng ta được gọi.

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

Nếu chúng tôi không sử dụng lớp (đó là yêu cầu của tôi ngày hôm nay), thì tôi không có bình luận nào. Tôi vẫn đang học cách sử dụng biến toàn cục trong Python và tôi biết biến toàn cục là một điều xấu.

Một điểm nữa, tôi tin rằng việc sử dụng (A) + (B)phương pháp tiếp cận có ưu thế hơn. Dưới đây là một số sự thật như tôi đã quan sát (vui lòng sửa lại cho tôi nếu tôi sai):

  1. Gọi A một lần, nó sẽ thực hiện một tìm kiếm _cachetheo sau sre_compile.compile()để tạo một đối tượng regex. Gọi A hai lần, nó sẽ thực hiện hai lần tìm kiếm và một lần biên dịch (vì đối tượng regex được lưu trữ).

  2. Nếu _cachenhận được ở giữa, thì đối tượng regex được giải phóng khỏi bộ nhớ và Python cần phải biên dịch lại. (ai đó đề nghị Python sẽ không biên dịch lại.)

  3. Nếu chúng ta giữ đối tượng regex bằng cách sử dụng (A), đối tượng regex vẫn sẽ vào _cache và bị xóa bằng cách nào đó. Nhưng mã của chúng tôi giữ một tham chiếu về nó và đối tượng regex sẽ không được giải phóng khỏi bộ nhớ. Những cái đó, Python không cần phải biên dịch lại.

  4. Sự khác biệt 2 giây trong bài kiểm tra của George được biên dịchInLoop so với biên dịch chủ yếu là thời gian cần thiết để xây dựng khóa và tìm kiếm _cache. Nó không có nghĩa là thời gian biên dịch của regex.

  5. Thử nghiệm thực sự của George cho thấy điều gì xảy ra nếu nó thực sự làm lại việc biên dịch mỗi lần: nó sẽ chậm hơn 100 lần (ông đã giảm vòng lặp từ 1.000.000 xuống còn 10.000).

Dưới đây là các trường hợp duy nhất (A + B) tốt hơn (C):

  1. Nếu chúng ta có thể lưu trữ một tham chiếu của đối tượng regex bên trong một lớp.
  2. Nếu chúng ta cần gọi (B) nhiều lần (bên trong một vòng lặp hoặc nhiều lần), chúng ta phải lưu trữ tham chiếu đến đối tượng regex bên ngoài vòng lặp.

Trường hợp (C) là đủ tốt:

  1. Chúng tôi không thể lưu trữ một tài liệu tham khảo.
  2. Chúng tôi chỉ sử dụng nó một lần trong một thời gian.
  3. Nhìn chung, chúng ta không có quá nhiều regex (giả sử cái đã biên dịch không bao giờ bị xóa)

Chỉ là một bản tóm tắt, đây là ABC:

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

Cảm ơn vì đã đọc.


8

Hầu hết, có rất ít sự khác biệt cho dù bạn có sử dụng re.compile hay không. Trong nội bộ, tất cả các chức năng được thực hiện theo các bước biên dịch:

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)

Ngoài ra, re.compile () bỏ qua logic bổ sung và bộ đệm ẩn:

_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

Ngoài lợi ích tốc độ nhỏ từ việc sử dụng re.compile , mọi người cũng thích khả năng đọc đến từ việc đặt tên các đặc tả mẫu có khả năng phức tạp và tách chúng khỏi logic nghiệp vụ được áp dụng:

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

Lưu ý, một người trả lời khác tin rằng các tệp pyc được lưu trữ các mẫu được biên dịch trực tiếp; tuy nhiên, trong thực tế, chúng được xây dựng lại mỗi lần khi PYC được tải:

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

Việc tháo gỡ ở trên xuất phát từ tệp PYC để tmp.pychứa:

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

1
"trong def search(pattern, string, flags=0):"một lỗi đánh máy?
phuclv

1
Lưu ý rằng nếu patternđã là một mẫu được biên dịch, chi phí lưu trữ bộ đệm sẽ trở nên quan trọng: băm a SRE_Patternrất tốn kém và mẫu không bao giờ được ghi vào bộ đệm, do đó việc tra cứu thất bại mỗi lần với a KeyError.
Eric Duminil

5

Nói chung, tôi thấy việc sử dụng cờ dễ dàng hơn (ít nhất là dễ nhớ hơn), như re.Ikhi biên dịch các mẫu hơn là sử dụng cờ nội tuyến.

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

đấu với

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

Dù sao thì bạn cũng có thể sử dụng cờ làm đối số thứ ba re.findall.
aderchox

5

Sử dụng các ví dụ đã cho:

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

Các trận đấu phương pháp trong ví dụ trên là không giống như một trong những sử dụng dưới đây:

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

re.compile () trả về một đối tượng biểu thức chính quy , có nghĩa hlà một đối tượng regex.

Đối tượng regex có phương thức khớp riêng với các tham số posendpose tùy chọn :

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

đặt ra

Tham số thứ hai tùy chọn pos cung cấp một chỉ mục trong chuỗi nơi bắt đầu tìm kiếm; nó mặc định là 0. Điều này không hoàn toàn tương đương với việc cắt chuỗi; các '^'nhân vật mô hình phù hợp với lúc bắt đầu thực sự của chuỗi và tại các vị trí ngay sau khi một dòng mới, nhưng không nhất thiết lúc chỉ số cho tìm kiếm để bắt đầu.

mục đích

Tham số tùy chọn endpose giới hạn khoảng cách chuỗi sẽ được tìm kiếm; nó sẽ như thể chuỗi dài các ký tự endpose , vì vậy chỉ các ký tự từ pos đến endpos - 1sẽ được tìm kiếm cho khớp. Nếu endpose nhỏ hơn pos , sẽ không tìm thấy kết quả khớp nào; mặt khác, nếu rx là một đối tượng biểu thức chính quy được biên dịch, rx.search(string, 0, 50)thì tương đương với rx.search(string[:50], 0).

Các phương thức tìm kiếm , findallfinditer của đối tượng regex cũng hỗ trợ các tham số này.

re.match(pattern, string, flags=0)không hỗ trợ họ như bạn có thể nhìn thấy,
cũng không nó tìm kiếm , findall , và finditer đối tác.

Một đối tượng khớp có các thuộc tính bổ sung cho các tham số này:

trận đấu

Giá trị của pos được truyền cho phương thức search () hoặc match () của đối tượng regex. Đây là chỉ mục vào chuỗi mà công cụ RE bắt đầu tìm kiếm kết quả khớp.

trận đấu

Giá trị của endpose được truyền cho phương thức search () hoặc match () của đối tượng regex. Đây là chỉ mục vào chuỗi mà ngoài đó công cụ RE sẽ không đi.


Một đối tượng regex có hai thuộc tính duy nhất, có thể hữu ích:

regex.groups

Số lượng các nhóm chụp trong mẫu.

regex.groupindex

Một từ điển ánh xạ bất kỳ tên nhóm tượng trưng nào được xác định bởi (? P) thành số nhóm. Từ điển trống nếu không có nhóm biểu tượng được sử dụng trong mẫu.


Và cuối cùng, một đối tượng khớp có thuộc tính này:

trận đấu

Đối tượng biểu thức chính quy có phương thức match () hoặc search () tạo ra thể hiện khớp này.


4

Hiệu suất khác biệt sang một bên, sử dụng re.compile và sử dụng đối tượng biểu thức chính quy được biên dịch để khớp (bất kỳ hoạt động liên quan đến biểu thức chính quy nào) làm cho ngữ nghĩa rõ ràng hơn với thời gian chạy Python.

Tôi đã có một số kinh nghiệm đau đớn về việc gỡ lỗi một số mã đơn giản:

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

và sau này tôi sẽ sử dụng so sánh trong

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

trong đó patternPhrasesđược coi là một biến chứa chuỗi biểu thức chính quy, x[columnIndex]là một biến chứa chuỗi.

Tôi gặp sự cố patternPhraseskhông khớp với chuỗi dự kiến!

Nhưng nếu tôi đã sử dụng mẫu re.compile:

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

sau đó trong

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

Python sẽ phàn nàn rằng "chuỗi không có thuộc tính khớp", do ánh xạ đối số vị trí trong compare, x[columnIndex]được sử dụng như biểu thức chính quy!, Khi tôi thực sự ngụ ý

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

Trong trường hợp của tôi, sử dụng re.compile rõ ràng hơn về mục đích của biểu thức chính quy, khi giá trị của nó được ẩn bằng mắt thường, do đó tôi có thể nhận thêm trợ giúp từ việc kiểm tra thời gian chạy của Python.

Vì vậy, đạo đức của bài học của tôi là khi biểu thức chính quy không chỉ là chuỗi ký tự, thì tôi nên sử dụng re.compile để cho Python giúp tôi khẳng định giả định của mình.


4

Có một lợi ích bổ sung khi sử dụng re.compile (), dưới dạng thêm nhận xét vào các mẫu biểu thức chính quy của tôi bằng cách sử dụng re.VERBOSE

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

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

Mặc dù điều này không ảnh hưởng đến tốc độ chạy mã của bạn, tôi thích làm theo cách này vì nó là một phần trong thói quen bình luận của tôi. Tôi hoàn toàn không thích dành thời gian cố gắng nhớ logic đã đi sau mã của tôi 2 tháng khi tôi muốn sửa đổi.


1
Tôi đã chỉnh sửa câu trả lời của bạn. Tôi nghĩ rằng đề cập đến re.VERBOSElà đáng giá, và nó thêm một cái gì đó mà các câu trả lời khác dường như đã bỏ qua. Tuy nhiên, dẫn câu trả lời của bạn với "Tôi đang đăng ở đây vì tôi chưa thể nhận xét" chắc chắn sẽ xóa nó. Vui lòng không sử dụng hộp câu trả lời cho bất cứ điều gì khác ngoài câu trả lời. Bạn chỉ có một hoặc hai câu trả lời tốt để có thể nhận xét ở bất cứ đâu (50 đại diện), vì vậy hãy kiên nhẫn. Đặt bình luận vào ô trả lời khi bạn biết bạn sẽ không đưa bạn đến đó nhanh hơn. Nó sẽ giúp bạn tải xuống và xóa câu trả lời.
skrrgwasme 20/03/2015

4

Theo tài liệu Python :

Trình tự

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

tương đương với

result = re.match(pattern, string)

nhưng sử dụng re.compile()và lưu đối tượng biểu thức chính quy để sử dụng lại sẽ hiệu quả hơn khi biểu thức sẽ được sử dụng nhiều lần trong một chương trình.

Vì vậy, kết luận của tôi là, nếu bạn sẽ khớp cùng một mẫu cho nhiều văn bản khác nhau, tốt hơn bạn nên biên dịch nó.


3

Thật thú vị, việc biên dịch sẽ chứng minh hiệu quả hơn đối với tôi (Python 2.5.2 trên 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

Chạy mã trên một lần như vậy và một lần với hai ifdòng nhận xét theo cách khác, regex được biên dịch nhanh gấp đôi


2
Vấn đề tương tự như với so sánh hiệu suất của dF. Nó không thực sự công bằng trừ khi bạn bao gồm chi phí hiệu năng của chính câu lệnh biên dịch.
Carl Meyer

6
Carl, tôi không đồng ý. Trình biên dịch chỉ được thực hiện một lần, trong khi vòng lặp khớp được thực thi một triệu lần
Eli Bendersky

@eliben: Tôi đồng ý với Carl Meyer. Việc biên soạn diễn ra trong cả hai trường hợp. Triptych đề cập rằng bộ nhớ đệm có liên quan, do đó, trong trường hợp tối ưu (vẫn ở trong bộ đệm) cả hai cách tiếp cận đều là O (n + 1), mặc dù phần +1 là loại bị ẩn khi bạn không sử dụng re.compile một cách rõ ràng.
ớt bột

1
Đừng viết mã điểm chuẩn của riêng bạn. Tìm hiểu cách sử dụng timeit.py, được bao gồm trong bản phân phối tiêu chuẩn.
jemfinch

Bao nhiêu thời gian bạn đang tạo lại chuỗi mẫu trong vòng lặp for. Chi phí này không thể tầm thường.
IceArdor

3

Tôi đã chạy thử nghiệm này trước khi tình cờ thảo luận ở đây. Tuy nhiên, khi chạy nó, tôi nghĩ rằng ít nhất tôi đã đăng kết quả của mình.

Tôi đã đánh cắp và bastard ví dụ trong "Làm chủ các biểu thức chính quy" của Jeff Friedl. Đây là trên macbook chạy OSX 10.6 (bộ đôi intel core 2Ghz, ram 4GB). Phiên bản Python là 2.6.1.

Chạy 1 - sử dụng 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

Chạy 2 - Không sử dụng 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

Câu trả lời này có thể đến muộn nhưng là một phát hiện thú vị. Sử dụng biên dịch thực sự có thể giúp bạn tiết kiệm thời gian nếu bạn dự định sử dụng regex nhiều lần (điều này cũng được đề cập trong các tài liệu). Dưới đây bạn có thể thấy rằng sử dụng regex đã biên dịch là nhanh nhất khi phương thức khớp được gọi trực tiếp trên nó. chuyển một regex đã biên dịch sang re.match làm cho nó thậm chí chậm hơn và chuyển re.match với chuỗi patter nằm ở đâu đó ở giữa.

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

Bên cạnh hiệu suất.

Sử dụng compilegiúp tôi phân biệt các khái niệm của
1. module (re) ,
2. đối tượng regex
3. đối tượng phù hợp
Khi tôi bắt đầu học 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'

Để bổ sung, tôi đã tạo ra một bộ mô-đun đầy đủ ređể bạn tham khảo.

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

Tôi thực sự tôn trọng tất cả các câu trả lời trên. Theo ý kiến ​​của tôi Có! Để chắc chắn rằng nó đáng để sử dụng re.compile thay vì biên dịch regex, lặp đi lặp lại, mọi lúc.

Sử dụng re.compile làm cho mã của bạn năng động hơn, vì bạn có thể gọi regex đã được biên dịch, thay vì biên dịch lại và không sử dụng. Điều này có lợi cho bạn trong các trường hợp:

  1. Nỗ lực của bộ xử lý
  2. Thời gian phức tạp.
  3. Làm cho regex Universal. (Có thể được sử dụng trong findall, search, match)
  4. Và làm cho chương trình của bạn trông mát mẻ.

Thí dụ :

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

Sử dụng trong Findall

 find_alpha_numeric_string.findall(example_string)

Sử dụng trong tìm kiếm

  find_alpha_numeric_string.search(example_string)

Tương tự, bạn có thể sử dụng nó cho: Kết hợp và thay thế


1

Đây là một câu hỏi hay. Bạn thường thấy mọi người sử dụng re.compile mà không có lý do. Nó làm giảm khả năng đọc. Nhưng chắc chắn rằng có rất nhiều lần khi biên dịch trước biểu thức được gọi cho. Giống như khi bạn sử dụng nó lặp đi lặp lại nhiều lần trong một vòng lặp hoặc một số như vậy.

Nó giống như mọi thứ về lập trình (thực tế mọi thứ trong cuộc sống). Áp dụng lẽ thường.


Theo như tôi có thể nói từ lần lướt qua của mình, Python in Nutshell không đề cập đến việc sử dụng mà không re.compile (), khiến tôi tò mò.
Mat

Đối tượng regex thêm một đối tượng vào bối cảnh. Như tôi đã nói, tồn tại nhiều tình huống trong đó re.compile () có vị trí của nó. Ví dụ được đưa ra bởi OP không phải là một trong số họ.
PEZ

1

(tháng sau) thật dễ dàng để thêm bộ nhớ cache của riêng bạn xung quanh re.match hoặc bất cứ điều gì khác cho vấn đề đó -

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

Một wibni, sẽ không hay nếu: cachehint (size =), cacheinfo () -> size, hit, nclear ...


1

Tôi đã có nhiều kinh nghiệm khi chạy regex được biên dịch 1000 lần so với biên dịch nhanh chóng và không nhận thấy bất kỳ sự khác biệt nào có thể nhận thấy

Các phiếu bầu cho câu trả lời được chấp nhận dẫn đến giả định rằng những gì @Triptych nói là đúng với mọi trường hợp. Điều này không thực sự đúng. Một sự khác biệt lớn là khi bạn phải quyết định chấp nhận chuỗi regex hay đối tượng regex được biên dịch làm tham số cho hàm:

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

Luôn luôn tốt hơn để biên dịch regex của bạn trong trường hợp bạn cần sử dụng lại chúng.

Lưu ý ví dụ trong thời gian trên mô phỏng việc tạo một đối tượng regex đã biên dịch một lần tại thời điểm nhập so với "đang di chuyển" khi được yêu cầu cho một trận đấu.


1

Như một câu trả lời khác, như tôi thấy rằng nó chưa được đề cập trước đây, tôi sẽ tiếp tục và trích dẫn tài liệu Python 3 :

Bạn nên sử dụng các hàm cấp mô-đun này, hay bạn nên lấy mẫu và tự gọi các phương thức của nó? Nếu bạn đang truy cập regex trong một vòng lặp, biên dịch trước nó sẽ lưu một vài lệnh gọi hàm. Bên ngoài các vòng lặp, không có nhiều khác biệt nhờ bộ đệm trong.


1

Dưới đây là một ví dụ về việc sử dụng re.compilenhanh hơn 50 lần, theo yêu cầu .

Điểm giống như những gì tôi đã đưa ra trong nhận xét ở trên, cụ thể là, sử dụng re.compilecó thể là một lợi thế đáng kể khi việc sử dụng của bạn chẳng hạn như không được hưởng lợi nhiều từ bộ đệm biên dịch. Điều này xảy ra ít nhất trong một trường hợp cụ thể (mà tôi gặp phải trong thực tế), cụ thể là khi tất cả những điều sau đây là đúng:

  • Bạn có rất nhiều mẫu regex (hơn re._MAXCACHE, có mặc định là 512) và
  • bạn sử dụng các biểu thức này rất nhiều lần và
  • các cách sử dụng liên tiếp của cùng một mẫu được phân tách bằng nhiều hơn re._MAXCACHEcác biểu thức chính khác ở giữa, để mỗi lần sử dụng được xóa khỏi bộ đệm giữa các lần sử dụng liên tiếp.
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}')

Ví dụ đầu ra tôi nhận được trên máy tính xách tay của mình (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

Tôi không bận tâm timeitvì sự khác biệt quá rõ ràng, nhưng tôi nhận được những con số tương tự về mặt chất lượng mỗi lần. Lưu ý rằng ngay cả khi không có re.compile, sử dụng cùng một biểu thức nhiều lần và chuyển sang lần tiếp theo không tệ lắm (chỉ chậm hơn khoảng 2 lần so với re.compile), nhưng theo thứ tự khác (lặp qua nhiều biểu thức), điều đó còn tệ hơn đáng kể , như mong đợi. Ngoài ra, việc tăng kích thước bộ đệm cũng hoạt động: chỉ cần cài đặt re._MAXCACHE = len(patterns)setup()trên (tất nhiên tôi không khuyên bạn nên thực hiện những việc như vậy trong quá trình sản xuất vì tên có dấu gạch dưới được quy ước là riêng tư) giảm ~ 23 giây xuống còn ~ 0,7 giây phù hợp với sự hiểu biết của chúng tôi.


PS: nếu tôi chỉ sử dụng 3 mẫu regex trong toàn bộ mã của mình, thì mỗi mẫu được sử dụng (không có thứ tự cụ thể nào) hàng trăm lần, bộ đệm regex sẽ tự động giữ regex được biên dịch trước, đúng không?
Basj

@Basj Tôi nghĩ bạn có thể dùng thử và xem :) Nhưng câu trả lời, tôi khá chắc chắn, là có: chi phí bổ sung duy nhất trong trường hợp đó AFAICT chỉ đơn giản là tìm kiếm mẫu trong bộ đệm . Cũng lưu ý rằng bộ đệm là toàn cục (cấp mô-đun), vì vậy về nguyên tắc bạn có thể có một số thư viện phụ thuộc thực hiện tìm kiếm regex ở giữa bạn, vì vậy thật khó để tin rằng chương trình của bạn chỉ sử dụng 3 (hoặc bất kỳ số lượng) regex nào mặt khác, nhưng sẽ khá kỳ lạ nếu không :)
ShreevatsaR

0

Biểu thức chính quy được biên dịch trước khi được sử dụng khi sử dụng phiên bản thứ hai. Nếu bạn định thực hiện nó nhiều lần thì tốt hơn hết là biên dịch nó trước. Nếu không biên dịch mỗi khi bạn khớp với một lần thì không sao.


0

Mức độ dễ đọc / mức độ nhận thức

Đối với tôi, mức tăng chủ yếu là tôi chỉ cần nhớ, và đọc, một hình thức cú pháp regex API phức tạp - các <compiled_pattern>.method(xxx)hình thức hơn là các re.func(<pattern>, xxx)hình thức.

Các re.compile(<pattern>)là một chút soạn thêm, đúng.

Nhưng khi regex có liên quan, bước biên dịch thêm đó dường như không phải là nguyên nhân lớn của tải nhận thức. Và trên thực tế, trên các mẫu phức tạp, bạn thậm chí có thể có được sự rõ ràng từ việc tách khai báo khỏi bất kỳ phương thức regex nào mà sau đó bạn gọi nó.

Trước tiên tôi có xu hướng điều chỉnh các mẫu phức tạp trong một trang web như Regex101 hoặc thậm chí trong một tập lệnh thử nghiệm tối thiểu riêng biệt, sau đó đưa chúng vào mã của tôi, do đó, việc tách khai báo khỏi sử dụng cũng phù hợp với quy trình làm việc của tôi.


-1

Tôi muốn thúc đẩy rằng việc biên dịch trước là cả về mặt khái niệm và 'nghĩa đen' (như trong 'lập trình biết chữ'). hãy xem đoạn mã này:

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

trong ứng dụng của bạn, bạn sẽ viết:

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

điều này là đơn giản về chức năng như nó có thể nhận được. bởi vì đây là ví dụ rất ngắn, tôi đã giới thiệu cách để có được _text_has_foobar_re_searchtất cả trong một dòng. nhược điểm của mã này là nó chiếm một ít bộ nhớ cho bất kỳ thời gian tồn tại của TYPOđối tượng thư viện là gì; lợi thế là khi thực hiện tìm kiếm foobar, bạn sẽ thoát khỏi hai cuộc gọi chức năng và hai tra cứu từ điển lớp. có bao nhiêu regex được lưu trong bộ nhớ cache revà chi phí chung của bộ đệm đó không liên quan ở đây.

so sánh điều này với phong cách thông thường hơn, dưới đây:

import re

class Typo:

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

Trong ứng dụng:

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

Tôi dễ dàng thừa nhận rằng phong cách của tôi rất khác thường đối với trăn, thậm chí có thể gây tranh cãi. tuy nhiên, trong ví dụ phù hợp chặt chẽ hơn với cách sử dụng python, để thực hiện một khớp duy nhất, chúng ta phải khởi tạo một đối tượng, thực hiện ba tra cứu từ điển và thực hiện ba lệnh gọi hàm; Ngoài ra, chúng tôi có thể vàore rắc rối về bộ nhớ đệm khi sử dụng hơn 100 biểu thức chính. Ngoài ra, biểu thức chính quy được ẩn bên trong thân phương thức, mà hầu hết thời gian không phải là một ý tưởng tốt.

có thể nói rằng mọi tập hợp con của các biện pháp --- các báo cáo nhập khẩu được nhắm mục tiêu, bí danh; phương pháp bí danh khi áp dụng; giảm các lệnh gọi hàm và tra cứu từ điển đối tượng --- có thể giúp giảm độ phức tạp tính toán và khái niệm.


2
WTF. Không chỉ bạn bỏ ra một câu hỏi cũ, trả lời. Mã của bạn không phải là thành ngữ cũng như sai ở nhiều cấp độ - (ab) sử dụng các lớp làm không gian tên trong đó một mô-đun là đủ, viết hoa tên lớp, v.v ... Xem pastebin.com/iTAXAWen để triển khai tốt hơn. Chưa kể regex bạn sử dụng cũng bị hỏng. Nhìn chung, -1

2
tội lỗi. đây là một câu hỏi cũ, nhưng tôi không ngại trở thành số 100 trong một cuộc trò chuyện chậm lại. Câu hỏi chưa được đóng lại. tôi đã cảnh báo mã của tôi có thể là bất lợi cho một số thị hiếu. Tôi nghĩ rằng nếu bạn có thể xem nó như một minh chứng đơn thuần về những gì có thể làm được ở trăn, như: nếu chúng ta lấy mọi thứ, mọi thứ chúng ta tin, là tùy chọn, và sau đó sửa lại bằng mọi cách, mọi thứ trông như thế nào chúng ta có thể được? tôi chắc chắn rằng bạn có thể nhận ra giá trị và sự phân tán của giải pháp này và có thể khiếu nại rõ ràng hơn. nếu không, tôi phải kết luận yêu cầu về sự sai trái của bạn phụ thuộc ít hơn PEP008
chảy vào

2
Không, đó không phải là về PEP8. Đó chỉ là quy ước đặt tên, và tôi sẽ không bao giờ hạ thấp việc không tuân theo những quy ước đó. Tôi đánh giá thấp bạn bởi vì mã bạn hiển thị chỉ đơn giản là được viết kém. Nó bất chấp các quy ước và thành ngữ mà không có lý do, và là một hiện thân của tối ưu hóa hoán vị: Bạn sẽ phải tối ưu hóa ánh sáng ban ngày của tất cả các mã khác để điều này trở thành một nút cổ chai, và thậm chí sau đó, lần viết lại thứ ba tôi đưa ra còn ngắn hơn, nhiều hơn thành ngữ nhanh như lý luận của bạn (cùng số lượng truy cập thuộc tính).

"Viết kém" - như tại sao chính xác? "bất chấp quy ước và thành ngữ" - tôi đã cảnh báo bạn. "không có lý do" - vâng tôi có một lý do: đơn giản hóa nơi sự phức tạp không phục vụ mục đích; "Hóa thân của tối ưu hóa sớm" - tôi rất thích phong cách lập trình chọn sự cân bằng về khả năng đọc và hiệu quả; OP đã yêu cầu khơi gợi "lợi ích trong việc sử dụng re.compile", mà tôi hiểu là một câu hỏi về hiệu quả. "(Ab) sử dụng các lớp làm không gian tên" - đó là những từ ngữ của bạn bị lạm dụng. lớp học ở đó để bạn có một điểm tham chiếu "tự". tôi đã thử sử dụng các mô-đun cho mục đích này, các lớp học hoạt động tốt hơn.
chảy

"Viết hoa tên lớp", "Không, đó không phải là về PEP8" - bạn rõ ràng rất tức giận, thậm chí bạn không thể nói về những gì cần phải cãi nhau trước. "WTF", " sai " --- xem bạn cảm xúc thế nào? khách quan hơn và ít bọt hơn xin vui lòng.
chảy

-5

Hiểu biết của tôi là hai ví dụ này là tương đương có hiệu quả. Sự khác biệt duy nhất là trong lần đầu tiên, bạn có thể sử dụng lại biểu thức chính quy được biên dịch ở nơi khác mà không khiến nó được biên dịch lại.

Đây là một tài liệu tham khảo cho bạn: http://diveintopython3.ep.io/refactoring.html

Gọi hàm tìm kiếm của đối tượng mẫu đã biên dịch bằng chuỗi 'M' hoàn thành điều tương tự như gọi re.search bằng cả biểu thức chính quy và chuỗi 'M'. Chỉ nhiều, nhanh hơn nhiều. (Trong thực tế, hàm re.search chỉ đơn giản biên dịch biểu thức chính quy và gọi phương thức tìm kiếm của đối tượng mẫu kết quả cho bạn.)


1
Tôi đã không đánh giá thấp bạn, nhưng về mặt kỹ thuật thì điều này là sai: Dù sao thì Python cũng sẽ không biên dịch lại
Triptych
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.