การใช้ re.compile ของ Python คุ้มค่าไหม


461

มีประโยชน์ในการใช้งานคอมไพล์สำหรับนิพจน์ทั่วไปใน Python หรือไม่?

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

VS

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

8
อื่น ๆ แล้วความจริงที่ว่าใน 2.6 re.subจะไม่โต้แย้งธง ...
new123456

58
ฉันเพิ่งพบกรณีที่การใช้งานre.compileมีการปรับปรุง 10-50x คุณธรรมคือถ้าคุณมี regexes จำนวนมาก (มากกว่า MAXCACHE = 100) และคุณใช้พวกเขาหลายครั้งในแต่ละครั้ง (และคั่นด้วยมากกว่า MAXCACHE regexes ในระหว่างเพื่อให้แต่ละคนได้รับการล้างจากแคช: ดังนั้นการใช้ เหมือนกันหลาย ๆ ครั้งแล้วเลื่อนไปยังอันถัดไปจะไม่นับ) จากนั้นก็จะช่วยให้พวกเขารวบรวมได้อย่างแน่นอน มิฉะนั้นจะไม่สร้างความแตกต่าง
ShreevatsaR

8
สิ่งหนึ่งที่ควรทราบคือสำหรับสตริงที่ไม่ต้องการ regex การinทดสอบสตริงย่อยจะเร็วกว่า:>python -m timeit -s "import re" "re.match('hello', 'hello world')" 1000000 loops, best of 3: 1.41 usec per loop >python -m timeit "x = 'hello' in 'hello world'" 10000000 loops, best of 3: 0.0513 usec per loop
Gamrix

@ShreevatsaR น่าสนใจ! คุณสามารถโพสต์คำตอบด้วยตัวอย่างที่แสดงการปรับปรุง 10x-50x ได้หรือไม่? คำตอบส่วนใหญ่ที่ให้ไว้ที่นี่แสดงการปรับปรุง 3x ในบางกรณีที่แม่นยำและในกรณีอื่น ๆ แทบจะไม่มีการปรับปรุงเลย
Basj

1
@Basj เสร็จสิ้นโพสต์คำตอบ ฉันไม่รำคาญที่จะขุดสิ่งที่ฉันใช้ Python ในเดือนธันวาคม 2013 แต่สิ่งที่ตรงไปตรงมาเป็นครั้งแรกที่ฉันพยายามแสดงให้เห็นถึงพฤติกรรมเดียวกัน
ShreevatsaR

คำตอบ:


436

ฉันมีประสบการณ์มากมายที่ใช้เร็กคอร์ด regex ที่รวบรวมได้ 1000 ครั้งเทียบกับการคอมไพล์แบบทันทีและไม่ได้สังเกตเห็นความแตกต่างที่สังเกตได้ เห็นได้ชัดว่านี่เป็นเกร็ดเล็กเกร็ดน้อยและไม่แน่นอนอาร์กิวเมนต์ที่ดีกับการรวบรวม แต่ฉันได้พบความแตกต่างที่จะมีเพียงเล็กน้อย

แก้ไข: หลังจากดูอย่างรวดเร็วที่รหัสห้องสมุด Python 2.5 จริง ๆ ฉันเห็นว่า Python รวบรวมและเก็บข้อมูลแคชทุกครั้งที่คุณใช้งาน (รวมถึงการโทรre.match()) ดังนั้นคุณจะเปลี่ยนเฉพาะเมื่อ regex ได้รับการรวบรวมและไม่ควร ' ไม่ต้องประหยัดเวลามากนัก - เพียงเวลาที่ใช้ในการตรวจสอบแคช (การค้นหาคีย์บนภายในdictประเภท )

จากโมดูล re.py (ความเห็นเป็นของฉัน):

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

ฉันยังคงรวบรวมนิพจน์ทั่วไปไว้ล่วงหน้า แต่เพื่อผูกไว้กับชื่อที่ใช้ซ้ำได้ดีไม่ใช่เพื่อประสิทธิภาพที่คาดหวังไว้


12
ข้อสรุปของคุณไม่สอดคล้องกับคำตอบของคุณ หาก regex ถูกรวบรวมและจัดเก็บโดยอัตโนมัติไม่จำเป็นต้องทำด้วยมือในกรณีส่วนใหญ่
jfs

84
เจเอฟเจบาสเตียนทำหน้าที่เป็นสัญญาณให้โปรแกรมเมอร์ทราบว่าการใช้งาน regexp ที่มีปัญหาจะถูกนำมาใช้อย่างมากและไม่ได้หมายถึงการทิ้ง
kaleissin

40
ยิ่งไปกว่านั้นฉันขอบอกว่าถ้าคุณไม่ต้องการให้คอมไพล์และแคชได้รับผลกระทบในส่วนที่มีความสำคัญต่อประสิทธิภาพการทำงานของแอปพลิเคชันของคุณคุณควรทำการคอมไพล์มันก่อนมือของคุณในส่วนที่ไม่สำคัญ .
Eddie Parker

20
ฉันเห็นข้อได้เปรียบหลักสำหรับการใช้ regex ที่คอมไพล์ถ้าคุณใช้ regex เดียวกันซ้ำหลาย ๆ ครั้งซึ่งจะช่วยลดความเป็นไปได้ในการพิมพ์ผิด หากคุณเพียงแค่เรียกมันว่าครั้งเดียว uncompiled ก็สามารถอ่านได้มากขึ้น
monkut

18
ดังนั้นความแตกต่างที่สำคัญคือเมื่อคุณใช้ regex ที่แตกต่างกันมากมาย (มากกว่า _MAXCACHE) บางอย่างเพียงครั้งเดียวและอื่น ๆ อีกมากมายครั้ง ... จากนั้นจึงเป็นเรื่องสำคัญที่จะต้องเก็บนิพจน์ที่คอมไพล์ไว้สำหรับคนที่ใช้งานมากขึ้น ไม่ถูกล้างออกจากแคชเมื่อเต็ม
fortran

133

สำหรับฉันประโยชน์ที่ใหญ่ที่สุดในการ re.compileจะสามารถแยกนิยามของ regex จากการใช้งาน

แม้แต่นิพจน์อย่างง่ายเช่น0|[1-9][0-9]*(จำนวนเต็มในฐาน 10 ที่ไม่มีศูนย์นำหน้า) ก็ซับซ้อนพอที่คุณไม่ต้องพิมพ์อีกครั้งตรวจสอบว่าคุณพิมพ์ผิดหรือไม่และหลังจากนั้นต้องตรวจสอบอีกครั้งว่าคุณมีข้อผิดพลาดหรือไม่ . พลัสก็ดีกว่าที่จะใช้ชื่อตัวแปรเช่น NUM หรือ num_b10 0|[1-9][0-9]*กว่า

