Pythonのre.compileを使用する価値はありますか?


461

Pythonで正規表現のコンパイルを使用するメリットはありますか?

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

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

8
その他、2.6 re.subではフラグの引数を取らないという事実...
new123456

58
使用re.compileすると10〜50倍の改善が見られる場合があります。道徳的には、ということであるならば、あなたは(MAXCACHE = 100以上)正規表現の多くを持っているあなたがMAXCACHE以上にそれらを回ずつ(および分離の多くを使用して、各1がキャッシュからフラッシュされることを、間に正規表現:その使用します同じ1回の多くし、次のいずれかに上を移動してカウントされません)、そして、それは間違いなくそれらをコンパイルするのに役立つでしょう。それ以外の場合、違いはありません。
ShreevatsaR

8
ノートに一つの小さな事は正規表現を必要としない文字列に、ということである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の改善を示す例を含む回答を投稿できますか?ここに記載されているほとんどの回答は、実際には正確なケースで3倍の改善を示し、他のケースではほとんど改善されない場合があります。
Basj

1
@Basj完了、回答を投稿しまし。2013年12月にPythonを使っていたものを掘り下げることはしませんでしたが、最初に試みた簡単なことは同じ動作を示しています。
ShreevatsaR

回答:


436

コンパイル済みの正規表現をオンザフライでコンパイルする場合と比較して、何千回も実行した経験が多く、目に見える違いはありませんでした。明らかに、これは逸話であり、確かに反対する大きな議論はありませんコンパイルはありませんが、違いはごくわずかであることがわかりました。

編集:実際のPython 2.5ライブラリコードを一目見た後、Pythonは内部的にコンパイルされ、とにかくそれらを使用するたびに正規表現をキャッシュすることがわかります(の呼び出しを含むre.match())ので、正規表現がコンパイルされたときに変更するだけなので、まったく時間を節約できません-キャッシュをチェックするのにかかる時間のみです(内部のキー検索)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
あなたの結論はあなたの答えと矛盾しています。正規表現が自動的にコンパイルおよび保存される場合、ほとんどの場合、手動で行う必要はありません。
jfs

84
JF Sebastian、それは問題の正規表現が頻繁に使用され、使い捨てであることを意味しないことをプログラマーに知らせる信号として機能します。
kaleissin 2009年

40
それ以上に、アプリケーションのパフォーマンスが重要な部分でコンパイルとキャッシュヒットを受けたくない場合は、アプリケーションの重要ではない部分でコンパイルする前にコンパイルしておくのが最善です。 。
エディパーカー、

20
同じ正規表現を複数回再利用し、タイプミスの可能性を減らす場合、コンパイル済み正規表現を使用することの主な利点がわかります。一度だけ呼び出すと、コンパイルされていない方が読みやすくなります。
monkut 2009年

18
したがって、主な違いは、多数の異なる正規表現(_MAXCACHE以上)を使用している場合です。それらの一部は1回だけで、その他は何度も...その後、より多く使用される式のコンパイル済み式を保持することが重要です。いっぱいになったときにキャッシュからフラッシュされません。
Fortran 2009

133

私にとって、最大のメリットは re.compileは、正規表現の定義をその使用から分離できることです。

0|[1-9][0-9]*(先行ゼロなしの基数10の整数)などの単純な式でさえ、再入力する必要がなく、タイプミスがないか確認し、後でデバッグを開始するときにタイプミスがあるかどうかを再確認する必要があるほど複雑になる場合があります。 。さらに、numやnum_b10などの変数名を使用する方がに比べて優れてい0|[1-9][0-9]*ます。

文字列を保存してre.matchに渡すことは確かに可能です。ただし、これは読みにくくなります。

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

コンパイルとの比較:

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

かなり近いですが、2番目の最後の行は繰り返し使用するとより自然でシンプルに感じられます。


5
私はこの答えに同意します。多くの場合、re.compileを使用すると、可読性の高いコードが得られます。
カールマイヤー

1
ただし、逆の場合もあります。たとえば、ある場所で正規表現を定義し、一致するグループを別の場所で使用する場合などです。
ケンウィリアムズ

