大阪大学医学部 Python会

Now is better than never.

Pythonで使える正規表現まとめ

2018-09-12(Wed) - Posted by 西垣 in 技術ブログ    tag:Python

Contents

    O'REILLYの著書『退屈なことはPythonにやらせよう』で「正規表現」について記載されているところをまとめたノートです。

    正規表現はCtrl-Fのテキスト検索でも気軽に使えて大変便利なので、以下のまとめはpython3での正規表現の初歩的な使い方をまとめたものですが、知らなかった人は見てみてもいいかもです。

    要点

    メタ文字

    • ?は、直前のグループの0回か1回の出現にマッチする。(任意のマッチ)
    • *は、直前のグループの0回以上の出現にマッチする。
    • +は、直前のグループの1回以上の出現にマッチする。
    • {n}は、直前のグループのn回の出現にマッチする。
    • {n,}は、直前のグループのn回以上の出現にマッチする。
    • {,m}は、直前のグループの0~m回の出現にマッチする。
    • {n,m}??+?は直前のグループの非貪欲マッチを行う。

    文字集合

    • \d:0~9の数字
    • \D:0~9の数字以外
    • \w:文字、数字、下線(単語wordのw)
    • \W:文字、数字、下線以外
    • \s:スペース、タブ、改行(空白spaceのs)
    • \S:スペース、タブ、改行以外

    正規表現マッチのまとめ

    • reモジュール
    • re.compile()関数
    • search()メソッド
    • group()メソッド
    • findall()メソッド

    実践

    In [1]:
    #! python 3
    #正規表現モジュールのインポート
    import re
    

    '\d'は1文字の数字を表す正規表現

    \dを用いて、あるtextから、電話番号パターンにマッチする部分を検索しましょう。 re.compile()に正規表現パターンを表す文字列を渡すと、Regexオブジェクトが返ります。

    In [2]:
    phone_num_regex = re.compile(r'\d\d\d-\d\d\d\d-\d\d\d\d')
    

    文字列の前のrはraw文字列を指定し、文字をエスケープせずに簡潔に表現しています。

    search()メソッドとgroup()メソッド

    Regexオブジェクトのsearch()メソッドは、渡された文字列の中から正規表現にマッチするパターンが見つかれば、Matchオブジェクトを返します。 Matchオブジェクトにはgroup()メソッドがあり、実際にマッチしたテキストを返します

    In [3]:
    mo = phone_num_regex.search('私の電話番号は080-1111-22222です。')
    print('電話番号が見つかりました:' + mo.group())
    
    電話番号が見つかりました:080-1111-2222
    

    '()'を用いたグルーピング

    In [4]:
    phone_num_regex = re.compile(r'(\d\d\d)-(\d\d\d\d-\d\d\d\d)')
    mo = phone_num_regex.search('私の電話番号は080-1000-2000です。')
    
    In [5]:
    print(mo.group(0))
    print(mo.group(1))
    print(mo.group(2))
    print(mo.groups())
    
    080-1000-2000
    080
    1000-2000
    ('080', '1000-2000')
    

    '|'を使って複数のグループとマッチ

    例えばr'Batman|Spiderman'という正規表現は、'Batman'or'Spiderman'にマッチします。

    In [6]:
    hero_regex = re.compile(r'Batman|Spiderman')
    mo1 = hero_regex.search('Batman and Yasmizuman')
    mo1.group()
    
    Out[6]:
    'Batman'

    両方ある場合は最初に出現したほうが返ります。

    In [7]:
    mo2 = hero_regex.search('The Spiderman fights Batman and Yasumizuman')
    mo2.group()
    
    Out[7]:
    'Spiderman'

    丸カッコと縦線とを応用

    In [8]:
    yasumizu_regex = re.compile(r'Yasumizu(man|woman|hotel)')
    mo = yasumizu_regex.search('Yasumizuman lost his way back to Yasumizuhotel.')
    mo.group(1)
    
    Out[8]:
    'man'

    '?'を用いた任意のマッチ

    In [9]:
    yasu_regex = re.compile(r'Yasumizu(wo)?man')
    mo1 = yasu_regex.search('The Adveture of Yasumizuman')
    mo1.group()
    
    Out[9]:
    'Yasumizuman'
    In [10]:
    mo2 = yasu_regex.search('The Adveture of Yasumizuwoman')
    mo2.group()
    
    Out[10]:
    'Yasumizuwoman'

    '*'を用いた0回以上のマッチ

    In [11]:
    yasu_regex = re.compile(r'Yasu(mi)*zu')
    mo1 = yasu_regex.search('The Adventure of Yasuzu')
    mo1.group()
    
    Out[11]:
    'Yasuzu'
    In [12]:
    mo2 = yasu_regex.search('The Adventure of Yasumimimimimimimizu')
    mo2.group()
    
    Out[12]:
    'Yasumimimimimimimizu'

    '+'を用いた1回以上のマッチ

    In [13]:
    yasu_regex = re.compile(r'Yasu(mi)+zu')
    mo1 = yasu_regex.search('The Adventure of Yasumimimimimizu')
    mo1.group()
    
    Out[13]:
    'Yasumimimimimizu'
    In [14]:
    mo2 = yasu_regex.search('The Adventure of Yasuzu')
    mo2 == None
    
    Out[14]:
    True

    '{}'を用いて繰り返し回数を指定する

    例:(Ha){3} = (HaHaHa) , (Ha){3,5} = (HaHaHa|HaHaHaHa|HaHaHaHaHa)

    In [15]:
    ha_regex = re.compile(r'(Ha){3,5}')
    mo1 = ha_regex.search('HaHaHaHaHa')
    mo1.group()
    
    Out[15]:
    'HaHaHaHaHa'
    In [16]:
    mo2 = ha_regex.search('Ha')
    mo2 == None
    
    Out[16]:
    True

    貪欲マッチ:ジャガビーは一番長いものをとる。

    Pythonの正規表現は、デフォルトでは貪欲マッチです。つまり、複数の可能性があると最も長い方にマッチします。

    In [17]:
    greedy_Ha_regex = re.compile(r'(Ha){3,5}')
    mo1 = greedy_Ha_regex.search('HaHaHaHaHaHaHaHaHaHaHa')
    mo1.group()
    
    Out[17]:
    'HaHaHaHaHa'

    非貪欲マッチ:遠慮して一番短いものをとる

    閉じカッコの後に?

    In [18]:
    nongreedy_Ha_regex = re.compile(r'(Ha){3,5}?')
    mo2 = nongreedy_Ha_regex.search('HaHaHaHaHaHaHaHaHaHaHa')
    mo2.group()
    
    Out[18]:
    'HaHaHa'

    findall()メソッド

    search()が最初に見つかった文字列を返すのに対し、findall()は見つかったすべての文字列を返します。 findall()はタプルのリストを返すことに注意。

    In [19]:
    phone_num_regex = re.compile(r'\d\d\d-\d\d\d\d-\d\d\d\d')
    phone_num_regex.findall('Cell: 415-5555-9999 Work: 212-5555-0000')
    
    Out[19]:
    ['415-5555-9999', '212-5555-0000']
    In [20]:
    phone_num_regex = re.compile(r'(\d\d\d)-(\d\d\d\d)-(\d\d\d\d)')
    phone_num_regex.findall('Cell: 415-5555-9999 Work: 212-5555-0000')
    
    Out[20]:
    [('415', '5555', '9999'), ('212', '5555', '0000')]

    続きはありますが、あまり出しすぎるとネタが無くなるので、今回はここまでにしておきます。 また書きます。

    上記の内容をうまく利用して、自分で電子メールアドレスの正規表現を作ってみてもおもしろいと思います。

    正規表現について、さらに詳しく