เป็นไปได้อย่างแน่นอนที่จะเก็บสตริงและส่งต่อให้ทำการแข่งขัน อย่างไรก็ตามสามารถอ่านได้น้อย :

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

เมื่อเทียบกับการรวบรวม:

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

แม้ว่าจะอยู่ใกล้พอสมควร แต่บรรทัดสุดท้ายของวินาทีจะดูเป็นธรรมชาติและเรียบง่ายขึ้นเมื่อใช้ซ้ำ ๆ


5
ฉันเห็นด้วยกับคำตอบนี้ บ่อยครั้งที่ใช้ re.compile จะทำให้ได้รหัสที่อ่านง่ายขึ้น
คาร์ลเมเยอร์

1
บางครั้งสิ่งที่ตรงกันข้ามก็เป็นจริงเช่น - หากคุณกำหนด regex ในที่เดียวและใช้กลุ่มการจับคู่ในที่ไกล ๆ
Ken Williams

1
@KenWilliams ไม่จำเป็น regex ชื่อดีสำหรับวัตถุประสงค์เฉพาะควรชัดเจนแม้เมื่อใช้ไกลจากคำจำกัดความเดิม ตัวอย่างus_phone_numberหรือsocial_security_numberอื่น ๆ
Brian M. Sheldon

2
@ BrianM.Sheldon ตั้งชื่อ regex ให้ดีไม่ได้ช่วยให้คุณรู้ว่ากลุ่มการจับภาพที่หลากหลายนั้นเป็นตัวแทนของอะไร
เคนวิลเลียมส์

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

ดังนั้นหากคุณจะใช้regex เดียวกันมากมันอาจคุ้มค่าที่จะทำre.compile(โดยเฉพาะสำหรับ regexes ที่ซับซ้อนมากขึ้น)

ข้อโต้แย้งมาตรฐานเทียบกับการปรับให้เหมาะสมก่อนกำหนดมีผล แต่ฉันไม่คิดว่าคุณจะสูญเสียความชัดเจน / ความตรงไปตรงมามากนักโดยใช้re.compileหากคุณสงสัยว่า regexps ของคุณอาจกลายเป็นคอขวดของประสิทธิภาพ

ปรับปรุง:

ใน Python 3.6 (ฉันสงสัยว่าการตั้งเวลาข้างต้นเสร็จสิ้นโดยใช้ Python 2.x) และฮาร์ดแวร์ 2018 (MacBook Pro) ตอนนี้ฉันได้รับการกำหนดเวลาต่อไปนี้แล้ว:

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

ฉันยังเพิ่มเคส (สังเกตเห็นความแตกต่างของเครื่องหมายคำพูดระหว่างการวิ่งสองครั้งล่าสุด) ที่แสดงให้เห็นว่าre.match(x, ...)แท้จริง [คร่าว ๆ ] เทียบเท่ากับre.compile(x).match(...)นั่นคือไม่มีการแคชเบื้องหลังของการเป็นตัวแทนที่รวบรวมดูเหมือนว่าจะเกิดขึ้น


5
ปัญหาที่สำคัญเกี่ยวกับวิธีการของคุณที่นี่เนื่องจากอาร์กิวเมนต์การตั้งค่าไม่รวมอยู่ในการกำหนดเวลา ดังนั้นคุณได้ลบเวลาการรวบรวมออกจากตัวอย่างที่สองและลองหาค่าเฉลี่ยในตัวอย่างแรก นี่ไม่ได้หมายความว่าตัวอย่างแรกรวบรวมทุกครั้ง
Triptych

1
ใช่ฉันยอมรับว่านี่ไม่ใช่การเปรียบเทียบที่เป็นธรรมของทั้งสองกรณี
Kiv

7
ฉันเห็นสิ่งที่คุณหมายถึง แต่ไม่ว่าสิ่งที่จะเกิดขึ้นในใบสมัครจริงที่ regexp ใช้หลายครั้งหรือไม่
dF

26
@Triptych, @Kiv: จุดทั้งหมดของการรวบรวม regexps แยกจากการใช้คือการลดการรวบรวม; การลบมันออกจากจังหวะนั้นเป็นสิ่งที่ dF ควรทำเพราะมันหมายถึงการใช้งานจริงอย่างแม่นยำที่สุด เวลาในการรวบรวมนั้นไม่เกี่ยวข้องอย่างยิ่งกับวิธีที่ timeit.py ทำการจับเวลาของที่นี่ มันทำงานได้หลายครั้งและรายงานเพียงช่วงเวลาสั้นที่สุด ณ จุดนี้ regexp ที่คอมไพล์แล้วจะถูกแคช ค่าใช้จ่ายเพิ่มเติมที่คุณเห็นอยู่ที่นี่ไม่ใช่ค่าใช้จ่ายในการรวบรวม regexp แต่ค่าใช้จ่ายในการค้นหาในแคช regexp ที่รวบรวม (พจนานุกรม)
jemfinch

3
@Triptych ควรimport reย้ายออกจากการตั้งค่าหรือไม่ มันคือทั้งหมดที่เกี่ยวกับที่คุณต้องการวัด ถ้าฉันเรียกใช้สคริปต์ไพ ธ อนหลายครั้งมันจะมีimport reเวลาเข้าชม เมื่อเปรียบเทียบทั้งสองสิ่งสำคัญคือต้องแยกทั้งสองเส้นออกเพื่อกำหนดเวลา ใช่อย่างที่คุณบอกว่ามันเป็นเมื่อคุณจะมีเวลาในการตี การเปรียบเทียบแสดงให้เห็นว่าคุณใช้เวลาในการเข้าชมหนึ่งครั้งและทำซ้ำเวลาที่น้อยกว่าโดยการรวบรวมหรือคุณจะตีในแต่ละครั้งสมมติว่าแคชได้รับการเคลียร์ระหว่างการโทร การเพิ่มเวลาของh=re.compile('hello')จะช่วยชี้แจง
Tom Myddeltyn

39

นี่คือกรณีทดสอบง่ายๆ:

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

ด้วย 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

ดังนั้นดูเหมือนว่าการคอมไพล์จะเร็วขึ้นด้วยเคสตัวเล็กนี้แม้ว่าคุณจะจับคู่ครั้งเดียวเท่านั้น


2
Python เวอร์ชันใดเป็นรุ่นนี้
Kyle Strand

2
มันไม่สำคัญจริงๆประเด็นคือการลองใช้เกณฑ์มาตรฐานในสภาพแวดล้อมที่คุณจะใช้รหัส
ดาวิดราชา

1
สำหรับฉันแล้วการแสดงเกือบเหมือนกันสำหรับ 1,000 ลูปหรือมากกว่านั้น รุ่นที่คอมไพล์นั้นเร็วกว่าสำหรับลูป 1-100 (ทั้ง Pythons 2.7 และ 3.4)
Zitrax