1
@KenWilliams必ずしもそうではありませんが、元の定義から遠く離れて使用されている場合でも、特定の目的のための適切な名前の正規表現は明確である必要があります。例えばus_phone_numberまたはsocial_security_number
ブライアンM.シェルドン

2
@ BrianM.Sheldonに正規表現に適切な名前を付けても、そのさまざまなキャプチャグループが何を表しているかを理解するのに役立ちません。
ケンウィリアムズ

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

したがって、同じ正規表現を頻繁に使用する場合は、re.compile(特に、より複雑な正規表現の場合)実行する価値があります。

時期尚早な最適化に対する標準的な議論が当てはまりますが、re.compile正規表現がパフォーマンスのボトルネックになる可能性があると思われる場合は、実際に使用することでそれほど明快さや単純さを失うとは思いません。

更新:

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(...)、つまり、コンパイルされた表現の舞台裏でのキャッシュが発生しないように見えるケース(最後の2つの実行間の引用符の違いに注意)を追加しました。


5
セットアップの引数がタイミングに含まれていないため、ここでの方法論の主な問題。したがって、2番目の例からコンパイル時間を削除し、最初の例で平均化するだけです。これは、最初の例が毎回コンパイルされるという意味ではありません。
トリプティク

1
はい、これは2つのケースの公平な比較ではないことに同意します。
Kiv

7
どういう意味かわかりますが、正規表現が何度も使用される実際のアプリケーションでは、どうなるでしょうか。
dF。

26
@ Triptych、@ Kiv:使用とは別に正規表現をコンパイルするポイントは、コンパイルを最小限に抑えることです。これをタイミングから削除することは、実際の使用を最も正確に表すため、dFが行うべきこととまったく同じです。コンパイル時間は、timeit.pyがタイミングをここで行う方法とは特に関係ありません。それはいくつかの実行を行い、最も短いものだけを報告します、その時点でコンパイルされた正規表現はキャッシュされます。ここに表示される追加のコストは、正規表現をコンパイルするコストではなく、コンパイル済みの正規表現キャッシュ(辞書)でそれを検索するコストです。
jemfinch 2010

3
@Triptych import reセットアップから移動する必要がありますか?測定したい場所がすべてです。Pythonスクリプトを何度も実行すると、import re時間がかかります。2つを比較するときは、タイミングのために2つのラインを分離することが重要です。はい、あなたはそれがあなたが時間を打つときになる時だと言います。比較は、一度ヒットした時間をとり、コンパイルによって短い時間のヒットを繰り返すか、キャッシュがコール間でクリアされたと想定されるたびにヒットすることを示しています。タイミングを追加するh=re.compile('hello')と明確になります。
トムMyddeltyn 16

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

したがって、この単純なケースを使用すると、1回しか一致しなくても、コンパイルはより高速になるようです。


2
これはどのバージョンのPythonですか?
カイルストランド14

2
それは実際には問題ではありません。ポイントは、コードを実行する環境でベンチマークを試すことです
david king

1
私にとって、1000ループ以上のパフォーマンスはほぼ同じです。コンパイルされたバージョンは、1-100ループの方が高速です。(Python 2.7と3.4の両方)。
Zitrax、2015年

2
私のPython 2.7.3セットアップではほとんど違いはありません。コンパイルの方が速い場合もあれば、遅い場合もあります。差は常に5%未満であるため、デバイスにはCPUが1つしかないため、差を不確実性の測定値として数えます。
ダッカロン

1
Python 3.4.3では、2つの別々の実行で見られました。コンパイルされたものを使用すると、コンパイルされていないものよりもさらに遅くなりました。
Zelphir Kaltstahl、2016年

17

私はこれを自分で試したところです。文字列から数値を解析して合計するという単純なケースでは、コンパイルされた正規表現オブジェクトを使用すると、reメソッドを使用した場合より約2倍高速になります。

他の人が指摘したように、reメソッド(を含むre.compile)は、以前にコンパイルされた式のキャッシュで正規表現文字列を検索します。したがって、通常の場合、reメソッドの使用による追加コストは、単にキャッシュルックアップのコストです。

