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は使わないでねっていう話でした。