2
ในการติดตั้ง Python 2.7.3 ของฉันแทบจะไม่แตกต่างกัน บางครั้งการคอมไพล์ก็เร็วขึ้นบางครั้งมันก็ช้ากว่า ความแตกต่างอยู่เสมอ <5% ​​ดังนั้นฉันจึงนับความแตกต่างเป็นการวัดความไม่แน่นอนเนื่องจากอุปกรณ์มีเพียงหนึ่ง CPU
Dakkaron

1
ใน Python 3.4.3 ที่เห็นในการวิ่งสองครั้ง: การใช้การคอมไพล์ก็ช้ากว่าการคอมไพล์
Zelphir Kaltstahl

17

ฉันแค่ลองเอง สำหรับกรณีง่าย ๆ ในการแยกตัวเลขออกมาจากสตริงและรวมมันการใช้ออบเจ็กต์นิพจน์ทั่วไปที่คอมไพล์นั้นมีความรวดเร็วเป็นสองเท่าของการใช้reเมธอด

ตามที่คนอื่น ๆ ได้ชี้ให้เห็นreวิธีการ (รวมถึงre.compile) ค้นหาสตริงการแสดงออกปกติในแคชของการแสดงออกที่รวบรวมไว้ก่อนหน้านี้ ดังนั้นในกรณีปกติค่าใช้จ่ายเพิ่มเติมของการใช้reเมธอดจึงเป็นเพียงค่าใช้จ่ายในการค้นหาแคช

อย่างไรก็ตามการตรวจสอบของรหัสแสดงให้เห็นว่าแคชถูก จำกัด ไว้ที่ 100 การแสดงออก สิ่งนี้ทำให้เกิดคำถามว่าการล้นแคชนั้นเจ็บปวดขนาดไหน? รหัสประกอบด้วยส่วนต่อประสานภายในกับคอมไพเลอร์นิพจน์ทั่วไป, re.sre_compile.compile. ถ้าเราเรียกมันว่าเราจะข้ามแคช ปรากฎว่ามันมีขนาดประมาณสองคำสั่งที่ช้ากว่าสำหรับการแสดงออกปกติพื้นฐานเช่นr'\w+\s+([0-9_]+)\s+\w*'มันจะออกมาเป็นประมาณสองคำสั่งของขนาดช้าลงสำหรับการแสดงออกปกติพื้นฐานเช่น

นี่คือการทดสอบของฉัน:

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

เมธอด 'ReallyCompiled' ใช้อินเตอร์เฟสภายในซึ่งข้ามแคช โปรดสังเกตว่าการคอมไพล์ในการวนซ้ำแต่ละรอบจะทำซ้ำ 10,000 ครั้งเท่านั้นไม่ใช่หนึ่งล้าน


ฉันเห็นด้วยกับคุณว่า regexes ที่คอมไพล์รันเร็วกว่าที่ไม่คอมไพล์ ฉันใช้ประโยคมากกว่า 10,000 ประโยคและวนซ้ำเพื่อทำซ้ำสำหรับ regexes เมื่อ regexes ไม่ได้ถูกรวบรวมและคำนวณทุกครั้งที่การทำนายการวิ่งเต็มรูปแบบคือ 8 ชั่วโมงหลังจากสร้างพจนานุกรมตามดัชนีที่มีรูปแบบ regex เรียบเรียงที่ฉันใช้ ทั้งสิ่งเป็นเวลา 2 นาที ฉันไม่สามารถเข้าใจคำตอบข้างต้น ...
Eli Borodach

12

ฉันเห็นด้วยกับ Abe ซื่อสัตย์ว่าmatch(...)ในตัวอย่างที่กำหนดมีความแตกต่าง พวกเขาไม่ใช่การเปรียบเทียบแบบตัวต่อตัวและผลลัพธ์จะแตกต่างกันไป เพื่อให้การตอบกลับของฉันง่ายขึ้นฉันใช้ A, B, C, D สำหรับฟังก์ชั่นที่เป็นปัญหา โอ้ใช่เรากำลังจัดการกับ 4 ฟังก์ชันre.pyแทน 3

ใช้รหัสชิ้นนี้:

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

เหมือนกับการใช้รหัสนี้:

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

เพราะเมื่อมองเข้าไปในแหล่งที่มาre.py(A + B) หมายถึง:

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

และ (C) เป็นจริง:

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

ดังนั้น (C) ไม่เหมือนกับ (B) ในความเป็นจริง (C) การโทร (B) หลังจากการโทร (D) ซึ่งถูกเรียกโดย (A) กล่าวอีกนัยหนึ่ง, (C) = (A) + (B). ดังนั้นการเปรียบเทียบ (A + B) ภายในลูปมีผลเช่นเดียวกับ (C) ภายในลูป

จอร์จregexTest.pyพิสูจน์เรื่องนี้ให้เราฟัง

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

ความสนใจของทุกคนคือวิธีการได้รับผล 2.323 วินาที เพื่อให้แน่ใจว่าcompile(...)ได้รับเรียกเพียงครั้งเดียวเราต้องเก็บวัตถุ regex ที่รวบรวมไว้ในหน่วยความจำ ถ้าเราใช้คลาสเราสามารถเก็บวัตถุและนำมาใช้ใหม่ได้ทุกครั้งที่เรียกใช้ฟังก์ชันของเรา

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

หากเราไม่ได้ใช้คลาส (ซึ่งเป็นคำขอของฉันในวันนี้) แสดงว่าฉันไม่มีความคิดเห็น ฉันยังเรียนรู้ที่จะใช้ตัวแปรทั่วโลกใน Python และฉันรู้ว่าตัวแปรทั่วโลกเป็นสิ่งที่ไม่ดี

อีกจุดหนึ่งฉันเชื่อว่าการใช้(A) + (B)วิธีการมีความได้เปรียบ นี่คือข้อเท็จจริงบางอย่างที่ฉันสังเกต (โปรดแก้ไขฉันหากฉันผิด):

  1. โทร A หนึ่งครั้งมันจะทำการค้นหาครั้งเดียวในรายการที่_cacheตามมาด้วยsre_compile.compile()การสร้างวัตถุ regex เรียก A สองครั้งมันจะทำการค้นหาสองครั้งและคอมไพล์หนึ่งรายการ (เนื่องจากแคชของ regex)

  2. ถ้า_cacheget flushed ในระหว่างนั้นวัตถุ regex ออกจากหน่วยความจำและ Python จำเป็นต้องรวบรวมอีกครั้ง (มีคนแนะนำว่า Python จะไม่คอมไพล์ใหม่)

  3. หากเราเก็บวัตถุ regex โดยใช้ (A) วัตถุ regex จะยังคงอยู่ใน _cache และถูกลบทิ้งอย่างใด แต่รหัสของเราเก็บไว้อ้างอิงและวัตถุ regex จะไม่ได้รับการปล่อยตัว ไพ ธ อนไม่จำเป็นต้องรวบรวมอีกครั้ง

  4. ความแตกต่าง 2 วินาทีในการทดสอบของ George ที่รวบรวม compiledInLoop กับการคอมไพล์ส่วนใหญ่เป็นเวลาที่จำเป็นในการสร้างคีย์และค้นหา _cache ไม่ได้แปลว่าเวลารวบรวมของ regex

  5. การทดสอบคอมไพล์ของจอร์จแสดงให้เห็นว่าจะเกิดอะไรขึ้นถ้ามันรวบรวมคอมไพล์ใหม่ทุกครั้ง: จะช้าลง 100x (เขาลดลูปจาก 1,000,000 เป็น 10,000)

