こんにちは、すのくろです。
テキスト処理において、特定のパターンを簡単に見つけたり、データを抽出したりするためには、正規表現は非常に強力なツールです。
この記事では、正規表現の基本から実践的な応用まで、幅広い使い方を解説します。
正規表現の使い方を学んで、すぐに実践できる手助けができれば幸いです!
正規表現とは
正規表現は、英語では「regular expression」略してregexと呼ばれます。正規表現はテキストのパターンの記述法となります。
いろんな文字列の中から指定したルールに当てはまる文字列を取得することに利用されます。
例えば正規表現で「\d」は数字1文字を表します。
したがって「\d\d\d-\d\d\d\d」であるテキストの中から電話番号「123−4567」などの文字列をマッチさせて探すことができます。
正規表現はかなり洗練されているので、非常にルールがあり複雑ですが、正規表現を覚えることでいろんなパターンのテキストを検索して抽出してプログラムとして扱うことができます。
正規表現を学ぶメリット
正規表現は非常に便利で、正規表現を使って検索や置換ができる正規表現を使えば、プログラマーにとっても大幅に時間を節約することができます。
実際にテクニカルライターのCory Doctorow氏は、
「プログラミングを教える前に正規表現を教えるべきだ」
と主張しているとのことです。
[参考文献] Here’s what ICT should really teach kids: how to do regular expressions
Pythonでの正規表現の使い方
Pythonで正規表現を扱うためには、扱うプログラムコード内で「re」モジュールをインポートする必要があります。
reモジュールはPython標準搭載なので、すぐに下記のコードを実行すれば使うことができます。
import re
正規表現の関数
それでは、ここから実際にpythonで正規表現を使った検索表現の仕方を見ていきます。
まずは正規表現を扱う際に用いる関数についての紹介です。
下記の主要な関数について実際のコードを交えながら解説します!
関数名 | 役割 |
---|---|
match() | 先頭の文字列からパターンに一致するものを検索 |
search() | 先頭に限らず、パターンに一致しるものがあるか確認。複数一致しても1つ目だけを返す。 |
findall() | パターンに一致するもの全てをリストで返す。位置情報は取得不可。 |
finditer() | パターンに一致するもの全てをマッチオブジェクトで返す。位置情報も取得可能。 |
fullmatch() | 文字列全体が一致しているか確認 |
sub() | パターンに一致した文字列を別の文字列に置き換える |
match()
match()は文字列の先頭から数えて、マッチするものだけ返します。
先頭が違う場合はNoneを返します。
m = re.match("x.z", "xyzxyzxyz")
print(m)
#出力結果
# <re.Match object; span=(0, 3), match='xyz'>
m = re.match("x.z", "abcxyzxyzxyz")
print(m)
#出力結果
# None
search()
search()は先頭になくてもOKです。文字列の途中でも一致するものがあれば一致したと見なされます。
複数一致している場合でも、あくまで、先頭から一つ目のみを返す。
m = re.search("x.z", "abcxyzxyzxyz")
print(m)
####
# <re.Match object; span=(3, 6), match='xyz'>
findall()
findall()では文字列内でマッチする部分全てをリストにして返します。
m = re.findall("x.z", "abcxyzxyzxyz")
print(m)
###
# ['xyz', 'xyz', 'xyz']
finditer()
finditer()では文字列内でマッチする部分全てをマッチオブジェクトにして返します。
先ほどのfindall()はリスト型で返していましたが、finditer()はマッチオブジェクトなので、位置情報の開始・終了位置も取得可能です。
位置情報を取得する時はfor文でイテレーションして返します。
m = re.finditer("x.z", "abcxyzxyzxyz")
print(m)
###
# <callable_iterator object at 0x7f6e4276aa40>
# 一つずつ見る場合は、for文で回す。
m = re.finditer("x.z", "abcxyzxyzxyz")
for n in m:
print(n)
###
# <re.Match object; span=(3, 6), match='xyz'>
# <re.Match object; span=(6, 9), match='xyz'>
# <re.Match object; span=(9, 12), match='xyz'>
fullmatch()
fullmatch()は文字列全体が一致している場合のみ返します。
関数名でだいたい機能が想像できますね。
m = re.fullmatch("x.z", "abcxyzxyzxyz")
print(m)
m2 = re.fullmatch("x.z", "xyz")
print(m2)
####
# None
# <re.Match object; span=(0, 3), match='xyz'>
sub()
sub()はパターンに一致した文字列を別の文字列に置き換えます。
これまで出てきた他の関数と違い、入力引数「置換後の文字列」が一つ増えています。
re.sub(正規表現のパターン, 置換後の文字列, 検索対象の文字列)
m = re.sub("x.z", "abc", "abcxyzxyzxyz")
print(m)
###
# abcabcabcabc
正規表現で扱う記号
正規表現を使う際に、ある条件を指定するときに文字を直接記述するのではなく、記号を使って表現することで簡潔かつ汎用的に書くことができます。
以降は、正規表現で扱う記号について紹介します!
英数字を指定する記号
数字やアルファベットを表す記号は下記の表のとおりです。
記号 | 意味 |
---|---|
[0-9] | 全ての数字 |
[a-zA-Z] | 全てのアルファベット |
[0-9a-zA-Z] | 全ての数字とアルファベット |
[^0-9a-zA-Z] | 全ての数字とアルファベット以外(^をつけると否定) |
| | a|b a,bのいずれかの文字にマッチ |
[] | []括弧内のいずれかの文字にマッチ |
[0-9]: 全ての数字
この記号は、0から9までの数字を表します。つまり、任意の数字1つにマッチします。
import re
pattern = r"[0-9]"
text = "There are 5 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['5', '1', '0']
[a-zA-Z]: 全てのアルファベット
こちらは、すべてのアルファベット文字(大文字と小文字)にマッチします。
import re
pattern = r"[a-zA-Z]"
text = "Hello World!"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd']
[0-9a-zA-Z]: 全ての数字とアルファベット
この記号は、数字とアルファベットのすべての文字にマッチします。
import re
pattern = r"[0-9a-zA-Z]"
text = "Password123"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['P', 'a', 's', 's', 'w', 'o', 'r', 'd', '1', '2', '3']
[^0-9a-zA-Z]: 全ての数字とアルファベット以外(^をつけると否定)
この記号は、数字とアルファベット以外の文字にマッチします。「^」は否定の意味を持ちます。
import re
pattern = r"[^0-9a-zA-Z]"
text = "Special characters: !@#"
matches = re.findall(pattern, text)
print(matches)
# 出力: [' ', ':', ' ', '!', '@', '#']
|: (または)
記号「|」は、「または」を意味します。
例えば、「a|b」とした場合、aまたはbにマッチします。選択肢のいずれかに合致する場合に使用します。
import re
pattern = r"apple|orange"
text = "I like apples and oranges."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['apple', 'orange']
[](または)
この記号は、括弧内のいずれかの文字にマッチします。この例では、a, i, u, e, oのいずれかにマッチします。
import re
pattern = r"[aeiou]"
text = "The quick brown fox jumps over the lazy dog."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['e', 'u', 'i', 'o', 'o', 'o', 'u', 'u', 'o', 'e', 'e', 'a', 'o']
繰り返し回数を指定する記号
正規表現で、繰り返し回数を指定する記号でよく使うものは、下記表のようなものになります。
記号 | 意味 |
---|---|
? | 0回もしくは1回 |
* | 0回以上 |
+ | 1回以上 |
{n} | n回 |
{n,} | n回以上 |
{n,m} | n回以上、m回まで |
?: 0回もしくは1回
この記号は、「?」の直前のパターンが0回か1回出現する場合にマッチします。
下記の例では、「o」の後に「?」をつけることで、「color」か「colour」にマッチするコードです。
「o?」が「o」があってもなくても良い(0回か1回)という意味となります。
import re
pattern = r"colou?r"
text = "The color of the sky is blue. colour"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['color', 'colour']
*: 0回以上
この記号は、直前のパターンが0回以上繰り返される場合にマッチします。
import re
pattern = r"go*gle"
text = "ggle gogle google gooogle"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['ggle', 'gogle', 'google', 'gooogle']
+: 1回以上
この記号は、直前のパターンが1回以上繰り返される場合にマッチします。
import re
pattern = r"go+gle"
text = "ggle gogle google gooogle"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['gogle', 'google', 'gooogle']
{n}: n回
{n}記号は、直前のパターンがちょうどn回繰り返される場合にマッチします。
import re
pattern = r"go{2}gle"
text = "ggle gogle google gooogle"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['google']
{n,}: n回以上
{n,}記号は、直前のパターンが少なくともn回以上繰り返される場合にマッチします。
import re
pattern = r"go{2,}gle"
text = "ggle gogle google gooogle"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['google', 'gooogle']
{n,m}: n回以上、m回まで
{n,m}記号は、直前のパターンがn回以上からm回以下繰り返される場合にマッチします。
下記の例では「o」が1回以上、3回までの文字列がマッチします。
import re
pattern = r"go{1,3}gle"
text = "ggle gogle google gooogle"
matches = re.findall(pattern, text)
print(matches) # 出力: ['gogle', 'google', 'gooogle']
文字列の先頭・末尾を指定する記号
続いて、以下にそれぞれの文字列の先頭・末尾を指定する記号の解説とコードの例を示します。
記号 | 意味 |
---|---|
^ | 文字列の先頭からパターンに一致するか判定 |
$ | 文字列の末尾からパターンに一致するか判定 |
^ や $ は、文字列の特定の位置にパターンが存在するかどうかを判定する際に便利です。
^: 文字列の先頭からパターンに一致するか判定
この記号は、文字列の先頭からパターンが一致するかどうかを判定します。
import re
pattern = r"^Hello"
text = "Hello, world! Hello there."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['Hello']
この例では、パターン “^Hello” は “Hello” が文字列の先頭に来る場合にマッチします。
最初の “Hello” のみがマッチします。
$: 文字列の末尾からパターンに一致するか判定
この記号は、文字列の末尾からパターンが一致するかどうかを判定します。
import re
pattern = r"world!$"
text = "Hello, world! Goodbye, world!"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['world!']
この例では、パターン “world!$” は “world!” が文字列の末尾に来る場合にマッチします。最後の “world!” のみがマッチします。
特殊シーケンス
特殊シーケンスは、正規表現内で特定の文字クラスや位置を表現するために使用される特別な文字の組み合わせです。以下にそれぞれの特殊シーケンスの詳細な説明を示します。
記号 | 意味 |
---|---|
\d | 全ての数字([0-9]と同じ) |
\D | 全ての数字以外([^0-9]と同じ) |
\w | 全ての英数字([a-zA-Z0-9_]と同じ) |
\W | 全ての英数字以外([^a-zA-Z0-9_]と同じ) |
\s | 空白 |
\S | 空白以外 |
\A | 文字列の先頭(^と同じ) |
\Z | 文字列の末尾($と同じ) |
これらの特殊シーケンスは、正規表現パターン内で特定の文字クラスや位置を指定する際に便利です。
正規表現を使う際にこれらの特殊シーケンスを駆使することで、より柔軟なマッチングを実現できます。
\d: 全ての数字
この記号は、全ての数字にマッチします。
つまり、上記で解説した[0-9]と同じで、0から9までのどの数字でも一致します。
import re
pattern = r"\d"
text = "There are 5 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['5', '1', '0']
\D: 全ての数字以外
この記号は、数字以外の文字にマッチします。つまり、0から9までの数字以外の文字に一致します。
import re
pattern = r"\D"
text = "There are 5 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['T', 'h', 'e', 'r', 'e', ' ', 'a', 'r', 'e', ' ', ' ', 'a', 'p', 'p', 'l', 'e', 's', ' ', 'a', 'n', 'd', ' ', ' ', 'o', 'r', 'a', 'n', 'g', 'e', 's', '.']
\w: 全ての英数字
この記号は、英字、数字、アンダースコア(_)にマッチします。
import re
pattern = r"\w"
text = "Hello, _world_123!"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['H', 'e', 'l', 'l', 'o', '_', 'w', 'o', 'r', 'l', 'd', '_', '1', '2', '3']
\W: 全ての英数字以外
この記号は、英字、数字、アンダースコア(_)以外の文字にマッチします。
import re
pattern = r"\W"
text = "Hello, _world_123!"
matches = re.findall(pattern, text)
print(matches)
# 出力: [',', ' ', '!']
\s: 空白
この記号は、空白文字にマッチします。
sがspace(空白)の頭文字を意味しています。
import re
pattern = r"\s"
text = "This is a sentence with spaces."
matches = re.findall(pattern, text)
print(matches)
# 出力: [' ', ' ', ' ', ' ']
\S: 空白以外
この記号は、空白文字以外の文字にマッチします。
import re
pattern = r"\S"
text = "This is a sentence with spaces."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['T', 'h', 'i', 's', 'i', 's', 'a', 's', 'e', 'n', 't', 'e', 'n', 'c', 'e', 'w', 'i', 't', 'h', 's', 'p', 'a', 'c', 'e', 's', '.']
\A: 文字列の先頭(^と同じ)
この記号は、文字列の先頭にパターンが一致するかどうかを判定します。^ と同様の機能です。
import re
pattern = r"\AHello"
text = "Hello, world! Hello there."
matches = re.findall(pattern, text)
print(matches)
# 出力: ['Hello']
この例では、パターン \AHello
は “Hello” が文字列の先頭に来る場合にマッチします。最初の “Hello” のみがマッチします。
\Z: 文字列の末尾($と同じ)
この記号は、文字列の末尾にパターンが一致するかどうかを判定します。$ と同様の機能です。
import re
pattern = r"world!\Z"
text = "Hello, world! Goodbye, world!"
matches = re.findall(pattern, text)
print(matches)
# 出力: ['world!']
この例では、パターン world!\Z
は “world!” が文字列の末尾に来る場合にマッチします。最後の “world!” のみがマッチします。
まとめ
正規表現は、テキストのパターンを検索や抽出するための強力なツールです。
本記事では、正規表現の基本的な概念と使い方について学びました。
これらの基本的な記号や特殊シーケンスを組み合わせることで、多彩なパターンを検出・抽出できます。正規表現はパターンマッチングの基礎として学ぶ価値がある強力なツールです。
今後も応用や実践で活用して、より高度なテキスト処理を行いましょう。
それでは、正規表現を使ったテキスト処理の世界への第一歩を踏み出してみてください!
これ以外にも、Pythonは自由度が高く、豊富なライブラリがまだまだ用意されているため、様々なデータ処理や効率化に応用することができます!
さらにPythonのスキルを高めて、効率的に業務を行いたい、高度なPythonを中心としたプログラミングをより体系的に学びたいと言う方向けに、おすすめのオンラインスクールを2つ厳選して紹介していますので、こちらもよければご覧ください!
自分も一度体系的にPythonを学んだことで、一気に日々の業務や人生が変わったと感じています!
以上、ここまでお読みいただき、ありがとうございました!
コメント