ただし、コードを調べると、キャッシュが100式に制限されていることがわかります。これは、キャッシュをオーバーフローさせるのがどれほど痛いですか?コードには、正規表現コンパイラへの内部インターフェイスが含まれていますre.sre_compile.compile。これを呼び出す場合は、キャッシュをバイパスします。次のような基本的な正規表現では、約2桁遅くなります。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」メソッドは、キャッシュをバイパスする内部インターフェイスを使用します。各ループの繰り返しでコンパイルされるものは、100万回ではなく、10,000回だけ反復されることに注意してください。


コンパイルされた正規表現は、コンパイルされていないものよりもはるかに速く実行されることに同意します。実行したコンパイル済みの正規表現パターンを含むインデックスに従って辞書を作成した後、正規表現がコンパイルされておらず、フルランの予測が8時間になるたびに計算されたときに、10,000を超える文を実行し、正規表現を反復するためにループを作成しました。全体を2分間。上記の答えを理解できません...
Eli Borodach

12

私は正直な阿部に同意しmatch(...)ます。これらは1対1の比較ではないため、結果はさまざまです。私の返信を簡単にするために、問題の関数には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)は(D)を呼び出した後に(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(...)1回だけ呼び出されるようにするには、コンパイルされた正規表現オブジェクトをメモリに格納する必要があります。クラスを使用している場合は、オブジェクトを格納し、関数が呼び出されるたびに再利用できます。

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

クラスを使用していない場合(今日の私の要望です)、コメントはありません。私はまだPythonでグローバル変数を使用することを学んでいます、そして私はグローバル変数が悪いことを知っています。

もう1つ、(A) + (B)アプローチを使用することのほうが有利だと思います。ここに私が観察したいくつかの事実があります(私が間違っている場合は修正してください):

  1. Aを1回呼び出すと、1つの検索が実行され、_cache続いて1 つの検索が実行さsre_compile.compile()れて、正規表現オブジェクトが作成されます。Aを2回呼び出すと、2つの検索と1つのコンパイルが行われます(正規表現オブジェクトがキャッシュされているため)。

  2. _cache間にフラッシュが発生した場合、正規表現オブジェクトはメモリから解放され、Pythonは再度コンパイルする必要があります。(誰かがPythonが再コンパイルしないことを示唆しています。)

  3. (A)を使用して正規表現オブジェクトを保持する場合、正規表現オブジェクトは引き続き_cacheに入り、何らかの方法でフラッシュされます。しかし、コードはその参照を保持し、正規表現オブジェクトはメモリから解放されません。それらは、Pythonを再度コンパイルする必要はありません。

  4. Georgeのコンパイルされたテストとコンパイルされたループの2秒間の違いは、主にキーを作成して_cacheを検索するのに必要な時間です。正規表現のコンパイル時間を意味するものではありません。

  5. Georgeの本当にコンパイルしたテストは、毎回本当にコンパイルをやり直すとどうなるかを示しています。100倍遅くなります(ループを1,000,000から10,000に減らしました)。

(A + B)が(C)より優れている唯一のケースは次のとおりです。

  1. クラス内の正規表現オブジェクトの参照をキャッシュできる場合。
  2. (B)を繰り返し(ループ内または複数回)呼び出す必要がある場合は、ループの外側で正規表現オブジェクトへの参照をキャッシュする必要があります。

(C)で十分な場合:

  1. 参照をキャッシュできません。
  2. たまにしか使いません。
  3. 全体として、正規表現が多すぎません(コンパイルされたものがフラッシュされないことを前提としています)

要約すると、ここに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()

他の1人の回答者は、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を含むPYCファイルからのものです。

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

1
ある"def search(pattern, string, flags=0):"タイプミス?
phuclv 2017

1
場合は、その注意patternすでにコンパイルされたパターンで、キャッシュはオーバーヘッド顕著になる:ハッシングはSRE_Pattern高価であり、検索が持つたびに失敗したように、パターンは、キャッシュに書き込まれることはありませんKeyError
Eric Duminil 2017年

5

一般に、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.findallとにかく、3番目の引数としてフラグを使用することもできます。
aderchox

5

与えられた例を使用して:

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

上記の例の一致方法は、以下で使用されるものと同じではありません。

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

re.compile()正規表現オブジェクトを返しhます。つまり、正規表現オブジェクトです。

regexオブジェクトには、オプションのposパラメータとendposパラメータを持つ独自のmatchメソッドがあります。

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

位置

オプションの2番目のパラメーターposは、検索を開始する文字列内のインデックスを提供します。デフォルトは0です。これは、文字列をスライスすることと完全に同じではありません。'^'パターン文字は必ずしも必要ではないが、検索を開始するインデックスでは、文字列の実際の先頭で、ちょうど改行後の位置に一致します。

endpos

オプションのパラメータendposは、文字列を検索する距離を制限します。これは、文字列がendpos文字であるかのようになり、posからtoまでの文字のみendpos - 1が検索されます。場合endposのが以下であるPOS、一致が見つかりません。それ以外の場合、rxがコンパイル済みの正規表現オブジェクトの場合、rx.search(string, 0, 50)と同等rx.search(string[:50], 0)です。

正規表現オブジェクトのsearchfindall、およびfinditerメソッドもこれらのパラメーターをサポートします。

re.match(pattern, string, flags=0)あなたが見ることができるようにそれらをサポートせず、
そのsearchfindall、およびfinditerの対応物もサポートしません。

マッチオブジェクトは、これらのパラメータを補完する属性があります。

match.pos

正規表現オブジェクトのsearch()またはmatch()メソッドに渡されたposの値。これは、REエンジンが一致の検索を開始した文字列へのインデックスです。

match.endpos

正規表現オブジェクトのsearch()またはmatch()メソッドに渡されたendposの値。これは、REエンジンが通過しない文字列へのインデックスです。


正規表現オブジェクトは、 2つのユニークな、おそらく有用な属性があります。

regex.groups

パターン内のキャプチャグループの数。

regex.groupindex

(?P)で定義されたシンボリックグループ名をグループ番号にマッピングする辞書。パターンでシンボリックグループが使用されていない場合、辞書は空です。


そして最後に、一致オブジェクトには次の属性があります。

match.re

match()またはsearch()メソッドがこの一致インスタンスを生成した正規表現オブジェクト。


4

パフォーマンスの違いはさておき、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])]