นี่เป็นกรณีเดียวที่ (A + B) ดีกว่า (C):

  1. หากเราสามารถแคชการอ้างอิงของวัตถุ regex ภายในชั้นเรียน
  2. หากเราจำเป็นต้องเรียก (B) ซ้ำ ๆ (ภายในลูปหรือหลาย ๆ ครั้ง) เราต้องแคชการอ้างอิงไปยังวัตถุ regex นอกลูป

กรณีที่ (C) ดีพอ:

  1. เราไม่สามารถแคชข้อมูลอ้างอิง
  2. เราจะใช้มันเป็นครั้งคราว
  3. โดยรวมแล้วเราไม่มี regex มากเกินไป (สมมติว่าคอมไพล์ที่ไม่เคยถูกลบทิ้ง)

แค่สรุปนี่คือ ABC:

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

ขอบคุณที่อ่าน.


8

ส่วนใหญ่มีความแตกต่างเล็กน้อยว่าคุณใช้re.compileหรือไม่ ภายในฟังก์ชั่นทั้งหมดจะดำเนินการในแง่ของขั้นตอนการรวบรวม:

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)

นอกจากนี้ re.compile () ยังเลี่ยงการใช้ตรรกะทางอ้อมและแคชเพิ่มเติม:

_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

นอกเหนือจากประโยชน์ความเร็วเล็ก ๆ จากการใช้re.compileผู้คนยังชอบความสามารถในการอ่านที่มาจากการตั้งชื่อรูปแบบข้อกำหนดที่ซับซ้อนและแยกพวกเขาออกจากตรรกะทางธุรกิจที่มีการใช้งาน:

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

หมายเหตุผู้ตอบอีกคนหนึ่งเชื่ออย่างไม่ถูกต้องว่าไฟล์pycจัดเก็บรูปแบบที่คอมไพล์โดยตรง อย่างไรก็ตามในความเป็นจริงพวกเขาจะสร้างใหม่ทุกครั้งเมื่อโหลด PYC:

>>> from dis import dis
>>> with open('tmp.pyc', 'rb') as f:
        f.read(8)
        dis(marshal.load(f))

  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (re)
              9 STORE_NAME               0 (re)

  3          12 LOAD_NAME                0 (re)
             15 LOAD_ATTR                1 (compile)
             18 LOAD_CONST               2 ('[aeiou]{2,5}')
             21 CALL_FUNCTION            1
             24 STORE_NAME               2 (lc_vowels)
             27 LOAD_CONST               1 (None)
             30 RETURN_VALUE

การถอดแยกชิ้นส่วนด้านบนมาจากไฟล์ PYC สำหรับtmp.pyบรรจุ:

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

1
เป็น"ในdef search(pattern, string, flags=0):"การพิมพ์ผิดหรือไม่?
phuclv

1
โปรดทราบว่าถ้าpatternมีอยู่แล้วเป็นรูปแบบการรวบรวมแคชค่าใช้จ่ายเป็นสำคัญ: hashing มีราคาแพงและรูปแบบที่ไม่เคยเขียนไปยังแคชเพื่อค้นหาล้มเหลวในแต่ละครั้งด้วยSRE_Pattern KeyError
Eric Duminil

5

โดยทั่วไปแล้วฉันพบว่าการใช้ค่าสถานะง่ายขึ้น (อย่างน้อยก็ง่ายต่อการจดจำวิธี) เช่นre.Iเมื่อรวบรวมรูปแบบมากกว่าการใช้ค่าสถานะแบบอินไลน์

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

VS

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

คุณสามารถใช้การตั้งค่าสถานะเป็นอาร์กิวเมนต์ที่สามของre.findallเกินไป
aderchox

5

ใช้ตัวอย่างที่กำหนด:

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

แข่งขันวิธีการในตัวอย่างข้างต้นไม่ได้เป็นเช่นเดียวกับที่ใช้ด้านล่าง:

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

re.compile ()ส่งคืนวัตถุนิพจน์ปกติซึ่งหมายถึงhเป็นออบเจ็กต์ regex

วัตถุ regex มีวิธีการจับคู่ของตัวเองด้วยพารามิเตอร์posและendpos เสริม :

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

POS

พารามิเตอร์ตัวเลือกที่สองposให้ดัชนีในสตริงที่การค้นหาจะเริ่ม; มันเริ่มต้นที่ 0 นี้ไม่เทียบเท่ากับการแบ่งสตริง; '^'ตัวอักษรรูปแบบตรงที่จุดเริ่มต้นที่แท้จริงของสตริงและที่ตำแหน่งหลังขึ้นบรรทัดใหม่ แต่ไม่จำเป็นต้องที่ดัชนีที่ค้นหาคือการเริ่มต้น

endpos

endposพารามิเตอร์ทางเลือกจะ จำกัด จำนวนการค้นหาสตริง มันจะเหมือนกับว่าสตริงนั้นมีความยาวอักขระสิ้นสุดดังนั้นเฉพาะอักขระจากposที่endpos - 1จะถูกค้นหาเพื่อจับคู่ หากendposน้อยกว่าposจะไม่พบการแข่งขัน มิฉะนั้นถ้าRXเป็นที่รวบรวมวัตถุนิพจน์ปกติเทียบเท่ากับrx.search(string, 0, 50)rx.search(string[:50], 0)

regex วัตถุค้นหา , findallและfinditerวิธีการยังสนับสนุนพารามิเตอร์เหล่านี้

re.match(pattern, string, flags=0)ไม่สนับสนุนพวกเขาในขณะที่คุณสามารถมองเห็น
หรือไม่ของการค้นหา , findallและfinditerลูกน้อง

วัตถุแข่งขันมีแอตทริบิวต์ที่เสริมพารามิเตอร์เหล่านี้:

match.pos

ค่าของ pos ซึ่งส่งผ่านไปยังวิธีการค้นหา () หรือการจับคู่ () ของวัตถุ regex นี่คือดัชนีลงในสตริงที่เอ็นจิน RE เริ่มค้นหาการจับคู่

match.endpos

มูลค่าของ endpos ที่ส่งผ่านไปยังวิธีการค้นหา () หรือการจับคู่ () ของวัตถุ regex นี่คือดัชนีลงในสตริงที่เกินกว่าที่เอ็นจิน RE จะไม่ดำเนินการ


