Regex

目次

正規表現

参考サイト


正規表現の記法は2種類

  • 以下の「”regex”」箇所に
  • 検索したい文字列の正規表現パターンを記載する(文字列だから””で囲む)
  • p: pattern or regexObject
  • r:raw文字列(エスケープシーケンスを回避)
  • どちらの書き方でもよい
  • 以下2つの書き方があるため、正規表現は最初とっつきにくい

パターン1:事前にコンパイル

  • 正規表現を別でコンパイルしてから、検索するパターン
    • =正規表現オブジェクトを作ってからそのメソッドを呼び出すケース
    • =コンパイル済み正規表現のメソッドとして利用するケース、とも言う
  • 同じ正規表現を複数回使うときやループの内側で使う場合は関数呼び出しを減らすため有効
  • (以降の解説はパターン1の記法も多い)
  • ループの外側で使うならパターン1,2どちらでもよいが、汎用性からこのパターン1を使う
import re
p = re.compile(r"regex")
match = p.search("string")
m.group()

# ex.
p = re.compile(r"P....n")
m = p.search("Hello Python")
m.group()
# >'Python'

パターン2:関数の中でコンパイル

  • モジュールレベルの関数、とも言う
  • 正規表現オブジェクトを前もってコンパイルする必要がない(使える変数が減る?)
  • (たぶんpositonが使えない?)
import re
m = re.search(r"regex", "string")
m.group()

m = re.search(r"P....n", "Hello Python")
m.group()
# >'Python'

メタキャラクタ

  • 一般の文字とは異なりその文字自身に特別な意味が割り当てられている文字のこと
記号意味合致例
\d任意の十進数とマッチ
[0-9]と同じ
\d\d\d
\d{3}
123%
4567
890abc
\D任意の非数字とマッチ
[^0-9]と同じ
\D{3}890abc
\s任意の空白文字とマッチ
[⎵\t\n\r\f\v]と同じ
abc⎵123abc123
\S任意の非空白文字とマッチ
[^⎵\t\n\r\f\v]と同じ
abc⎵123abc123
\w任意の英数文字とマッチ
[a-zA-Z0-9_]と同じ
abc⎵123abc123
\W任意の非英数文字とアンダーバーとマッチ
[^a-zA-Z0-9_]と同じ
abc⎵123abc123
記号意味合致する文字列
*先行する文字、部分式、角括弧文字の 0個以上と合致a*b*aaaaaa,aaabbbb,bbbbb
+〃 の 1個以上と合致a+b+
(long⎵)+
aaaaaab,aaabbbb,abbbbb
long⎵long⎵ago
?〃の0個または1個以上と合致
{0,1}と同じ
AB?C
Java⎵?SE
AB,ABC
Java⎵SE, JavaSE
[]角括弧内の任意の文字(すなわちどれでもよい)[A-Z]*APPLE,CAPITAL,QWERY
()まとめた部分式
(正規表現の「演算順序」で最初に評価される)
(a*b)*aaabaab,abaaab,ababaaab
{m}直前の正規表現のm回の繰り返し(Go!⎵){2}Go! Go! Go!
Go! Go! Go! Go!
{m,n}先行する文字、部分式、角括弧文字を
m個からn個(両端含む)と合致
a{2,3}b{2,3}aabbb,aaabbb,aabb
[^]角括弧にない文字と合致[^A-Z]apple,lowercase,qwerty
|パイプで区切られた文字や部分式の
どちらかに合致
b(a|i|e)dbad,bid,bed
.任意の1文字(記号、数字、空白等を含む)と合致(改行以外)b.dbad,bzd,b$d,b d
^文字または部分式が文字列の先頭にあることを示す
文字列内に改行文字があってもそこを含めマッチする
^a
^This.*
apple,asdf,a
This is a pen.
$正規表現の末尾として使う
これがないと全正規表現は「.*」を末尾に持ち
文字列の先頭部分と合致すること意味する
[A-Z]*[a-z]*$
line$
ABCabc,zzzyx.Bob

1_line
2_line
\エスケープ文字\^\|\\
\.
^|\
.
\b単語の境界下部参照
?!「含まない」を意味する^((?![A-Z]).)*$no-caps-here,$ymb01s a4e f!ne
\n事前に( )で囲まれた部分正規表現に
マッチした文字列
^(…)\1abcabcabc
# \b 単語の境界

