『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

繰り返しパターンと任意の文字列

文字列の繰返しにマッチする正規表現

+* などのメタ文字を使うと、文字列の繰返しにマッチする正規表現 をつくることができます。

1回以上の繰返し

+ は直前の文字の1回以上の繰返しを意味するメタ文字です。たとえば、は+
 
ははは
はははは
あははは
 
などの文字列にマッチします。

# PYTHON_REGEX_REPEAT_MATCH

# In[1]

# 正規表現オブジェクトを作成
regex = re.compile(r'あ+')

# 検索対象となる文字列を作成
line_1 = "あ~あ。牛乳をこぼしちゃった。"

line_2 = "ああっ! しまった! 宿題を忘れた!"

# line_1を検索
f1 = regex.findall(line_1)

# line_2を検索
f2 = regex.findall(line_2)

print("line1の検索結果", f1)
print("line2の検索結果", f2)

# line1の検索結果 ['あ', 'あ']
# line1の検索結果 ['ああ']

( ) で括った文字列の後ろに+ を付けると、( ) の中の文字列が繰り返されるパターンとマッチするようになります。

# In[2]

regex = re.compile(r'(くた)+')

# 検索対象となる文字列を作成
line = "Pythonやり過ぎてくたくたに疲れた。"

# lineを検索
s = regex.search(line)

print("lineの検索結果", s)
# <_sre.SRE_Match object; span=(11, 15), match='くたくた'>

0 回以上の繰返し

* は直前の文字の 0 回以上の繰返しを意味するメタ文字です。お*
 
おっす!
おおっと!
おいおい。
 
などの文字列にマッチしますが、abcdeあいうえお など、他のどんな文字列ともマッチしてしまいます。

したがって、* は2文字以上の後ろの添えるのが一般的です。たとえば、おい!* という正規表現の場合、* の直前にある文字は ! ですから、
 
おい
おい!!
おい!!!
 
のような文字列にマッチすることになります。つまり、! はあってもなくてもいい、という条件で検索します。

# In[3]

regex = re.compile(r'おい!*')

# 検索対象となる文字列を作成
line_1 = "このお菓子は、とてもおいしいですね。"

line_2 = "おい!! 何やってるんだよ!!"

line_3 = "おいおい。そりゃないだろ。"

# line_1を検索
f1 = regex.findall(line_1)

# line_2を検索
f2 = regex.findall(line_2)

# line_3を検索
f3 = regex.findall(line_3)

print("line1の検索結果", f1)
print("line2の検索結果", f2)
print("line3の検索結果", f3)

# line1の検索結果 ['おい']
# line2の検索結果 ['おい!!']
# line3の検索結果 ['おい', 'おい']

繰り返し回数の指定

{m,n} は直前の文字の m 回以上、n 回以下の繰り返しを表すメタ文字です。たとえば、わは{3,5} が 3 回以上、5 回以下の文字列にマッチします。つまり、
 
わはははは
 
のような文字列にはマッチしますが、
 
わはは
 
にはマッチしません。

# In[4]

regex = re.compile(r'わは{3,5}')

# 検索対象となる文字列を作成
line = "わはは。わはははは。"

# sentenceを検索して適合した文字列をすべて取得
f = regex.findall(line)

print(f)
# ['わはははは']

n を省略して {m,} のように書くこともできます。{0,}* と、{1,}+ と同じ意味をもちます。

あらゆる文字列にマッチする正規表現

.* は「改行以外の任意の 1 文字の 0 回以上の繰返し」なので、改行を除くあらゆる文字列にマッチするメタ文字 です。このような正規表現は、たとえば「住所:」や「氏名:」などの後に続く文字列を取り出したいときなどに使います。

# In[5]

regex = re.compile('住所:(.*)')

# 住所
line = '住所:東京都豊島区南池袋 x-y-z あとりえこばと出版'

# グループ1に適合する文字列を取得
g1 = regex.search(line).group(1)

print(g1)
# 東京都豊島区南池袋 x-y-z あとりえこばと出版

re.compile() のフラグに re.S または re.DOTALL を指定すると、. は改行を含む任意の 1 文字を表すメタ文字になります。

# In[6]

# 正規表現オブジェクトを作成
# フラグにre.Sを渡して改行を含むように設定
regex = re.compile('氏名:(.*)\n住所:(.*)', re.S)

# 検索対象文字列
my_data = '氏名:城戸涼音\n\
住所:東京都文京区小石川 x-y-z'

# my_dataを検索
s = regex.search(my_data)

# グループ1に適合する文字列を取得
g1 = s.group(1)

# グループ2に適合する文字列を取得
g2 = s.group(2)

print(g1)
print(g2)

# 城戸涼音
# 東京都文京区小石川 x-y-z

コメント

  1. norimitsu. より:

    別件ですが、ディレクトリ作成方法 分からなくて困っています。
    vn = int(input”数字入力してください”)#4桁の数字を入力する。

    save_dir =”./vn”
    os.mkdir(save_dir)
    実行結果は ”vn”のフォルダーが作成されました。 目的はnnnnのフォルダーを作成。
    入力した数字4桁 nnnn を vn に置き換えたい、どの様すると良いのかを知りたい。

    • BlogCat より:

      次のコードを試してみてください。
      vn = input(“数字入力してください”)
      save_dir = “./{}”.format(vn)
      os.mkdir(save_dir)

  2. HNaito より:

    「0 回以上の繰り返し」の説明で、「つまり、! はあってもなくてもいい」という解説は、今まで「0回」の解釈で何となく暗中模索だった自分には光が射した思いでした。

    「繰り返し回数の指定」の説明文で、
    {m, n} は m 回以上、n 回以下を表す → {m, n} は直前の文字の m 回以上、n 回以下の繰り返しを表す
    のほうが、説明文が統一されてわかりやすいと思いました。

    • あとりえこばと より:

      私も初めて「0回以上の繰り返し」という説明文を読んだ時に頭が大変混乱したことを覚えています。プログラミングの説明って、こういう独特な表現をすることが多いですよね。わからないときには、自分であれこれ試行錯誤で実行してみて「ああ、こういうことなんだ」と納得してから、なるべくわかりやすい表現を探すようにしています。

      「{m, n} は m 回以上、n 回以下を表す」はひどい説明文でしたね … 直しておきました。いつも的確なご指摘をしていただき、ありがとうございます。m(_ _)m

  3. HNaito より:

    最近気づいたのですが、回数指定の{ }内に空白を書いてしまうとちゃんとマッチできなくなるようです。たとえば REGEX_REPEAT_MATCH_04 の {3,5} のどこでも{ }内にスペースを入れてしまうと regex.findall(line) は []を返してきます。ドキュメントを見てもとくに何も書いてないようなのですが、原因をご教示いただけるとありがたいです。

    • HNaito より:

      一応解決しました。基本的には正規表現中の空白は無視されない。無視してほしければ、re.compile( ) の第 2 引数に re.VERBOSE を渡せばこの制限は緩和されるが、{ } 内の空白は無視されないようです。

  4. あとりえこばと より:

    ありがとうございます。re.VERBOSE という引数は初めて知りました。昨日のコメントを見て私も色々調べていたのですが、正規表現だけでなく、文字列の format メソッドの {} も、空白を入れると書式設定が反映されなくなるようです。