วัตถุ regexมีที่ไม่ซ้ำกันที่มีประโยชน์อาจจะเป็นสองคุณลักษณะ:

regex.groups

จำนวนของกลุ่มการดักจับในรูปแบบ

regex.groupindex

การแม็พพจนานุกรมชื่อกลุ่มสัญลักษณ์ใด ๆ ที่กำหนดโดย (? P) กับหมายเลขกลุ่ม พจนานุกรมว่างเปล่าหากไม่มีการใช้กลุ่มสัญลักษณ์ในรูปแบบ


และสุดท้ายวัตถุการจับคู่มีคุณลักษณะนี้:

match.re

วัตถุการแสดงออกปกติที่มีการแข่งขัน () หรือการค้นหา () วิธีการผลิตอินสแตนซ์การแข่งขันนี้


4

ความแตกต่างด้านประสิทธิภาพการทำงานโดยใช้ re.compile และการใช้ออบเจ็กต์นิพจน์ทั่วไปที่คอมไพล์เพื่อจับคู่

ฉันมีประสบการณ์ที่เจ็บปวดในการแก้ไขข้อบกพร่องของรหัสง่ายๆ:

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

และต่อมาฉันจะใช้การเปรียบเทียบ

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

ที่patternPhrasesควรจะเป็นตัวแปรที่มีสตริงแสดงออกปกติx[columnIndex]เป็นตัวแปรที่มีสตริง

ฉันมีปัญหาที่patternPhrasesไม่ตรงกับสตริงที่คาดหวังบางอย่าง!

แต่ถ้าฉันใช้แบบฟอร์ม re.compile:

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

จากนั้นใน

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

งูหลามจะมีบ่นว่า "สตริงไม่ได้มีแอตทริบิวต์ของการแข่งขัน" โดยการทำแผนที่อาร์กิวเมนต์ตำแหน่งในcompare,x[columnIndex]ใช้เป็นนิพจน์ปกติ !, เมื่อฉันหมายถึงจริง

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

ในกรณีของฉันการใช้ re.compile มีความชัดเจนมากขึ้นในวัตถุประสงค์ของการแสดงออกปกติเมื่อค่าถูกซ่อนไว้ด้วยตาเปล่าดังนั้นฉันสามารถขอความช่วยเหลือเพิ่มเติมจากการตรวจสอบ Python ในเวลาทำงาน

ดังนั้นคุณธรรมของบทเรียนของฉันคือเมื่อการแสดงออกปกติไม่ใช่แค่ตัวอักษรจริงฉันควรใช้ re.compile เพื่อให้ Python ช่วยฉันยืนยันสมมติฐานของฉัน


4

มีอีกหนึ่งการเพิ่มของการใช้ re.compile () ในรูปแบบของการเพิ่มความคิดเห็นในรูปแบบ regex ของฉันโดยใช้ re.VERBOSE

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

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

แม้ว่าสิ่งนี้จะไม่ส่งผลกระทบต่อความเร็วในการเรียกใช้รหัสของคุณ แต่ฉันชอบที่จะทำเช่นนี้เนื่องจากเป็นส่วนหนึ่งของนิสัยการแสดงความคิดเห็นของฉัน ฉันไม่ชอบที่จะใช้เวลาพยายามจำตรรกะที่ตามหลังโค้ดของฉันลงไป 2 เดือนเมื่อฉันต้องการแก้ไข


1
ฉันแก้ไขคำตอบของคุณแล้ว ฉันคิดว่าการกล่าวถึงre.VERBOSEนั้นคุ้มค่าและเพิ่มสิ่งที่คำตอบอื่น ๆ ดูเหมือนจะไม่ได้รับ อย่างไรก็ตามการนำคำตอบของคุณไปที่ "ฉันกำลังโพสต์ที่นี่เพราะฉันยังไม่สามารถแสดงความคิดเห็นได้" แน่ใจว่าได้ลบทิ้งแล้ว โปรดอย่าใช้กล่องคำตอบสำหรับสิ่งอื่นนอกจากคำตอบ คุณมีเพียงหนึ่งหรือสองคำตอบที่ดีในการแสดงความคิดเห็นได้ทุกที่ (50 ตัวแทน) ดังนั้นโปรดอดทนรอ การใส่ความคิดเห็นลงในกล่องคำตอบเมื่อคุณรู้ว่าคุณไม่ควรไปเร็วกว่านี้ มันจะทำให้คุณโหวตและลบคำตอบ
skrrgwasme

4

ตามเอกสาร Python :

เป็นลำดับ

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

เทียบเท่ากับ

result = re.match(pattern, string)

แต่ใช้ re.compile()และการบันทึกออบเจ็กต์นิพจน์ทั่วไปที่เกิดขึ้นเพื่อนำมาใช้ซ้ำนั้นมีประสิทธิภาพมากขึ้นเมื่อนิพจน์จะถูกใช้หลายครั้งในโปรแกรมเดียว

ดังนั้นข้อสรุปของฉันคือถ้าคุณจะจับคู่รูปแบบเดียวกันกับข้อความต่าง ๆ มากมายคุณควรคอมไพล์มันให้ดีกว่า


3

ที่น่าสนใจการรวบรวมนั้นพิสูจน์ว่ามีประสิทธิภาพมากกว่าสำหรับฉัน (Python 2.5.2 สำหรับ 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

การเรียกใช้โค้ดข้างต้นหนึ่งครั้งเท่าที่เป็นไปได้และอีกครั้งที่ทั้งสองifบรรทัดแสดงความคิดเห็นในทางกลับกัน Regex ที่คอมไพล์จะเร็วเป็นสองเท่า


2
ปัญหาเดียวกับการเปรียบเทียบประสิทธิภาพของ dF มันไม่ยุติธรรมเลยเว้นแต่คุณจะรวมค่าประสิทธิภาพของคำสั่งคอมไพล์ด้วย
คาร์ลเมเยอร์

6
คาร์ลฉันไม่เห็นด้วย คอมไพล์จะถูกดำเนินการเพียงครั้งเดียวในขณะที่วงที่ตรงกันจะถูกดำเนินล้านครั้ง
Eli Bendersky

@eliben: ฉันเห็นด้วยกับคาร์ลเมเยอร์ การรวบรวมเกิดขึ้นในทั้งสองกรณี อันมีค่ากล่าวถึงการแคชดังนั้นในกรณีที่เหมาะสม (อยู่ในแคช) ทั้งสองวิธีคือ O (n + 1) แม้ว่าส่วน +1 นั้นจะถูกซ่อนไว้เมื่อคุณไม่ได้ใช้ re.compile อย่างชัดเจน
paprika

1
อย่าเขียนรหัสการเปรียบเทียบของคุณเอง เรียนรู้การใช้ timeit.py ซึ่งรวมอยู่ในการแจกแจงมาตรฐาน
jemfinch

คุณใช้เวลาเท่าไรในการสร้างสตริงรูปแบบในลูป for ค่าใช้จ่ายนี้ไม่สำคัญเลย
IceArdor

3

ฉันวิ่งทดสอบนี้ก่อนที่จะพบกับการอภิปรายที่นี่ อย่างไรก็ตามหลังจากใช้มันฉันคิดว่าอย่างน้อยฉันก็โพสต์ผลลัพธ์ของฉัน

ฉันขโมยและยกตัวอย่างไอเทมใน "Mastering Regular Expressions" ของ Jeff Friedl นี่คือบน macbook ที่ใช้ OSX 10.6 (2Ghz intel core 2 duo, 4GB ram) Python เวอร์ชัน 2.6.1

เรียกใช้ 1 - ใช้ 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

เรียกใช้ 2 - ไม่ใช้ 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

คำตอบนี้อาจจะมาสาย แต่ก็น่าสนใจ การใช้การคอมไพล์สามารถช่วยคุณประหยัดเวลาได้มากหากคุณวางแผนที่จะใช้งาน regex หลายครั้ง (ซึ่งจะกล่าวถึงในเอกสารด้วย) ด้านล่างคุณจะเห็นว่าการใช้ regex ที่คอมไพล์เป็นวิธีที่เร็วที่สุดเมื่อวิธีการจับคู่ถูกเรียกใช้โดยตรง การส่ง regex ที่คอมไพล์ไปที่ re.match ทำให้มันช้าลงและส่ง re.match ด้วยสตริง patter นั้นอยู่ตรงกลาง

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

นอกจากประสิทธิภาพ

การใช้compileช่วยให้ฉันแยกความแตกต่างของแนวคิดของ
1. โมดูล (ใหม่) ,
2. วัตถุ regex
3. จับคู่วัตถุ
เมื่อฉันเริ่มเรียนรู้ 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'

ในฐานะที่เป็นส่วนประกอบฉันได้ทำสูตร cheatsheet ของโมดูลreสำหรับการอ้างอิงของคุณ

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

ฉันเคารพคำตอบทั้งหมดข้างต้นจริงๆ จากความคิดเห็นของฉันใช่! แน่นอนว่ามันคุ้มค่าที่จะใช้ re.compile แทนการรวบรวม regex ซ้ำแล้วซ้ำอีกทุกครั้ง

การใช้re.compileทำให้โค้ดของคุณมีชีวิตชีวามากขึ้นในขณะที่คุณสามารถเรียก regex ที่คอมไพล์แล้ว, แทนที่จะรวบรวมอีกครั้งและ aagain สิ่งนี้มีประโยชน์สำหรับคุณในบางกรณี:

  1. ความพยายามของหน่วยประมวลผลกลาง
  2. ความซับซ้อนของเวลา
  3. ทำให้ regex Universal (สามารถใช้ใน findall, ค้นหา, จับคู่)
  4. และทำให้โปรแกรมของคุณดูดี

ตัวอย่าง:

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

ใช้ใน Findall

 find_alpha_numeric_string.findall(example_string)

ใช้ในการค้นหา

  find_alpha_numeric_string.search(example_string)

คุณสามารถใช้เพื่อ: จับคู่และทดแทน


1

นี่เป็นคำถามที่ดี คุณมักจะเห็นคนใช้ re.compile โดยไม่มีเหตุผล มันช่วยลดการอ่าน แต่แน่ใจว่ามีหลายครั้งเมื่อรวบรวมนิพจน์ล่วงหน้า เช่นเมื่อคุณใช้ซ้ำหลายครั้งในลูปหรือบางอย่าง

มันเหมือนทุกอย่างเกี่ยวกับการเขียนโปรแกรม (ทุกอย่างในชีวิตจริง) ใช้สามัญสำนึก


เท่าที่ฉันสามารถบอกได้จากสะบัดสั้นของฉันผ่านPython ในกะลาไม่พูดถึงการใช้งานโดยไม่ต้อง re.compile () ซึ่งทำให้ฉันอยากรู้
Mat

วัตถุ regex เพิ่มวัตถุอีกหนึ่งเข้าไปในบริบท ดังที่ฉันพูดมีหลายสถานการณ์ที่ re.compile () มีที่อยู่ ตัวอย่างที่กำหนดโดย OP ไม่ใช่หนึ่งในนั้น
PEZ

1

(เดือนต่อมา) มันง่ายที่จะเพิ่มแคชของคุณเองรอบ ๆ re.match หรือสิ่งอื่นใดสำหรับเรื่องนั้น -

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

วิกิ, มันจะไม่ดีถ้า: cachehint (size =), cacheinfo () -> ขนาด, เพลงฮิต, nclear ...


1

ฉันมีประสบการณ์มากมายในการรัน regex ที่รวบรวมได้ 1000 ครั้งเทียบกับการคอมไพล์ on-the-fly และไม่ได้สังเกตเห็นความแตกต่างที่สังเกตได้

คะแนนโหวตสำหรับคำตอบที่ยอมรับนำไปสู่การสันนิษฐานว่า @Triptych พูดว่าเป็นจริงสำหรับทุกกรณี สิ่งนี้ไม่เป็นความจริง ความแตกต่างที่สำคัญอย่างหนึ่งคือเมื่อคุณต้องตัดสินใจว่าจะยอมรับสตริง regex หรือวัตถุ regex ที่คอมไพล์เป็นพารามิเตอร์ของฟังก์ชัน:

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

เป็นการดีกว่าเสมอในการรวบรวม regex ของคุณในกรณีที่คุณจำเป็นต้องใช้ซ้ำ

โปรดสังเกตตัวอย่างใน timeit ข้างต้นจำลองการสร้างวัตถุ regex ที่รวบรวมหนึ่งครั้งในเวลานำเข้ากับ "on-the-fly" เมื่อจำเป็นสำหรับการแข่งขัน


1

ในฐานะที่เป็นคำตอบอื่นที่ฉันเห็นว่ามันไม่ได้กล่าวถึงมาก่อนฉันจะดำเนินการต่อและอ้างอิงเอกสาร Python 3 :

คุณควรใช้ฟังก์ชั่นระดับโมดูลเหล่านี้หรือคุณควรจะได้รับรูปแบบและเรียกวิธีการของตัวเอง? หากคุณเข้าถึง regex ภายในลูปการคอมไพล์ล่วงหน้าจะบันทึกการเรียกใช้ฟังก์ชันบางอย่าง ด้านนอกของลูปไม่มีความแตกต่างกันมากนักเนื่องจากแคชภายใน


1

นี่คือตัวอย่างที่การใช้re.compileเร็วกว่า 50 เท่าตามที่ร้องขอการร้องขอ

ประเด็นก็เหมือนกับสิ่งที่ฉันทำในความคิดเห็นข้างต้นกล่าวคือการใช้re.compileสามารถเป็นข้อได้เปรียบที่สำคัญเมื่อการใช้งานของคุณนั้นไม่ได้รับประโยชน์มากนักจากการรวบรวมแคช สิ่งนี้เกิดขึ้นอย่างน้อยหนึ่งกรณี (ที่ฉันพบเจอในทางปฏิบัติ) คือเมื่อทั้งหมดต่อไปนี้เป็นจริง:

  • คุณมีรูปแบบ regex จำนวนมาก (มากกว่าre._MAXCACHEซึ่งมีค่าเริ่มต้นปัจจุบันคือ 512) และ
  • คุณใช้ regexes เหล่านี้หลายครั้งและ
  • การใช้งานแบบต่อเนื่องของคุณในรูปแบบเดียวกันจะถูกคั่นด้วยมากกว่าre._MAXCACHEregexes อื่น ๆ ในระหว่างนั้นเพื่อให้แต่ละรายการถูกล้างออกจากแคชระหว่างการใช้งานแบบต่อเนื่อง
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}')

ตัวอย่างผลลัพธ์ที่ฉันได้รับจากแล็ปท็อปของฉัน (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

ฉันไม่ได้สนใจด้วยtimeitเพราะความแตกต่างนั้นช่างสิ้นเชิง แต่ฉันก็ได้ตัวเลขที่มีคุณภาพใกล้เคียงกันทุกครั้ง โปรดทราบว่าแม้ไม่มีการre.compileใช้ regex เดียวกันหลาย ๆ ครั้งและย้ายไปยังหน้าถัดไปก็ไม่เลว (เพียงประมาณ 2 เท่าช้ากว่าด้วยre.compile) แต่ในลำดับอื่น ๆ (วนซ้ำหลาย regexes) มันแย่ลงอย่างมีนัยสำคัญ , อย่างที่คาดไว้. นอกจากนี้การเพิ่มขนาดแคชทำงานมากเกินไป: เพียงการตั้งค่าre._MAXCACHE = len(patterns)ในsetup()ข้างต้น (แน่นอนผมไม่แนะนำให้ทำสิ่งต่างๆในการผลิตเป็นชื่อที่มีขีดตามอัตภาพ“ส่วนตัว”) หยด ~ 23 วินาทีกลับลงไป ~ 0.7 วินาทีซึ่งยัง ตรงกับความเข้าใจของเรา


PS: ถ้าฉันใช้รูปแบบ regex เพียง 3 รูปแบบในรหัสทั้งหมดของฉันแต่ละรูปแบบใช้ (โดยไม่ต้องมีลำดับเฉพาะ) หลายร้อยครั้งแคช regex จะเก็บ regex ที่คอมไพล์แล้วโดยอัตโนมัติใช่ไหม
Basj

@Basj ผมคิดว่าคุณก็สามารถลองดู :) แต่คำตอบที่ผมมั่นใจว่าสวยคือใช่: ค่าใช้จ่ายเพียง แต่เพิ่มเติมในกรณี AFAICT ที่เป็นเพียงที่เพียงแค่มองขึ้นไปในรูปแบบแคช โปรดทราบด้วยว่าแคชนั้นเป็นส่วนกลาง (ระดับโมดูล) ดังนั้นโดยหลักการแล้วคุณอาจมีห้องสมุดอ้างอิงที่ทำการค้นหา regex ระหว่างคุณดังนั้นจึงเป็นเรื่องยากที่จะมั่นใจอย่างเต็มที่ว่าโปรแกรมของคุณใช้ regex เพียง 3 ตัว (หรือจำนวนเท่าใดก็ได้) รูปแบบ แต่มันจะแปลกสวยที่จะเป็นอย่างอื่น :)
ShreevatsaR

0

นิพจน์ปกติจะถูกคอมไพล์ก่อนที่จะถูกใช้เมื่อใช้เวอร์ชันที่สอง หากคุณจะดำเนินการหลาย ๆ ครั้งมันแน่นอนดีกว่าที่จะรวบรวมมันก่อน หากไม่ได้รวบรวมทุกครั้งที่คุณจับคู่หนึ่งครั้งจะถือว่าใช้ได้


0

การกำหนดค่าตามความชอบโหลด / ความรู้ความเข้าใจ

ให้ฉันกำไรหลักคือว่าผมจะต้องจดจำและอ่านหนึ่งรูปแบบของความซับซ้อนไวยากรณ์ API regex - The <compiled_pattern>.method(xxx)รูปแบบมากกว่าที่และre.func(<pattern>, xxx)รูปแบบ

re.compile(<pattern>)เป็นบิตของสำเร็จรูปพิเศษจริง

แต่ในกรณีที่มีความกังวลเกี่ยวกับ regex ขั้นตอนการรวบรวมพิเศษนั้นไม่น่าจะเป็นสาเหตุใหญ่ของภาระการรับรู้ ในความเป็นจริงในรูปแบบที่ซับซ้อนคุณอาจได้ความชัดเจนจากการแยกการประกาศจากวิธีการ regex อะไรก็ตามที่คุณเรียกใช้

ฉันมักจะปรับรูปแบบที่ซับซ้อนในเว็บไซต์เช่น Regex101 หรือแม้แต่ในสคริปต์ทดสอบขั้นต่ำแยกต่างหากจากนั้นนำมาไว้ในรหัสของฉันดังนั้นการแยกการประกาศจากการใช้งานนั้นเหมาะกับเวิร์กโฟลว์ของฉันเช่นกัน


-1

ฉันต้องการกระตุ้นให้การคอมไพล์ล่วงหน้ามีทั้งแนวคิดและ 'ความรู้' (ใน 'การเขียนโปรแกรมเชิงความรู้') ได้เปรียบ ดูตัวอย่างรหัสนี้:

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

ในใบสมัครของคุณคุณจะเขียน:

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

นี่เป็นเรื่องง่ายในแง่ของการใช้งานเท่าที่จะทำได้ เพราะนี่เป็นตัวอย่างสั้นมากฉันทำให้วิธีรับ_text_has_foobar_re_searchทั้งหมดในหนึ่งบรรทัด ข้อเสียของรหัสนี้คือมันใช้หน่วยความจำเพียงเล็กน้อยสำหรับทุกสิ่งที่อายุการใช้งานของTYPOวัตถุไลบรารีคือ ข้อได้เปรียบคือเมื่อทำการค้นหา foobar คุณจะได้รับสองฟังก์ชันการโทรและการค้นหาพจนานุกรมสองระดับ จำนวนแคชที่แคชโดยreและค่าใช้จ่ายของแคชนั้นไม่เกี่ยวข้องที่นี่

เปรียบเทียบกับสไตล์ปกติด้านล่าง:

import re

class Typo:

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

ในแอปพลิเคชัน:

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