p = re.compile(r'book')
p.findall('book,notebook,bookstore')
# > ['book', 'book', 'book']

p = re.compile(r'\bbook\b')
p.findall('book,notebook,bookstore')
# > ['book']


正規表現、引数の意味

  • 正規表現とは
    • 正しく規則性がある表現をした文字列、のこと
    • つまりそのままでは単なる文字列
    • 検索で使うには、コンパイルしてコンピューターに解釈してもらう必要がある
    • コンパイルされた正規表現(regex)=正規表現オブジェクト(regexObject)
    • 事前にコンパイルするか、関数内でコンパイルするかでregexObjectまたはpatternが使われる場所が変わる
キーワード・引数意味
regex正規表現(Regex Expression, RegExp等)←文字列で指定する
エスケープシーケンスは以下を利用する
・raw文字列「r“**”」
・pythonの「\
regexObject
pattern と記載されることが多い)
正規表現オブジェクト
パターンオブジェクト、プリコンパイルともいう
(⇔reモジュール)

string検索対象の文字列
flags処理モード(コンパイルフラグ)
pos検索の開始位置
endpos検索の終了位置

正規表現オブジェクトを生成する

p = re.compile(regex[, flags])

  • 戻り値:regexObject(正規表現オブジェクト)
  • 上記の記法はプリコンパイル(あらかじめコンパイルしておく方法)
  • つまり、パターン1のやり方
  • cf.もう一方の記法はモジュールレベル関数(re.match("regex", "string"))

  • reモジュールのメソッドcompileは、指定された文字列をコンパイルして、コンパイル済みの正規表現オブジェクトを生成する
  • reモジュールはimport reで利用可
  • r“正規表現” ←raw文字列でエスケープシーケンスを無効にする
regex = re.compile(r'\d+') 
# ↑本来はregexObjectと書くべきだが

p = re.compile(reflex)、でもよい

コンパイルフラグ

  • 参照
  • パターン1、2いずれでも利用可
  • 使用する際は先頭にreモジュールが必要
    • re.DOTALLre.S
    • re.VERBOSEre.X
Flag(省略形)意味
ASCII、A\w\b\s, そして \d などをそれぞれのプロパティをもつ ASCII 文字だけにマッチさせます
DOTALL、S. を改行を含む任意の文字にマッチするようにします。
IGNORECASE、I大文字小文字を区別しないマッチを行います。
LOCALE、Lロケールに対応したマッチを行います。
MULTILINE、M^ や $ の意味を変更し、複数行文字列に対するマッチングを行います。
VERBOSE、X(=extend)冗長な正規表現を利用できるようにして、よりきれいで理解しやすくまとめることができます。

メソッド概要

メソッド目的
search( )正規表現パターンがどこにマッチするかを調べる
戻り値:matchオブジェクト or None
match( )文字列の先頭で正規表現パターンとマッチするかを調べる
戻り値:matchオブジェクト or None
findall( )正規表現パターンにマッチする部分文字列をすべて探して「リスト」で返す
戻り値:リスト or 空リスト
finditer( )正規表現パターンにマッチする部分文字列をすべて探して、イテレーターで返す
matchオブジェクトを要素とするイテレーターのため、groupで取り出す必要あり
戻り値:イテレーター or ???
sub( )正規表現パターンにマッチする部分を置換する
戻り値:置換された後の文字列
split( )正規表現パターンにマッチする部分で分割する
戻り値:文字列をパターンで分割したリスト

matchオブジェクト

  • 上記メソッドでマッチした場合、matchオブジェクトが戻ってくる(findall( )は除く)
  • matchオブジェクトは以下のメソッドで取り出す
メソッド目的
group( )正規表現にマッチした文字列を返す
m.group()またはm.group(0):マッチした全体を返す
m.group(1):最初のマッチ文字列を返す
start( )マッチの開始位置を返す
end( )マッチの終了位置を返す
span( )マッチの位置(start, end)を含むタプルを返す
regex = re.compile("(.+),(.+),(.+)") # 末尾にカンマは不要
match = regex.search("山田一郎,15,男性")

match.group() # or match.group(0)
# > '山田一郎,15,男性'

match.group(1)
# >'山田一郎'

match.group(2)
# >'15'

match.group(3)
# >'男性'

match.span()
# > (0,10)

メソッドの使い方

search

文字列中のいずれかの場所でマッチ

  • regexOjbect.search(string[, pos[, endpos])
    • 戻り値:マッチ成功時は matchオブジェクト、失敗時は None
import re
regex = re.compile(r'\d+')

# マッチありの場合
m = regex.search('abc123')
m.group()
# > '123'

# マッチなしの場合
m = regex.search('abc')
m.group()
# > AttributeError: 'NoneType' object has no attribute 'group'
print(m)
# > 'None'

match

文字列の先頭でマッチ

  • regexOjbect.match(string[, pos[, endpos])
    • 戻り値:マッチ成功時は matchオブジェクト、失敗時は None
import re 
regex = re.compile(r'\d+')

m1 = regex.match('123abc')
m1.group()
# > '123'

m2 = regex.match('abc123')
m2.group()
# > AttributeError: 'NoneType' object has no attribute 'group'
  • 実際のプログラムでは以下のような表現をすればよい
impor re

p = re.compile("[a-z]+")
m = p.match("abあc")

if m:
    print('Match found: ', m.group())
else:
    print('No match')

# > Match found:  ab

fullmatch

文字列全体にマッチ

  • regexOjbect.fullmatch(string[, pos[, endpos])
    • 戻り値:マッチ成功時は matchオブジェクト、失敗時は None
import re
regex = re.compile('\d+')

m1 = regex.fullmatch('123')
m1.group()
# > 123

m2 = regex.fullmatch('abc123')
m2.group()
# > AttributeError: 'NoneType' object has no attribute 'group'

findall

マッチする文字列を複数さがす1

  • regexOjbect.findall(string[, pos[, endpos])
    • 戻り値:マッチした全ての部分文字列を格納したリスト
    • 戻り値はリストなので、group()で取り出そうとするとエラーになる
import re
regex = re.compile(r'\d+')
matches = regex.findall('123,456,789')
matches
# > ['123', '456', '789']

# マッチなしの場合は空の[]が返る
import re
regex = re.compile('\d+')
match = regex.findall('ab123cde456')
match
# > ['123', '456']
import re
regex = re.compile('\d+')

m1 = regex.findall('ab123cde456')
m1 if m1 else print('No match')
# > ['123', '456']

m2 = regex.findall('abcde')
m2 if m2 else print('No match')
# > No match

finditer

マッチする文字列を複数さがす2

  • regexOjbect.finditer(string[, pos[, endpos])
    • 戻り値:マッチした全ての部分文字列に対するmatchオブジェクトのiterrow
regex = re.compile(r'\d+')

matches = regex.finditer('123,456,789')

for m in matches:
    print(f'{m.group()}:{m.start()}〜{m.end()}')

# 123:0〜3
# 456:4〜7
# 789:8〜11

sub

マッチする文字列を置換する

  • regexOjbect.sub(置換文字列,string[, 置換上限数])
p = re.compile('(blue|white|red)')
p.sub('color', 'blue shirts and white socks and red shoes')

# > 'color shirts and color socks and color shoes'
p = re.compile('^hello')
p.sub('Hello', 'hello hello hello')

# > 'Hello hello hello'
p = re.compile(r'(\d+):(\w+)')
p.sub('[\\2:\\1]', '1134:abc,56:def,789:ghi')

# > '[abc:1134],[def:56],[ghi:789]'

split

マッチした全ての場所で分割する

  • regexOjbect.split(string[, 置換上限数])
p = re.compile(r'\W+')

p.split('This is a test, short and sweet, of split().')
> # ['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']

p.split('This is a test, short and sweet, of split().', 3)
> # ['This', 'is', 'a', 'test, short and sweet, of split().']

最短マッチを行うには

  • 「*」「+」「?」といった繰り返しを表す特殊文字は、範囲が一番長くなるようにマッチする
  • これを「欲張りマッチ」等という
  • 「.+」は1文字以上の任意の長さの文字列のこと
  • この場合、最初の「東京,」だけを取り出したくても取り出せない
regex = re.compile("^.+,")
match = regex.search("東京,大阪,名古屋,福岡")
match.group()

# 東京,大阪,名古屋, ←マッチした文字列がすべて取得される
  • それに対して「+」の代わりに「+?」を指定すると「最短マッチ」になる
  • 同様に
  • 「*」の代わりに「*?」を指定すると最短マッチになる
  • 「?」の代わりに「??」を指定すると最短マッチになる
regex = re.compile("^.+?,")
match = regex.search("東京,大阪,名古屋,福岡")
match.group()

# 東京, ← マッチ文字列の最短が表示される

グルーピング

ポイント

( )で囲まれた正規表現をひとかたまりにするメリット

  • 部分正規表現を定義できる
    • 各種の量指定子(*や+など)をセットできる
      • 例:(abc)+
    • 演算子の優先順位を変更できる
      • 例:(Bill|William) Gates
  • マッチした部分をキャプチャ(記憶)する
    • 逆に()を付けないとキャプチャされない?
p = re.compile("(ab)*")
m = p.match("ababababab")
m.group()
# > 'ababababab'

p = re.compile("(a)b")
m = p.match("ababababab")
m.group()
# > 'ab'
  • サブグループは左から右へ1ずつナンバリングされている
  • 0は全体を示す、1以降の番号は単に「(」の順番どおり
  • "(a(b)c)d" ← 全体が0、「(」の1番目、2番目
p = re.compile("(a(b)c)d")
m = p.match('abcd')

m.group(0)
# > 'abcd'

m.group(1)
# > 'abc'

m.group(2)
# > 'b'

# グループ1から全てのグループの文字列をタプルで返す
m.groups()
# > ('abc', 'b')

# こんな表現もできる
m.group(2,1,2,1,1)
# > ('b', 'abc', 'b', 'abc', 'abc')
import re
regex = re.compile(r'(\d{3})-(\d{4})')
str = "郵便番号は123-4567です"

match = regex.search(str)

match.group()
# > '123-4567'

match.group(1)
# > '123'

match.group(2)
# > '4567'

こんな使い方もある

import re

regex = re.compile('(To|From):(.*)')

string = """
To:abc@gmail.com
From:xyz@yahoo.co.jp
"""

match = regex.search(string)

match.group()
# > 'To:abc@gmail.com'
import re

regex = re.compile('(To|From):(.|\s)*')

string = """
To:abc@gmail.com
From:xyz@yahoo.co.jp
"""

match = regex.search(string)

match.group()
# > 'To:abc@gmail.com\nFrom:xyz@yahoo.co.jp\n'

取り出さないグループ

  • (?:...)
  • グループ化された正規表現(部分正規表現)の一部を取り出したくない場合は「( 」の後ろに「?:」を付ける
  • 「…」は正規表現
import re

# 3つの(キャプチャ)グループがあるが
# 最初のグループ(「製品コード」)をキャプチャ対象外と設定する
regex = re.compile(r'(?:製品コード):([A-Z]{2})-(\d{2})')
str = "製品コード:AX-02"

match = regex.search(str)

match.group()
# > '製品コード:AX-02'

match.group(1)
# > 'AX'

match.group(2)
# > '02'
regex = re.compile('^(...)(...)(...)')
str = 'abc123abc'
regex.findall(str)
# > [('abc', '123', 'abc')]

regex = re.compile('^(...)(?:...)(...)')
str = 'abc123abc'
regex.findall(str)
# > [('abc', 'abc')] ?タプル?

名前付きグループ(未)

  • (?P<name>...)
  • 番号で参照する代わりに、グループに名前を付けて参照できる
  • 番号参照はどれがどれかわかりにくいが、名前だとわかりやすい
p = re.compile(r'(?P<word>\b\w+\b)')
m = p.search('(((( Lots of punctuation )))')

m.group()
# > 'Lots'

m.group('word')
# > 'Lots'

m.group(1)
# > 'Lots'
m = re.match(r'(?P<first>\w+) (?P<last>\w+)', 'Jane Doe')
m.groupdict()
# > {'first': 'Jane', 'last': 'Doe'}

df[列]から正規表現で抽出(s.str.extract(r’〜’))

参考URL:

https://note.nkmk.me/python-pandas-str-extract-extractall/

r'(^.*?[都道府県])'

  • ^:先頭、.*:任意文字、?[都道府県]:「都道府県」のどれか1文字
# 都道府県を抽出し、列に追加
df['都道府県'] = df['address'].str.extract(r'(^.*?[都道府県])', expand=True)

# 参考)都道府県に応じたコードを付与
df['都道府県コード'] = df['address']\
          .str.extract(r'(^.*?[都道府県])')[0]\   # mapのためSeriesとして取り出す
          .map({"神奈川県": "2", "東京都": "1", "千葉県": "3"})