python - مكتبة - هل يستحق استخدام recompile بايثون؟




مكتبة اكواد بايثون (15)

هل هناك أي فائدة في استخدام الترجمة للتعبيرات العادية في بايثون؟

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

ضد

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

https://code.i-harness.com


I've had a lot of experience running a compiled regex 1000s of times versus compiling on-the-fly, and have not noticed any perceivable difference

The votes on the accepted answer leads to the assumption that what @Triptych says is true for all cases. This is not necessarily true. One big difference is when you have to decide whether to accept a regex string or a compiled regex object as a parameter to a function:

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

It is always better to compile your regexs in case you need to reuse them.

Note the example in the timeit above simulates creation of a compiled regex object once at import time versus "on-the-fly" when required for a match.


(بعد مرور شهور) من السهل إضافة ذاكرة التخزين المؤقت الخاصة بك حول 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 ...

Wibni ، لن يكون من الجميل إذا: cachehint (الحجم =) ، cacheinfo () -> الحجم ، الزيارات ، nclear ...


أتفق مع صادقة آبي أن 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 ، (أ + ب) يعني:

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)

إذا لم نستخدم الفصل الدراسي (وهو طلبي اليوم) ، فعندئذ ليس لدي أي تعليق. ما زلت أتعلم استخدام المتغير الشامل في بايثون ، وأعرف أن المتغير العالمي هو أمر سيء.

نقطة أخرى ، أعتقد أن استخدام أسلوب (A) + (B) له يد عليا. فيما يلي بعض الحقائق كما لاحظت (يرجى تصحيح لي إذا كنت مخطئا):

  1. المكالمات مرة واحدة ، ستقوم بإجراء بحث واحد في _cache متبوعًا بـ sre_compile.compile() واحد لإنشاء كائن regex. المكالمات مرتين ، ستقوم بعمليتي بحث وتجميع واحد (لأن كائن regex يتم تخزينه مؤقتًا).

  2. إذا تم _cache بينهما ، فسيتم تحرير كائن regex من الذاكرة وستحتاج Python إلى الترجمة مرة أخرى. (يشير أحدهم إلى أن بايثون لن تعيد تجميعها).

  3. إذا احتفظنا بعنصر التعميم باستخدام (A) ، فسيظل كائن regex يدخل إلى _cache وسيحصل على مسح بطريقة ما. لكن شفرتنا تحتفظ بمرجع عليها ولن يتم تحرير كائن regex من الذاكرة. تلك ، بيثون لا تحتاج إلى ترجمة مرة أخرى.

  4. الاختلافات 2 ثانية في اختبار جورج compiledInLoop مقابل تجميع هو أساسا الوقت اللازم لبناء المفتاح والبحث في _cache. هذا لا يعني وقت تجميع regex.

  5. يظهر اختبار جورج الحقيقي تقريبًا ما يحدث إذا أعاد فعلًا الترجمة في كل مرة: سيكون أبطأ بمقدار 100 مرة (فقد خفض الحلقة من 1،000،000 إلى 10000).

في ما يلي الحالات الوحيدة التي تكون فيها (A + B) أفضل من (C):

  1. إذا كنا نستطيع تخزين مرجع كائن regex داخل فئة.
  2. إذا احتجنا إلى إجراء مكالمات (B) بشكل متكرر (داخل حلقة أو عدة مرات) ، فيجب علينا تخزين المرجع إلى كائن regex خارج الحلقة.

الحالة التي (C) جيدة بما فيه الكفاية:

  1. لا يمكننا تخزين مرجع.
  2. نحن نستخدمها فقط من حين لآخر.
  3. بشكل عام ، ليس لدينا الكثير من التعابير المنطقية (افترض أنه لا يمكن الحصول على مسح واحد مطلقًا)

مجرد خلاصة ، وهنا هي ABC:

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

شكرا للقراءة.


أجريت هذا الاختبار قبل التعثر في المناقشة هنا. ومع ذلك ، بعد تشغيله اعتقدت أنني على الأقل نشر نتائجي.

لقد سرقت ونسلطت على المثال في "تعبير التعبيرات العادية" لجيف فريدل. هذا هو على ماك بوك تشغيل OSX 10.6 (2 غيغاهرتز الأساسية 2 إنتل ، 4 غيغابايت من ذاكرة الوصول العشوائي). بيثون الإصدار هو 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