ฉันยอมรับว่าสไตล์ของฉันเป็นเรื่องผิดปกติอย่างมากสำหรับงูหลามบางทีอาจเป็นที่ถกเถียงกัน อย่างไรก็ตามในตัวอย่างที่ตรงกับวิธีที่ใช้ไพ ธ อนมากที่สุดเพื่อทำการจับคู่เดี่ยวเราจะต้องสร้างอินสแตนซ์ของวัตถุทำการค้นหาพจนานุกรมอินสแตนซ์สามรายการและทำการเรียกฟังก์ชันสามครั้ง นอกจากนี้เราอาจเข้าไปreปัญหาแคชเมื่อใช้มากกว่า 100 regexes นอกจากนี้การแสดงออกปกติจะถูกซ่อนอยู่ภายในร่างกายวิธีการซึ่งส่วนใหญ่ไม่ได้เป็นความคิดที่ดี

ไม่ว่าจะเป็นว่าทุกส่วนย่อยของมาตรการ --- กำหนดเป้าหมายแถลงการณ์การนำเข้าที่มีนามแฝง วิธีนามแฝงที่เกี่ยวข้อง; การลดการเรียกฟังก์ชันและการค้นหาพจนานุกรมวัตถุสามารถช่วยลดความซับซ้อนในการคำนวณและแนวคิด


2
WTF ไม่ใช่แค่คุณตอบคำถามเก่า ๆ ที่ตอบแล้วเท่านั้น รหัสของคุณไม่เป็นไปในทางที่ผิดและผิดในหลายระดับ - (ab) โดยใช้คลาสเป็น namespaces ที่โมดูลก็เพียงพอใช้ชื่อคลาสให้เป็นประโยชน์ ฯลฯ ... ดูpastebin.com/iTAXAWenเพื่อการใช้งานที่ดีขึ้น ไม่ต้องพูดถึง regex ที่คุณใช้เสียเช่นกัน โดยรวม, -1

2
รู้สึกผิด นี่เป็นคำถามเก่า แต่ฉันไม่รังเกียจที่จะ # 100 ในการสนทนาที่ช้าลง คำถามยังไม่ถูกปิด ฉันเตือนรหัสของฉันอาจเป็นปฏิปักษ์กับรสนิยมบางอย่าง ฉันคิดว่าถ้าคุณสามารถดูมันเป็นเพียงการสาธิตสิ่งที่ทำได้ในหลามเช่น: ถ้าเราทำทุกอย่างทุกอย่างที่เราเชื่อว่าเป็นตัวเลือกและจากนั้นคนจรจัดด้วยวิธีใดสิ่งที่สิ่งที่ดูเหมือนว่าเราสามารถ ได้รับ? ฉันแน่ใจว่าคุณสามารถมองเห็นข้อดีและ dismerits ของการแก้ปัญหานี้และสามารถบ่นมากขึ้น มิฉะนั้นฉันจะต้องสรุปข้อเรียกร้องของคุณเกี่ยวกับความผิดอาศัยน้อยกว่า PEP008
flow

2
ไม่มันไม่เกี่ยวกับ PEP8 นั่นเป็นเพียงการตั้งชื่อการประชุมและฉันจะไม่ลงคะแนนเพราะไม่ได้ติดตามพวกเขา ฉันลงคะแนนให้คุณเพราะรหัสที่คุณแสดงนั้นเขียนได้ไม่ดี มันท้าทายการประชุมและสำนวนโดยไม่มีเหตุผลและเป็นอวตารของการปรับให้เหมาะสมที่สุด: คุณต้องปรับออแกไนเซอร์ช่วงกลางวันจากรหัสอื่น ๆ ทั้งหมดเพื่อให้เป็นคอขวดและถึงแม้การเขียนครั้งที่สามที่ฉันเสนอจะสั้นกว่า เป็นไปตามสำนวนและเร็วโดยเหตุผลของคุณ (จำนวนการเข้าถึงคุณลักษณะเดียวกัน)

"เขียนไม่ดี" - เหมือนทำไมล่ะ "ท้าทายการประชุมและสำนวน" - ฉันเตือนคุณ "โดยไม่มีเหตุผล" - ใช่ฉันมีเหตุผล: ลดความซับซ้อนที่ความซับซ้อนไม่มีวัตถุประสงค์ "การจุติของการเพิ่มประสิทธิภาพก่อนกำหนด" - ฉันเป็นอย่างมากสำหรับรูปแบบการเขียนโปรแกรมที่เลือกความสมดุลของความสามารถในการอ่านและประสิทธิภาพ; OP ขอให้มีการอธิบาย "ประโยชน์ในการใช้ re.compile" ซึ่งฉันเข้าใจว่าเป็นคำถามเกี่ยวกับประสิทธิภาพ "(ab) ใช้คลาสเป็นเนมสเปซ" - เป็นคำที่ไม่เหมาะสม ชั้นเรียนอยู่ที่นั่นเพื่อให้คุณมีจุดอ้างอิง "ตัวเอง" ฉันพยายามใช้โมดูลเพื่อจุดประสงค์นี้ชั้นเรียนทำงานได้ดีขึ้น
flow

"ชื่อคลาสเป็นตัวพิมพ์ใหญ่", "ไม่มันไม่เกี่ยวกับ PEP8" - เห็นได้ชัดว่าคุณโกรธมากจนคุณไม่สามารถบอกอะไรได้ "WTF", " ผิด " --- ดูอารมณ์ของคุณว่าอย่างไร? กรุณาเที่ยงธรรมมากขึ้นและน้อยลง
flow

-5

ความเข้าใจของฉันคือว่าทั้งสองตัวอย่างมีความเท่าเทียมกันอย่างมีประสิทธิภาพ ความแตกต่างเพียงอย่างเดียวคือในครั้งแรกคุณสามารถใช้นิพจน์ปกติที่คอมไพล์แล้วที่อื่นโดยไม่ทำให้คอมไพล์นั้นถูกรวบรวมอีกครั้ง

นี่คือข้อมูลอ้างอิงสำหรับคุณ: http://diveintopython3.ep.io/refactoring.html

การเรียกใช้ฟังก์ชั่นการค้นหาของวัตถุรูปแบบที่คอมไพล์ด้วยสตริง 'M' จะทำสิ่งเดียวกันกับการเรียก re.search โดยมีทั้งนิพจน์ปกติและสตริง 'M' เท่านั้นเร็วกว่ามาก (อันที่จริงแล้วฟังก์ชั่น re.search เพียงรวบรวมการแสดงออกปกติและเรียกวิธีการค้นหาของวัตถุรูปแบบผลลัพธ์สำหรับคุณ)


1
ฉันไม่ downvote คุณ แต่ในทางเทคนิคนี้ไม่ถูกต้อง: งูหลามจะไม่ recompile อยู่ดี
Triptych
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.