試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonで正規表現による文字列処理(検索・置換・分割)を行う

正規表現操作を行う「re」モジュールを使用すると、文字列の高度な検索・置換・分割処理が行える。
文字列のパターンを独自のルールで記述して関数を呼ぶことで、対象文字列がこれに当てはまるかを確認することができるのだが、マッチした部分を取り出したり別の文字列に置き換えたりすることもできる。
また、パターンにマッチした部分を区切りにした分割も行える。
(2014/10/4)リンク先修正やサンプルコードの修正を行った。

  1. 簡単な検索・置換・分割では使用しない
  2. 関数を使用した例
  3. パターンのコンパイルを使用する

簡単な検索・置換・分割では使用しない

決まった文字列で検索・置換・分割を行う場合、高速な文字列メソッドを使用することを推奨。本当に正規表現を使用する必要があるのかを判断しないと、無駄に処理が遅くなってしまうかもしれない。

関数を使用した例

re.sub()で置換を行い、re.search()re.match()で検索を行う。re.search()正規表現を使用した一般的な検索で、re.match()は与えられたパターンが指定位置を含めその後ろと一致するかどうかを調べるものとなる。
分割はre.split()で行え、分割された結果がリスト型として得られる。

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function

import re

# 簡単な置換の例・関数を使用
print ('簡単な置換の例/関数使用:')
str1_before = 'この後ろに[http://d.hatena.ne.jp/kakurasan/:title=試験運用中なLinux備忘録へのリンク]があります'
str1_after = re.sub ('\[(http://.+?):title=(.+?)\]', r'<a href="\1">\2</a>', str1_before)
print ('  文字列: {0}\n  置換後: {1}'.format (str1_before, str1_after))