إلى جانب الأداء.

استخدام 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'

كمكمل ، أنا قدمت chitesheet شاملة من وحدة 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',]
}

باستخدام الأمثلة المعطاة:

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

طريقة المطابقة في المثال أعلاه ليست هي نفس الطريقة المستخدمة أدناه:

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

re.compile() إرجاع كائن تعبير عادي ، مما يعني أن h هو كائن regex.

يحتوي كائن regex على طريقة match الخاصة به مع معلمات pos و endpos الاختيارية:

match

نقاط البيع

يعطي معامل المعلمة الثانية الاختياري فهرسا في السلسلة حيث يبدأ البحث ؛ افتراضياً إلى 0. هذا لا يكافئ تشريح السلسلة؛ يطابق حرف نمط '^' في البداية الحقيقية للسلسلة وفي المواضع بعد السطر الجديد ، ولكن ليس بالضرورة في الفهرس الذي يبدأ البحث فيه.

endpos

يحدد المعلمة الاختيارية endpos مدى المسافة التي سيتم فيها البحث في السلسلة ؛ سيكون الأمر كما لو كانت السلسلة عبارة عن أحرف endpos طويلة ، لذلك سيتم البحث فقط عن الأحرف من pos إلى endpos - 1 لمطابقة. إذا كان endpos أقل من pos ، فلن يتم العثور على أي تطابق. وإلا ، إذا كان rx عبارة عن كائن تعبير عادي rx.search(string, 0, 50) ، فإن rx.search(string, 0, 50) يساوي rx.search(string[:50], 0) .

كما تدعم أساليب البحث في البحث المعتاد ، والبحث ، والأساسي ، هذه المعلمات.

re.match(pattern, string, flags=0) لا يدعمها كما يمكنك رؤيتها ،
ولا البحث ، و findall ، ونظرائهم.

يحتوي كائن المطابقة على سمات تكمل هذه المعلمات:

match.pos

قيمة pos التي تم تمريرها إلى أسلوب البحث () أو match () لكائن regex. هذا هو الفهرس في السلسلة التي بدأ فيها محرك RE يبحث عن مطابقة.

match.endpos

قيمة endpos التي تم تمريرها إلى طريقة البحث () أو match () لكائن regex. هذا هو الفهرس في السلسلة التي لا يتم بعدها تشغيل محرك RE.

يحتوي كائن regex على خاصيتين فريدتين ، ربما مفيدتين:

regex.groups

عدد مجموعات الالتقاط في النموذج.

regex.groupindex

قاموس يعين أي أسماء مجموعات رمزية محددة بواسطة (؟ P) إلى أرقام المجموعات. القاموس فارغ إذا لم يتم استخدام مجموعات رمزية في هذا النمط.

وأخيرًا ، يحتوي كائن المطابقة على هذه السمة:

match.re

كائن التعبير العادي الذي أسفرت طريقة المطابقة () أو البحث () عن مثيل المطابقة هذا.


بشكل عام ، أجد أنه من الأسهل استخدام العلامات (على الأقل من السهل تذكر كيف) ، مثل re.I عند تجميع الأنماط بدلاً من استخدام الإشارات المضمنة.

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

ضد

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

في الغالب ، هناك اختلاف بسيط ما إذا كنت تستخدم 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

يأتي التفكيك السابق من ملف tmp.py للحصول على tmp.py يحتوي على:

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

قد تصل هذه الإجابة في وقت متأخر ولكنها اكتشاف مثير للاهتمام. يمكن أن يؤدي استخدام الترجمة إلى توفير وقتك إذا كنت تخطط لاستخدام regex عدة مرات (وهذا مذكور أيضًا في المستندات). يمكنك أن ترى أدناه أن استخدام regex المترجم هو الأسرع عندما يتم استدعاء طريقة المطابقة مباشرة عليه. تمرير regex المترجم إلى re.match يجعل حتى أبطأ واعادة re.match مع سلسلة صدف في مكان ما في الوسط.

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

لقد حاولت هذا بنفسي. بالنسبة للحالة البسيطة المتمثلة في تحليل رقم من سلسلة وجمعها ، فإن استخدام كائن التعبير العادي المترجم يكون أسرع من استخدام أساليب re .

كما أشار آخرون ، فإن طرق re (بما في ذلك re.compile ) البحث عن سلسلة التعبير العادي في ذاكرة التخزين المؤقت re.compile المترجمة مسبقًا. لذلك ، في الحالة العادية ، التكلفة الإضافية من استخدام الأساليب re هي ببساطة تكلفة البحث في ذاكرة التخزين المؤقت.

ومع ذلك ، فإن فحص code ، يظهر أن ذاكرة التخزين المؤقت تقتصر على 100 تعبير. هذا يطرح السؤال ، كم هو مؤلم لتجاوز ذاكرة التخزين المؤقت؟ تحتوي الشفرة على واجهة داخلية re.sre_compile.compile التعبير العادي ، 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 مرة ، وليس مليون.


من المثير للاهتمام أن التجميع أثبت أنه أكثر فعالية بالنسبة لي (Python 2.5.2 on 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 تعقيبات على العكس ، فإن التعبير العادي هو ضعف سرعة


هذا سؤال وجيه. غالبًا ما ترى أشخاصًا يستخدمون re.compile بدون سبب. يقلل من قابلية القراءة. ولكن من المؤكد أن هناك الكثير من المرات التي يتم فيها طلب تجميع مسبق للتعبير. مثل عند استخدامه مرات متكررة في حلقة أو بعض هذه.

انها مثل كل شيء عن البرمجة (كل شيء في الحياة في الواقع). تطبيق الحس السليم.


يؤدي اختلاف الأداء جانباً ، باستخدام re.compile واستخدام كائن التعبير العادي المترجم للقيام بالمطابقة (أي عمليات متعلقة بالتعبير المعتاد) إلى جعل الأدق أكثر وضوحًا لوقت تشغيل Python.

أتيحت لي بعض التجارب المؤلمة في تصحيح بعض الأكواد البسيطة:

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] يتم استخدام x[columnIndex] كتعبير عادي!

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

في حالتي ، فإن استخدام re.compile يكون أكثر وضوحًا لغرض التعبير العادي ، عندما تكون القيمة مخفية للعين المجردة ، وبالتالي يمكنني الحصول على المزيد من المساعدة من فحص وقت التشغيل في Python.

لذا ، فإن أخلاقي من الدرس الخاص بي هو أنه عندما يكون التعبير العادي ليس مجرد سلسلة حرفية ، فيجب أن أستخدم re.compile للسماح لبيثون بمساعدتي في تأكيد افتراضاتي.


My understanding is that those two examples are effectively equivalent. The only difference is that in the first, you can reuse the compiled regular expression elsewhere without causing it to be compiled again.

Here's a reference for you: http://diveintopython3.ep.io/refactoring.html

إن استدعاء دالة البحث عن كائن النمط المترجم مع السلسلة 'M' يحقق نفس الشيء مثل استدعاء re.search بكل من التعبير العادي والسلسلة 'M'. فقط الكثير ، أسرع بكثير. (في الواقع ، تقوم وظيفة re.search ببساطة بتجميع التعبير العادي وتستدعي طريقة بحث كائن النمط الناتج لك.)


i'd like to motivate that pre-compiling is both conceptually and 'literately' (as in 'literate programming') advantageous. have a look at this code snippet:

from re import compile as _Re

class TYPO:

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

TYPO = TYPO()

in your application, you'd write:

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

this is about as simple in terms of functionality as it can get. because this is example is so short, i conflated the way to get _text_has_foobar_re_search all in one line. the disadvantage of this code is that it occupies a little memory for whatever the lifetime of the TYPO library object is; the advantage is that when doing a foobar search, you'll get away with two function calls and two class dictionary lookups. how many regexes are cached by re and the overhead of that cache are irrelevant here.

compare this with the more usual style, below:

import re

class Typo:

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

In the application:

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

I readily admit that my style is highly unusual for python, maybe even debatable. however, in the example that more closely matches how python is mostly used, in order to do a single match, we must instantiate an object, do three instance dictionary lookups, and perform three function calls; additionally, we might get into re caching troubles when using more than 100 regexes. also, the regular expression gets hidden inside the method body, which most of the time is not such a good idea.

be it said that every subset of measures---targeted, aliased import statements; aliased methods where applicable; reduction of function calls and object dictionary lookups---can help reduce computational and conceptual complexity.





regex