Pythonは、「文字列は一致の属性を持たない」と不平を言っていましたがcomparex[columnIndex]実際には、の位置引数マッピングによって正規表現として使用されています!

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

私の場合、正規表現の目的がre.compileを使用することでより明確になり、その値が肉眼では見えないため、Pythonランタイムチェックからより多くの助けを得ることができます。

したがって、私のレッスンの教訓は、正規表現が単なるリテラル文字列ではない場合、re.compileを使用して、Pythonに仮定の主張を支援させることです。


4

re.compile()を使用すると、re.VERBOSEを使用して正規表現パターンにコメントを追加するという形で、さらに1つの特典があります。

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

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

これはコードの実行速度には影響しませんが、私のコメント習慣の一部であるため、この方法で実行するのが好きです。変更を加えたいときに、2か月後にコードの背後にあるロジックを思い出そうとすることに時間を費やすことは、まったく嫌いです。


1
回答を編集しました。言及することre.VERBOSEは価値があると私は思います、そしてそれは他の答えが省かれているように思われる何かを追加します。ただし、「まだコメントできないので、ここに投稿します」でリードすると、確実に削除されます。回答以外の目的で回答ボックスを使用しないでください。どこにでもコメントできるようになるまで(50担当)、1つまたは2つの良い答えしか得られないので、しばらくお待ちください。あなたがあなたを早くそこに連れて行ってはいけないことがわかっているときに回答ボックスにコメントを入れる。それはあなたの反対票と削除された回答を取得します。
skrrgwasme 2015年

4

Pythonのドキュメントによると

シーケンス

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

に相当

result = re.match(pattern, string)

しかし、使用 re.compile()、結果の正規表現オブジェクトを再利用するためにおよび保存する方が、単一のプログラムで式が複数回使用される場合により効率的です。

したがって、私の結論は、多くの異なるテキストに対して同じパターンを一致させる場合は、より適切にプリコンパイルすることです。


3

興味深いことに、コンパイルは私にとってより効率的です(Win XPのPython 2.5.2)。

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

上記のコードをそのまま1回実行すると、2 if行でコメントを逆にすると、コンパイルされた正規表現は2倍の速度になります。


2
dFのパフォーマンス比較と同じ問題。コンパイルステートメント自体のパフォーマンスコストを含めない限り、それは実際には公平ではありません。
カールマイヤー

6
カール、私は同意しません。コンパイルは1回だけ実行されますが、マッチングループは100万回実行されます
Eli Bendersky 2009

@eliben:私はカールマイヤーに同意します。コンパイルはどちらの場合でも行われます。トリプティクはキャッシングが関係していると言及しているため、最適な場合(キャッシュに留まる)は両方ともO(n + 1)ですが、re.compileを明示的に使用しない場合、+ 1の部分は隠されています。
パプリカ

1
独自のベンチマークコードを記述しないでください。標準ディストリビューションに含まれているtimeit.pyの使い方を学んでください。
jemfinch 2010

forループでパターン文字列を再作成する時間はどれくらいですか。このオーバーヘッドは些細なことではありません。
IceArdor 2014

3

ここでの議論に遭遇する前に、私はこのテストを実行しました。しかし、それを実行したので、少なくとも結果を投稿すると思いました。

Jeff Friedlの「Mastering Regular Expressions」の例を盗んで粗末にしました。これは、OSX 10.6(2 GHz Intel Core 2 Duo、4 GB RAM)を実行しているMacBook上にあります。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

この回答は遅れて届く可能性がありますが、興味深い発見です。正規表現を複数回使用することを計画している場合、コンパイルを使用すると時間を本当に節約できます(これはドキュメントにも記載されています)。以下では、matchメソッドが直接呼び出されたときに、コンパイル済みの正規表現を使用するのが最も速いことがわかります。コンパイル済みの正規表現を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

3

パフォーマンス以外にも。

を使用compileすると、1。module
(re)
2。正規表現オブジェクト
3.一致オブジェクトの概念を区別するのに役立ちます。正規表現の
学習を始めたとき

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

補足として、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を使用する価値があります。

re.compileを使用すると、コードをより動的にします。これは、再度コンパイルして再度実行する代わりに、すでにコンパイルされた正規表現を呼び出すことができるためです。このことは、次の場合に役立ちます。

  1. プロセッサーの取り組み
  2. 時間の複雑さ。
  3. 正規表現をユニバーサルにします(findall、search、matchで使用できます)
  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を使用するのをよく見ます。読みやすさが低下します。ただし、式を事前にコンパイルする必要がある場合はたくさんあります。ループなどで繰り返し使用するときのように。

それはプログラミングに関するすべてのようなものです(実際にはすべてのものです)。常識を適用します。


私の簡単なフリックスルーからわかる限り、NutshellのPythonは re.compile()なしでの使用に言及していないので、興味津々でした。
マット

正規表現オブジェクトは、もう1つのオブジェクトをコンテキストに追加します。私が言ったように、re.compile()がその場所にある多くの状況が存在します。OPによって与えられた例はそれらの1つではありません。
PEZ、2012年

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

wibniの場合、次の条件が良いと思いませんか:cachehint(size =)、cacheinfo()-> size、hits、nclear ...


1

コンパイル済みの正規表現をオンザフライでコンパイルする場合と比較して、何千回も実行した多くの経験があり、認識できる違いに気づいていません

承認された回答に対する投票は、@ Triptychの発言がすべてのケースに当てはまるという仮定につながります。これは必ずしも真実ではありません。1つの大きな違いは、関数のパラメーターとして正規表現文字列またはコンパイル済み正規表現オブジェクトを受け入れるかどうかを決定する必要がある場合です。

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

再利用する必要がある場合に備えて、常に正規表現をコンパイルすることをお勧めします。

上記のtimeitの例は、コンパイル時に正規表現オブジェクトの作成をインポート時に1回シミュレートするのではなく、一致に必要なときに「オンザフライ」でシミュレートすることに注意してください。


1

別の答えとして、これまでに言及されていないことがわかったので、先に進んでPython 3のドキュメントを引用します。

これらのモジュールレベルの関数を使用する必要がありますか、それともパターンを取得してそのメソッドを自分で呼び出す必要がありますか?ループ内で正規表現にアクセスしている場合、それをプリコンパイルすると、いくつかの関数呼び出しが節約されます。ループの外では、内部キャッシュのおかげでそれほど大きな違いはありません。


1

リクエストに応じてre.compile、使用が50倍以上高速になる例を次に示します。

要点は、上のコメントで述べたのと同じです。つまり、re.compileコンパイルキャッシュのメリットがあまりない場合などに使用すると、大きなメリットが得られます。これは、少なくとも1つの特定のケース(私が実際に遭遇したケース)で、つまり次のすべてが当てはまる場合に発生します。

  • あなたは多くの正規表現パターンを持っています(より多くre._MAXCACHEデフォルトは現在512です)、そして
  • これらの正規表現を何度も使用し、
  • 同じパターンの連続する使用は、re._MAXCACHEその間にある他の正規表現よりも多く分離されているため、連続する使用の間にそれぞれがキャッシュからフラッシュされます。
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、同じ正規表現を複数回使用して次の正規表現に進むのはそれほど悪くはありませんでした(の場合の約2倍の速度しかありませんre.compile)が、逆の順序(多くの正規表現をループする)の場合は、かなり悪いことに注意してください、 予想通り。また、キャッシュサイズを増やすことも機能します。上記の設定を行うだけです(もちろん、アンダースコア付きの名前は通常「プライベート」であるため、本番環境re._MAXCACHE = len(patterns)ではsetup()このようなことはお勧めしません)、〜23秒を〜0.7秒に戻します。私たちの理解に一致します。


PS:コード全体で3つの正規表現パターンのみを使用し、それぞれが何百回も(特定の順序なしで)使用される場合、正規表現キャッシュはプリコンパイルされた正規表現を自動的に保持しますか?
Basj

@Basj私はあなたがそれを試してみて見ることができると思います:)しかし、答えは確かにそうです、その場合の唯一の追加費用はAFAICTが単にキャッシュ内のパターンを調べることだけです。キャッシュはグローバル(モジュールレベル)であることにも注意してください。原則として、依存関係ライブラリが正規表現の検索を実行している可能性があるため、プログラムが3(または任意の数)の正規表現しか使用していないことを完全に確信するのは困難です。パターンですが、それ以外の場合はかなり奇妙
です

0

正規表現は、2番目のバージョンを使用するときに使用される前にコンパイルされます。何度も実行する場合は、最初にコンパイルすることをお勧めします。一度にコンパイルするたびにコンパイルしない場合は問題ありません。


0

読みやすさ/認知負荷の好み

私にとっての主な利点は、複雑な正規表現API構文の1つの形式(それでは<compiled_pattern>.method(xxx)なく形式re.func(<pattern>, xxx)形。

re.compile(<pattern>)真、余分な定型のビットです。

しかし、正規表現が関係している場合、その余分なコンパイル手順が認知負荷の大きな原因になることはほとんどありません。そして実際、複雑なパターンでは、呼び出しを実行する正規表現メソッドから宣言を分離することで、さらに明確になる場合があります。

私はまずRegex101のようなWebサイトまたは別の最小限のテストスクリプトでさえも複雑なパターンを調整し、それをコードに組み込んで、宣言をその使用から分離することも私のワークフローに適合します。


-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すべてを1行で表示する方法を確認しました。このコードの欠点は、TYPOライブラリオブジェクトの存続期間が何であっても、メモリを少ししか使用しないことです。利点は、foobar検索を実行するときに、2つの関数呼び出しと2つのクラスディクショナリルックアップで済むことです。キャッシュされる正規表現の数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 ) )