# 簡単な検索の例・関数を使用
print ('\n簡単な検索の例/関数使用:')
str2 = 'この文字列にはhttp://www.example.com/のようなURLが含まれます'
print ('  文字列:', str2)
# str2の内容の中に最初の引数のパターンがあればmに一致した箇所のオブジェクトが入る
m = re.search ('(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)', str2)
if m:
  # group([数字])でパターン内の括弧部分が取得できる(数字部分は何箇所目の括弧か)
  print ('  search("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  # 一致なしの場合
  print ('  search("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありませんでした')
# 最初の引数のパターンがstr2の先頭にあればmに一致した箇所のオブジェクトが入る
m = re.match ('(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)', str2)
if m:
  print ('  match("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  print ('  match("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありませんでした')
# 先頭に一致するようなパターンを試す
m = re.match ('この([^a-z]+)', str2)
if m:
  print ('  match("この([^a-z]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  print ('  match("この([^a-z]+)"): 一致箇所はありませんでした')

# 簡単な分割の例・関数を使用
print ('\n簡単な分割の例/関数使用:')
str3 = 'abc,dEf/ghi3.jk:456'
print ('  文字列:', str3)
str3_split = re.split ('[^a-zA-Z0-9]+', str3)
print ('  分割後: {0}'.format (str3_split))

下は実行結果。

簡単な置換の例/関数使用:
  文字列: この後ろに[http://d.hatena.ne.jp/kakurasan/:title=試験運用中なLinux備 忘録へのリンク]があります
  置換後: この後ろに<a href="http://d.hatena.ne.jp/kakurasan/">試験運用中なLinux備忘録へのリンク</a>があります

簡単な検索の例/関数使用:
  文字列: この文字列にはhttp://www.example.com/のようなURLが含まれます
  search("(http://[A-Za-z0-9'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "http://www.example.com/" がマッチしました
  match("(http://[A-Za-z0-9'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありません でした
  match("この([^a-z]+)"): "文字列には" がマッチしました

簡単な分割の例/関数使用:
  文字列: abc,dEf/ghi3.jk:456
  分割後: ['abc', 'dEf', 'ghi3', 'jk', '456']

(2009/4/24)マッチングについては検索とマッチングの比較(リファレンス)も参照。下は対話モードでのテスト例。「<_sre.SRE_Match object at ...>」が出ているときに一致している。

>>> import re
>>> re.compile ('a').match ('abcdefg')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.compile ('b').match ('abcdefg')
>>> re.compile ('c').match ('abcdefg', 2)
<_sre.SRE_Match object; span=(2, 3), match='c'>

パターンのコンパイルを使用する

正規表現でパターンを指定して処理をするとき、内部的には、「パターン文字列を解析して、処理に使える形のデータを作り出す」ということ(コンパイル)が行われ、その後でこれを使用して処理を行う。Pythonではre.compile()により正規表現オブジェクトが生成される形となっている。
上の例のような関数を使用した場合、関数が呼ばれる度に毎回コンパイルは行われているのだが、これは一度しか使われない(使い捨てにされている)。もし、同じパターンを用いた処理が複数回出てきたとしても、またコンパイル作業が行われることとなってしまい、無駄に処理を遅くする原因となる。
コンパイルした正規表現オブジェクトは何度でも使い回すことができる。これにより、同じパターンを何度も使用する場面において、同じコンパイル作業を行う必要がなくなる。
そのため、「本当に一度しか使わない」というパターンでない限り、コンパイルしたオブジェクトを使うのを習慣にするのが望ましいと思われる。
上の例をre.compile()を使用する形で書き換えると下のようになる。コンパイルして作成した正規表現オブジェクトのメンバ関数を使用している点に注意。

#! /usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function

import re

# 簡単な置換の例・compile()を使用
print ('簡単な置換の例/compile()使用:')
str1_before = 'この後ろに[http://d.hatena.ne.jp/kakurasan/:title=試験運用中なLinux備忘録へのリンク]があります'
ptn1 = re.compile ('\[(http://.+?):title=(.+?)\]')
str1_after = ptn1.sub (r'<a href="\1">\2</a>', str1_before)
print ('  文字列: {0}\n  置換後: {1}'.format (str1_before, str1_after))

# 簡単な検索の例・compile()を使用
print ('\n簡単な検索の例/compile()使用:')
str2 = 'この文字列にはhttp://www.example.com/のようなURLが含まれます'
print ('  文字列:', str2)
ptn2 = re.compile ('(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)')
# str2の内容の中に上のパターンがあればmに一致した箇所のオブジェクトが入る
m = ptn2.search (str2)
if m:
  # group([数字])でパターン内の括弧部分が取得できる(数字部分は何箇所目の括弧か)
  print ('  search("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  # 一致なしの場合
  print ('  search("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありませんでした')
m = ptn2.match (str2)
if m:
  print ('  match("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  print ('  match("(http://[A-Za-z0-9\'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありませんでした')
# 先頭に一致するようなパターンを試す
ptn2_2 = re.compile ('この([^a-z]+)')
m = ptn2_2.match (str2)
if m:
  print ('  match("この([^a-z]+)"): "{0}" がマッチしました'.format (m.group (1)))
else:
  print ('  match("この([^a-z]+)"): 一致箇所はありませんでした')

# 簡単な分割の例・compile()を使用
print ('\n簡単な分割の例/compile()使用:')
str3 = 'abc,dEf/ghi3.jk:456'
print ('  文字列:', str3)
ptn3 = re.compile ('[^a-zA-Z0-9]+')
str3_split = ptn3.split (str3)
print ('  分割後: {0}'.format (str3_split))

下は実行結果。

簡単な置換の例/compile()使用:
  文字列: この後ろに[http://d.hatena.ne.jp/kakurasan/:title=試験運用中なLinux備 忘録へのリンク]があります
  置換後: この後ろに<a href="http://d.hatena.ne.jp/kakurasan/">試験運用中なLinux備忘録へのリンク</a>があります

簡単な検索の例/compile()使用:
  文字列: この文字列にはhttp://www.example.com/のようなURLが含まれます
  search("(http://[A-Za-z0-9'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): "http://www.example.com/" がマッチしました
  match("(http://[A-Za-z0-9'~+\-=_.,/%\?!;:@#\*&\(\)]+)"): 一致箇所はありません でした
  match("この([^a-z]+)"): "文字列には" がマッチしました

簡単な分割の例/compile()使用:
  文字列: abc,dEf/ghi3.jk:456
  分割後: ['abc', 'dEf', 'ghi3', 'jk', '456']

この例ではstr2のところだけでしかオブジェクトが再利用されていないため、それほど処理速度は変わらないのだが、同じパターンの正規表現を複数回使用することが多くなればなるほど、再利用の効果は大きくなる。

参考URL:

使用したバージョン: