Pythonの正規表現で半角数字の判定に「\d」は使うべきではない
環境
Python3.8.2
はじめに
タイトルの通りPythonのの正規表現で半角数字の判定に\d
を使って判定しないほうが良いよって話です。
理由は以下の二点
- 厳密には半角整数の判定ではない
- パフォーマンスが悪い
また、この記事内で単に正規表現と書いている部分はPythonの標準ライブラリのreモジュールの正規表現を指しています。
厳密には半角数字の判定ではない
では、まず一点目
正規表現を習うとき、
「数字を判定するときには[0-9]
か\d
って書きますよー」
なんて話を聞きますが、Pythonの文字列の正規表現においては、[0-9]
と\d
は同じ意味ではありません。
Pythonの公式ドキュメントにも記載があります。
https://docs.python.org/ja/3/library/re.html#index-28
\d
Unicode (str) パターンでは: 任意の Unicode 10 進数字 (Unicode 文字カテゴリ [Nd]) にマッチします。これは [0-9] とその他多数の数字を含みます。 > ASCII フラグが使われているなら [0-9] のみにマッチします。 8 ビット (bytes) パターンでは: 任意の 10 進数字にマッチします。これは [0-9] と等価です。
文字列が半角数字だけで構成されているかを判定するのに\d
を利用してバリデーションを行っている人は今すぐやめましょう。
思わぬ不具合を生む原因となります。
Unicode 文字カテゴリ [Nd]には、全角の「1」や「➀」などの特殊記号も含まれています。
パフォーマンスが悪い
続いて二点目
一点目が大きく絡む話ですが、/d
がUnicode 文字カテゴリ [Nd]を判定している都合、[0-9]
よりも比較の対象になる文字の種類が多いわけです。
つまり、[0-9]
が10文字との比較で済むのに対して、/d
はUnicode 文字カテゴリ [Nd]すべてが比較対象となりうるので、比較の回数が大きく増えます。
数桁ぐらいなら大した、差にはならないかもしれませんが、これが何十桁にもなってくると組み合わせの数は、何倍にも膨れ上がります。
試しに簡単な正規表現でパフォーマンスを計測してみます。
import re
from timeit import timeit
d = re.compile(r'\d+')
n = re.compile(r'[0-9]+')
print(timeit("""d.match("0123456789")""", number=1_000_000, globals=globals()))
print(timeit("""n.match("0123456789")""", number=1_000_000, globals=globals()))
0.43016938499999996
0.37247920799999995
このように僅かではありますが差が出ます。
最後に
以上、Pythonの正規表現で半角数字を判定するときに/d
は使わないでねっていう話でした。