私のスタイルはpythonで非常に珍しく、多分議論の余地があるかもしれません。ただし、Pythonのほとんどの使用方法とより厳密に一致する例では、単一の一致を実行するために、オブジェクトをインスタンス化し、3つのインスタンスディクショナリルックアップを実行し、3つの関数呼び出しを実行する必要があります。さらに、re 100を超える正規表現を使用すると、キャッシュの問題が発生する。また、正規表現はメソッド本体の中に隠されますが、ほとんどの場合、これはあまり良い考えではありません。

メジャーのすべてのサブセット---対象とされたエイリアスのインポートステートメントと言われている; 該当する場合、エイリアスメソッド。関数呼び出しとオブジェクトディクショナリルックアップの削減---計算と概念の複雑さを減らすのに役立ちます。


2
WTF。あなただけが古くて答えられた質問を見つけ出すだけではありません。あなたのコードは慣用的ではなく、非常に多くのレベルで間違っています-(ab)モジュールが十分な名前空間としてクラスを使用する、クラス名を大文字にするなど... より良い実装についてはpastebin.com/iTAXAWenを参照してください。あなたが使用する正規表現はもちろん壊れています。全体として、-1

2
有罪。これは古い質問ですが、スローダウンした会話で100番になってもかまいません。質問は閉じられていません。私のコードは一部の好みに反する可能性があると警告しました。私があなたがそれをpythonで何ができるかの単なるデモンストレーションと見なすことができると私は思います:私たちはすべてを取り、私たちが信じているすべてをオプションとして取り、そしてどのような方法でも一緒にいじくり回すと、私たちができるものはどのように見えるのでしょうか取得する?このソリューションの長所と短所を識別でき、より明確に文句を言うことができると思います。そうでなければ、私はあなたが間違っているというあなたの主張はPEP008より少しに依存していると結論しなければなりません
フロー

2
いいえ、それはPEP8についてではありません。これは単なる命名規則であり、私はそれらに従わないことに対して反対票を投じることはありません。あなたが示したコードは単に不十分に書かれているため、私はあなたに反対票を投じました。それは理由もなく慣習やイディオムに逆らうものであり、時期尚早の最適化の化身です。これがボトルネックになるためには、他のすべてのコードから日常の光を最適化する必要があります。あなたの推論によって慣用的同じくらい速い(同じ数の属性アクセス)。

「貧弱に書かれている」-なぜ正確に?「慣習と慣用句に反する」-私はあなたに警告しました。「理由もなく」-はい、私には理由があります。複雑さが目的を果たさない場合は簡略化してください。「時期尚早の最適化の化身」-私は、可読性と効率のバランスを選択するプログラミングスタイルに非常に適しています。OPは、「re.compileを使用する利点」の引き出しを求めました。これは、効率に関する質問として理解しています。「(ab)クラスを名前空間として使用する」-乱用するのはあなたの言葉です。クラスが存在するため、「自己」参照ポイントがあります。私はこの目的でモジュールを使用してみましたが、クラスはよりうまく機能します。
フロー

「クラス名を大文字にする」、「いいえ、それはPEP8に関するものではありません」-どうやら、とんでもないほど怒って、最初に何をビッカースにするかさえわからないようです。「WTF」、「間違っている」---あなたがどれほど感情的であるかを見てください。客観性を高め、泡立ちを少なくしてください。
フロー

-5

私の理解では、これら2つの例は事実上同等です。唯一の違いは、最初の部分では、コンパイルされた正規表現を他の場所で再利用できるため、再度コンパイルされることはありません。

ここにあなたのためのリファレンスがあります:http : //diveintopython3.ep.io/refactoring.html

コンパイルされたパターンオブジェクトの検索関数を文字列「M」で呼び出すと、正規表現と文字列「M」の両方でre.searchを呼び出すのと同じ結果になります。はるかに、はるかに高速です。(実際、re.search関数は正規表現をコンパイルし、結果のパターンオブジェクトの検索メソッドを呼び出すだけです。)


1
私はあなたに反対票を投じませんでしたが、技術的にはこれは間違っています:Pythonはとにかく再コンパイルされません
Triptych
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.