▲正規表現

正規表現について説明します。

参考

正規表現 (regular expression) を扱う場合、re というモジュールをインポートする必要があります。

[1]:
import re

正規表現の基本

正規表現とは、文字列のパターンを表す式です。 文字列が正規表現にマッチするとは、文字列が正規表現の表すパターンに適合していることを意味します。 また、正規表現が文字列にマッチするという言い方もします。

たとえば、正規表現 abc は文字列 abcde (の部分文字列 abc)にマッチします。

正規表現に文字列がマッチしているかどうかを調べることのできる関数に match があります。

match は、指定した 正規表現A文字列B(の先頭部分)にマッチするかどうか調べます。

re.match(正規表現A, 文字列B)
[2]:
match1 = re.match('abc', 'abcde') #マッチする
print(match1)
match1 = re.match('abc', 'ababc') #マッチしない
print(match1)
<re.Match object; span=(0, 3), match='abc'>
None

match では、マッチが成立している場合、matchオブジェクトと呼ばれる特殊なデータを返します。マッチが成立しない場合、 None を返します。

つまり、マッチする部分文字列を含む場合、返値は None ではないので、if文などの条件で真とみなされます。 したがって以下のようにして条件分岐することができます。

if re.match(正規表現, 文字列):
    ...
[3]:
if re.match('abc', 'abcde'): #マッチする
    print('正規表現abcが文字列abcdeにマッチする')
else:
    print('正規表現abcが文字列abcdeにマッチしない')
if re.match('abc', 'ababc'): #マッチしない
    print('正規表現abcが文字列ababcにマッチする')
else:
    print('正規表現abcが文字列ababcにマッチしない')
正規表現abcが文字列abcdeにマッチする
正規表現abcが文字列ababcにマッチしない

さて、上で紹介したmatchオブジェクトには、マッチした文字列の情報が格納されています。上のセルの1つ目の実行結果を印字したものを見てください。

<_sre.SRE_Match object; span=(0, 3), match='abc'> と表示されていると思います。このオブジェクト内の match という値は、マッチした文字列を、 span という値はマッチしたパターンが存在する、文字列のインデックスの範囲を表します。

正規表現では大文字と小文字は区別されます。たとえば、正規表現 abc は文字列 ABCdef にはマッチしません。勿論、正規表現 ABC も文字列 abcdef にはマッチしません。

[4]:
match1 = re.match('abc', 'ABCdef')
print(match1)
match1 = re.match('ABC', 'abcdef')
print(match1)
None
None

そこで match の3番目の引数として re.IGNORECASE もしくは re.I を指定すると、大文字と小文字を区別せずにマッチするかどうかを調べることができます。

[5]:
match1 = re.match('abc', 'ABCdef', re.IGNORECASE)
print(match1)
match1 = re.match('ABC', 'abcdef', re.IGNORECASE)
print(match1)
match1 = re.match('ABC', 'ABCdef', re.IGNORECASE)
print(match1)
match1 = re.match('abc', 'ABCdef', re.I)
print(match1)
match1 = re.match('ABC', 'abcdef', re.I)
print(match1)
match1 = re.match('ABC', 'ABCdef', re.I)
print(match1)
match1 = re.match('AbC', 'aBcdef', re.I)
print(match1)
<re.Match object; span=(0, 3), match='ABC'>
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 3), match='ABC'>
<re.Match object; span=(0, 3), match='ABC'>
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 3), match='ABC'>
<re.Match object; span=(0, 3), match='aBc'>

match は文字列の先頭がマッチするかどうか調べますので、次のような場合、matchオブジェクトを返さずに None が返されます。

[6]:
match1 = re.match('def', 'abcdef')
print(match1)
match1 = re.match('xyz', 'abcdef')
print(match1)
None
None

文字列の先頭しか調べられないのでは、いかにも不便です。

そこで、関数 search は、指定した 正規表現A文字列B に(文字列の先頭以外でも)マッチするかどうか調べることができます。

re.search(正規表現A, 文字列B)
[7]:
match1 = re.search('abc', 'abcdef')
print(match1)
match1 = re.search('abc', 'ababcd')
print(match1)
match1 = re.search('def', 'abcdef')
print(match1)
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(2, 5), match='abc'>
<re.Match object; span=(3, 6), match='def'>

search の場合も3番目の引数として re.IGNORECASE、 もしくは re.I を指定することで、大文字と小文字を区別せずにマッチするかどうかを調べることができます。

[8]:
match1 = re.search('abc', 'ABCdef')
print(match1)
match1 = re.search('DEF', 'abcdef')
print(match1)
match1 = re.search('abc', 'ABCdef', re.IGNORECASE)
print(match1)
match1 = re.search('DEF', 'abcdef', re.I)
print(match1)
match1 = re.search('not', 'It is NOT me.', re.I)
print(match1)
match1 = re.search('NOT', 'It is not mine.', re.I)
print(match1)
None
None
<re.Match object; span=(0, 3), match='ABC'>
<re.Match object; span=(3, 6), match='def'>
<re.Match object; span=(6, 9), match='NOT'>
<re.Match object; span=(6, 9), match='not'>

match 関数と同様に、 search 関数においても、if文を使った条件分岐が可能であることは覚えておいてください。

if re.search(正規表現, 文字列):
    ...
[9]:
if re.search('not', 'It is NOT me.'):
    print('正規表現notが文字列It is NOT me.にマッチする')
else:
    print('正規表現notが文字列It is NOT me.にマッチしない')
if re.search('not', 'It is NOT me.', re.I):
    print('正規表現notが文字列It is NOT me.にマッチする')
else:
    print('正規表現notが文字列It is NOT me.にマッチしない')
正規表現notが文字列It is NOT me.にマッチしない
正規表現notが文字列It is NOT me.にマッチする

文字列の先頭からのマッチを調べたいときには、正規表現の先頭にキャレット (^) をつけてください。また、文字列の最後からマッチさせたいときは、正規表現の最後にドル記号 ($) をつけてください。

[10]:
match1 = re.search('abc', 'ababcd')  #キャレットなしだとマッチする
print(match1)
match1 = re.search('^abc', 'ababcd') #キャレットありだとマッチしない
print(match1)
match1 = re.search('def', 'abcdefg') #ドル記号なしだとマッチする
print(match1)
match1 = re.search('def$', 'abcdefg') #ドル記号ありだとマッチしない
print(match1)
match1 = re.search('def$', 'abcdefxyzdef') # 2つあるうちの2番目(最後)の def にマッチする
print(match1)
<re.Match object; span=(2, 5), match='abc'>
None
<re.Match object; span=(3, 6), match='def'>
None
<re.Match object; span=(9, 12), match='def'>

ただ、ここまでの内容だと、正規表現を用いずに文字列のメソッド (find など)によっても実現が可能です。これでは正規表現を使うメリットはほとんどありません。

というのも、ここまで見てきた1つの正規表現によって、1つの文字列を表していたからです。しかし、最初に言った通り、正規表現は文字列の「パターン」を表します。すなわち、1つの正規表現で複数の文字列を表すことが可能なのです。

たとえば、正規表現 ab と正規表現 de という2つの正規表現を | という記号で繋げた ab|de も正規表現を表します。この正規表現では、abde という2つの文字列を表しており、これらのいずれかを含む文字列にマッチします。この | の記号(演算)を、もしくは選択といいます。

[11]:
match1 = re.search('ab|de', 'bcdef')
print(match1)
match1 = re.search('ab|de', 'abcdef')
print(match1)
match1 = re.search('ab|de', 'fgdeab')
print(match1)
match1 = re.search('ab|de', 'acdf')
print(match1)
match1 = re.search('a|an|the', 'I slipped on a piece of the banana.')
print(match1)
match1 = re.search('a|an|the', 'I slipped on the banana.')
print(match1)
match1 = re.search('Good (Morning|Evening)', 'Good Evening, Vietnam.') #正規表現内の()については下で述べます
print(match1)
match1 = re.search('colo(u|)r', 'That color matches your suit.') #正規表現内の()については下で述べます
print(match1)
match1 = re.search('colo(u|)r', 'That colour matches your suit.') #正規表現内の()については下で述べます
print(match1)
<re.Match object; span=(2, 4), match='de'>
<re.Match object; span=(0, 2), match='ab'>
<re.Match object; span=(2, 4), match='de'>
None
<re.Match object; span=(13, 14), match='a'>
<re.Match object; span=(13, 16), match='the'>
<re.Match object; span=(0, 12), match='Good Evening'>
<re.Match object; span=(5, 10), match='color'>
<re.Match object; span=(5, 11), match='colour'>

上記の3番目の例に注意してください。正規表現 ab | de では abde よりも先に記述されていますが、マッチする文字列は文字列上で先に出てきた方(ab ではなく、de)であることに注意してください。

細かい話ですが、正規表現 abc は、正規表現 a と正規表現 b と正規表現 c という3つの正規表現を繋げて構成された正規表現であり、このように正規表現を繋げて新しい正規表現を作る演算を連接といいます。

a* は、正規表現 a を0回以上繰り返した文字列とマッチします。この * の演算を閉包といいます。

[12]:
match1 = re.search('a*', 'abcdef')
print(match1)
match1 = re.search('a*', 'aabbcc')
print(match1)
match1 = re.search('a*', 'cde')
print(match1)
match1 = re.search('bo*', 'boooo!')
print(match1)
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 2), match='aa'>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 5), match='boooo'>

上記の3番目の例において(文字 a が含まれていないにも関わらず)None が返らずに、マッチしているのを不思議に思うかもしれません。しかし、a*a を0回反復した文字列にもマッチします。この0回反復した文字列とは、長さが0の文字列であり、空列とか空文字列と呼ばれます。文字列 cde の先頭には、空列があると見なせるので、a* が先頭部分にマッチしているのです。

たとえば、正規表現 abb* は、 ab, abb, abbb, … という文字列にマッチします。

[13]:
match1 = re.search('abb*', 'abcdef')
print(match1)
match1 = re.search('abb*', 'aabbcc')
print(match1)
match1 = re.search('abb*', 'cde')
print(match1)
match1 = re.search('hello*', 'Hi, hellooooo!')
print(match1)
match1 = re.search('hello*', 'Hi, good morning!')
print(match1)

<re.Match object; span=(0, 2), match='ab'>
<re.Match object; span=(1, 4), match='abb'>
None
<re.Match object; span=(4, 13), match='hellooooo'>
None

これまでに紹介した連接、和、閉包という3つの演算を組み合わせることで様々な正規表現を記述することができますが、これらの演算には結合の強さが存在します。たとえば、先に見た ab | cd という正規表現は、 ab もしくは cd という文字列にマッチします((ab)|(cd) と同じ意味です)。つまり、連接の方が和よりも強く結合しているのです。そこで、丸括弧を使って a(b|c)d とすると、この正規表現は、abd | acd と同じ意味になります。

[14]:
match1 = re.search('ab|de', 'fgdeab')
print(match1)
match1 = re.search('(ab)|(de)', 'fgdeab')
print(match1)
match1 = re.search('a(b|d)e', 'fgdeab')
print(match1)
match1 = re.search('a(b|d)e', 'fgadeab')
print(match1)
match1 = re.search('abe|ade', 'fgadeab')
print(match1)
match1 = re.search("(I|i)t('s| is| was)", "It was rainy yesterday, but it's fine today.")
print(match1)
match1 = re.search("(I|i)t('s| is| was)", "It rained yesterday, but it's fine today.")
print(match1)
<re.Match object; span=(2, 4), match='de'>
<re.Match object; span=(2, 4), match='de'>
None
<re.Match object; span=(2, 5), match='ade'>
<re.Match object; span=(2, 5), match='ade'>
<re.Match object; span=(0, 6), match='It was'>
<re.Match object; span=(25, 29), match="it's">

演算の結合の強さは、「和 < 連接 < 閉包」という順序になっています。これは数学の、「和(+)< 積(×) < べき」と同じですので、直感的にもわかりやすいと思います。これまでに紹介した連接、和、閉包という3つの演算と結合の順序を明記する丸括弧 () とを組み合わせることで様々な正規表現を記述することができます。

[15]:
match1 = re.search('a(bc|b)*', 'defabcxyz')
print(match1)
match1 = re.search('a(bc|b)*', 'bbacbabbbbc')
print(match1)
match1 = re.search('ca(r|t(egory|tle|))', 'What category is this cat in?')
print(match1)
match1 = re.search('ca(r|t(egory|tle|))', 'No, this is not a carpet.')
print(match1)
match1 = re.search('ca(r|t(egory|tle|))', 'We saw a cattle car almost hit the cat.')
print(match1)
match1 = re.search('ca(r|t(egory|tle|))', 'Please locate him.')
print(match1)
match1 = re.search('ca(r|t(egory|tle|))', "Don't play castanets.")
print(match1)
<re.Match object; span=(3, 6), match='abc'>
<re.Match object; span=(2, 3), match='a'>
<re.Match object; span=(5, 13), match='category'>
<re.Match object; span=(18, 21), match='car'>
<re.Match object; span=(9, 15), match='cattle'>
<re.Match object; span=(9, 12), match='cat'>
None

Pythonでは正規表現は文字列によって表していることに注意してください。たとえば、match 関数の第一引数を文字列の変数で置き換えられるということです。

[16]:
match1 = re.match('abc', 'abcde')
print(match1)
reg1 = 'abc' # 正規表現を文字列で記述する
match2 = re.match(reg1, 'abcde') #match1と同じ結果になる
print(match2)
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 3), match='abc'>

このことを覚えておくと複雑な正規表現を書くときに、少しずつ分解して記述することができて便利です。

[17]:
match1 = re.search("(I|i)t('s| is| was)", "It was rainy yesterday, but it's fine today.")
print(match1)
reg1 = '(I|i)t' # 正規表現の前半部分
reg2 = "('s| is| was)" # 正規表現の後半部分
reg3 = reg1 + reg2 # 正規表現を表す2つの文字列を結合する
print(reg3)
match2 = re.search(reg3, "It was rainy yesterday, but it's fine today.")
print(match2)
<re.Match object; span=(0, 6), match='It was'>
(I|i)t('s| is| was)
<re.Match object; span=(0, 6), match='It was'>

練習

文字列 str1 を引数として取り、 str1 の中に「月を表す文字列」が含まれているかどうか調べて、含まれていればマッチしたときのmatchオブジェクトを、含まれいなければ None を返す関数 check_monthstr を作成してください。 ただし、「月を表す文字列」 は次のような文字列とします。

  1. 長さ2の mm という文字列

  2. mm は、00, 01, …, 12 のいずれかの文字列

以下のセルの ... のところを書き換えて解答してください。

[18]:
import ...
def check_monthstr(str1):
    ...
  File "/tmp/ipykernel_9427/3763894078.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[19]:
print(check_monthstr('10').group() == '10') # group()については後半に説明があります(オプショナル)
print(check_monthstr('mon1521vb') == None)
print(check_monthstr('00an23') == None)
print(check_monthstr('13302').group() == '02')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/479121597.py in <module>
----> 1 print(check_monthstr('10').group() == '10') # group()については後半に説明があります(オプショナル)
      2 print(check_monthstr('mon1521vb') == None)
      3 print(check_monthstr('00an23') == None)
      4 print(check_monthstr('13302').group() == '02')

NameError: name 'check_monthstr' is not defined

練習

文字列 str1 を引数として取り、 str1 を構成する文字列が A, C, G, T の4種類の文字以外の文字を含むかどうか調べて、これら以外を含む場合は False を、そうでない場合は True を返す関数 check_ACGTstr を作成してください。 ただし、大文字と小文字は区別しません。また、空列の場合は False を返してください。

以下のセルの ... のところを書き換えて解答してください。

[20]:
import ...
def check_ACGTstr(str1):
    ...
  File "/tmp/ipykernel_9427/390249491.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[21]:
print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
print(check_ACGTstr(':ACaacgta24FgtGH') == False)
print(check_ACGTstr('') == False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/3681665936.py in <module>
----> 1 print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
      2 print(check_ACGTstr(':ACaacgta24FgtGH') == False)
      3 print(check_ACGTstr('') == False)

NameError: name 'check_ACGTstr' is not defined

練習

文字列 str1 を引数として取り、 str1 の中に「時刻を表す文字列」が含まれているかどうか調べて、含まれていればマッチしたときのmatchオブジェクトを、含まれいなければ None を返す関数 check_timestr を作成してください。 ただし、「時刻を表す文字列」 は次のような文字列とします。

  1. 長さ5の hh:mm という文字列であり、12時間表示で時間を表す。

  2. 前半の2文字 hh は、 00, 01, …, 11 のいずれかの文字列

  3. 後半の2文字 mm は、 00, 01, …, 59 のいずれかの文字列

以下のセルの ... のところを書き換えて解答してください。

[22]:
import ...
def check_timestr(str1):
    ...
  File "/tmp/ipykernel_9427/159983618.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[23]:
print(check_timestr('10:23').group() == '10:23') # group()については後半に説明があります(オプショナル)
print(check_timestr('time?1023') == None)
print(check_timestr('time?11:23').group() == '11:23')
print(check_timestr('12:3xx1;23ah23:23') == None)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/1589433981.py in <module>
----> 1 print(check_timestr('10:23').group() == '10:23') # group()については後半に説明があります(オプショナル)
      2 print(check_timestr('time?1023') == None)
      3 print(check_timestr('time?11:23').group() == '11:23')
      4 print(check_timestr('12:3xx1;23ah23:23') == None)

NameError: name 'check_timestr' is not defined

練習

文字列 str1 を引数として取り、str1 の中に「IPv4を表す文字列」が含まれているかどうか調べて、含まれていればマッチしたときのmatchオブジェクトを、含まれいなければ None を返す関数 check_ipv4str を作成してください。 ただし、「IPv4を表す文字列」 は次のような文字列とします。

  1. aaa:bbb:ccc:ddd という形式の長さ15の文字列

  2. aaa, bbb, ccc, ddd はいずれも、 000, 001, …, 254, 255 のいずれかの文字列

以下のセルの ... のところを書き換えて解答してください。

[24]:
import ...
def check_ipv4str(str1):
    ...
  File "/tmp/ipykernel_9427/19572505.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[25]:
print(check_ipv4str('IP=255:255:255:255').group() == '255:255:255:255')
print(check_ipv4str('notIP=2x5:a5b:2c:255:14:444') == None)
print(check_ipv4str('IP?=25:25:55:155') == None)
print(check_ipv4str('IP?=255:255:255') == None)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/2259691458.py in <module>
----> 1 print(check_ipv4str('IP=255:255:255:255').group() == '255:255:255:255')
      2 print(check_ipv4str('notIP=2x5:a5b:2c:255:14:444') == None)
      3 print(check_ipv4str('IP?=25:25:55:155') == None)
      4 print(check_ipv4str('IP?=255:255:255') == None)

NameError: name 'check_ipv4str' is not defined

練習

文字列 str1 を引数として取り、 str1 の中に「月と日を表す文字列」が含まれているかどうか調べて、含まれていればマッチしたときのmatchオブジェクトを、含まれいなければ None を返す関数 check_monthdaystr を作成してください。 ただし、「月と日を表す文字列」 は次のような文字列とします。

  1. mm/dd という長さ5の文字列

  2. mm は、 01, 02, …, 12 のいずれかの文字列

  3. dd は、mm01, 03, 05, 07, 08, 10, 12 ならば、01, 02, …, 31 のいずれかの文字列

  4. dd は、mm04, 06, 09, 11 ならば、01, 02, …, 30 のいずれかの文字列

  5. dd は、mm02 ならば、01, 02, …, 29 のいずれかの文字列

以下のセルの ... のところを書き換えて解答してください。

[26]:
import ...
def check_monthdaystr(str1):
    ...
  File "/tmp/ipykernel_9427/2570444019.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[27]:
print(check_monthdaystr('year11/31month11/30day15hour/27minute/sec').group() == '11/30')
print(check_monthdaystr('11/31') == None)
print(check_monthdaystr('x02f/2d5ax') == None)
print(check_monthdaystr('03/24').group() == '03/24')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/1893991142.py in <module>
----> 1 print(check_monthdaystr('year11/31month11/30day15hour/27minute/sec').group() == '11/30')
      2 print(check_monthdaystr('11/31') == None)
      3 print(check_monthdaystr('x02f/2d5ax') == None)
      4 print(check_monthdaystr('03/24').group() == '03/24')

NameError: name 'check_monthdaystr' is not defined

文字クラス

[abc]a|b|c と同じ意味の正規表現です。この角括弧用いた表記は、文字クラスと呼ばれます。

[28]:
match1 = re.search('[abc]', 'defabcxyz')
print(match1)
match1 = re.search('[3456]', '1234567890')
print(match1)
match1 = re.search('ha[sd]', 'He has an apple and they have pineapples.')
print(match1)

<re.Match object; span=(3, 4), match='a'>
<re.Match object; span=(2, 3), match='3'>
<re.Match object; span=(3, 6), match='has'>

勿論、これまでの和や閉包と組み合わせて用いることができます。

[29]:
match1 = re.search('[def][abc]', 'defabcxyz')
print(match1)
match1 = re.search('4[3456][3456]([3456]|[7890])', '1234567890')
print(match1)
match1 = re.search('6[789]*', '1234567890')
print(match1)
match1 = re.search('she ha[sd]|they ha(ve|d)', 'He has an apple and they have pineapples.', re.I)
print(match1)
<re.Match object; span=(2, 4), match='fa'>
<re.Match object; span=(3, 7), match='4567'>
<re.Match object; span=(5, 9), match='6789'>
<re.Match object; span=(20, 29), match='they have'>

ただし、文字クラスの中で連接、和、閉包は無効化されます。たとえば、[a*] という正規表現は、a もしくは、* にマッチします。

[30]:
match1 = re.search('[a*]', 'aaaaaa') # a一文字にマッチ
print(match1)
match1 = re.search('[a*]', '*') # *一文字にマッチ
print(match1)
match1 = re.search('a*', 'aaaaaa')
print(match1)
match1 = re.search('a*', '*') # 文字クラスでない場合、*にはマッチしない
print(match1)
match1 = re.search('[a|b]', 'defabcxyz') # a一文字にマッチ
print(match1)
match1 = re.search('[a|b]', '|') #  |一文字にマッチ
print(match1)
match1 = re.search('a|b', '|') # 文字クラスでない場合、|にはマッチしない
print(match1)
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='*'>
<re.Match object; span=(0, 6), match='aaaaaa'>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(3, 4), match='a'>
<re.Match object; span=(0, 1), match='|'>
None

文字クラスでは一文字分の連続する和演算を表すことができますが、長さ2以上の文字列を表すことはできません。すなわち、ab | cd という正規表現を(1つの)文字クラスで表すことはできません。

また、 [abcdefg][gcdbeaf] などは [a-g][1234567][4271635] などは [1-7] などとハイフン (-) を用いることで簡潔に表すことができます。たとえば、全てのアルファベットと数字を表す場合は、[a-zA-Z0-9] で表されます。

[31]:
match1 = re.search('[a-c]', 'defabcxyz')
print(match1)
match1 = re.search('3[4-8]', '1234567890')
print(match1)
match1 = re.search(':[a-zA-Z0-9]*:', 'a1b2c3:d4e5f:6g7A8B:9C0D')
print(match1)
<re.Match object; span=(3, 4), match='a'>
<re.Match object; span=(2, 4), match='34'>
<re.Match object; span=(6, 13), match=':d4e5f:'>

文字クラスの内側をキャレット (^) で始めると、否定文字クラスとなり、キャレットの後ろに指定した文字以外の文字とマッチする正規表現となります。たとえば、[^abc]a, b, c 以外の1文字とマッチする正規表現です。

[32]:
match1 = re.search('[^abc]', 'abcdefxyz')
print(match1)
match1 = re.search('[^def]', 'defabcxyz')
print(match1)
match1 = re.search('[^1-7]', '1234567890')
print(match1)
match1 = re.search('ha[^sd]e', 'He has an apple and they have pineapples.')
print(match1)
<re.Match object; span=(3, 4), match='d'>
<re.Match object; span=(3, 4), match='a'>
<re.Match object; span=(7, 8), match='8'>
<re.Match object; span=(25, 29), match='have'>

キャレットを先頭以外につけた場合は、単なる文字クラスになります。すなわち、キャレットにマッチするかどうかが判定されます。たとえば、[d^ef] は、d, ^, e, f のいずれかにマッチします。

[33]:
match1 = re.search('[d^ef]', 'defabcxyz')
print(match1)
match1 = re.search('[d^ef]', 'a^bcdef') # キャレットにマッチする
print(match1)
<re.Match object; span=(0, 1), match='d'>
<re.Match object; span=(1, 2), match='^'>

正規表現に関する基本的な関数

上で紹介した正規表現を利用してマッチする文字列が存在するかどうかを調べるだけではなく、マッチした文字列に対して色々な処理を加えることができます。以下では2つの基本的な関数を紹介します。

sub

sub は、正規表現R にマッチする 文字列A 中の全ての文字列を、指定した 文字列B で置き換えることができます。

具体的には次のようにすると、

re.sub(正規表現R, 置換する文字列B, 元になる文字列A)

R とマッチする A 中の全ての文字列を B と置き換えることができます。置き換えられた結果の文字列(新たに作られて)が返値となります。(もちろん、もとの文字列 A は変化しません。)

[34]:
str1 = re.sub('[346]', 'x', '03-5454-68284') #3,4,6をxに置き換える
print(str1)
str1 = re.sub('[.,:;!?]', '', "He has three pets: a cat, a dog and a giraffe, doesn't he?") #句読点を削除する(空文字列に置き換える)
print(str1)
str1 = re.sub('\(a\)|あっとまーく|@', '@', 'accountnameあっとまーくtest.ecc.u-tokyo.ac.jp') # スパム回避の文字列を@に置き換える
print(str1)# \(と\)の意味については、下記の「正規表現のエスケープシーケンス」の節を参照してください
0x-5x5x-x828x
He has three pets a cat a dog and a giraffe doesn't he
accountname@test.ecc.u-tokyo.ac.jp
re.sub(r'[ \t\n][ \t\n]*', ' ', str1)

とすると、文字列 str1 の空白文字の並びがスペース1個に置き換わります。

ここで、r'[ \t\n][ \t\n]*' という正規表現は、空白かタブか改行の1回以上の繰り返しのパターンを表します。つまり、aa* という形をした「1回以上の a という文字列とマッチする正規表現」は a+ という + を使った正規表現で置き換えることが可能です。この + は後で正式に紹介します。

[35]:
re.sub(r'[ \t\n][ \t\n]*', ' ', 'Hello,\n    World!\tHow are you?')
[35]:
'Hello, World! How are you?'

以下では、HTMLやXMLのタグを消しています(空文字列に置き換えています)。

[36]:
re.sub(r'<[^>]*>', '', '<body>\nClick <a href="a.href">this</a>\n</body>\n')
[36]:
'\nClick this\n\n'

r'<[^>]*>' という正規表現は、< の後に > 以外の文字の繰り返しがあって最後に > が来るというパターンを表します。

re.split

split は、正規表現R にマッチする文字列を区切り文字(デリミタ)として、文字列A を分割します。分割された文字列がリストに格納されて返値となります。

具体的には次のように用います。

re.split(正規表現R, 元になる文字列A)

以下が典型例です。

re.split(r'[^a-zA-Z][^a-zA-Z]*', 'Hello, World! How are you?')

[^a-zA-Z][^a-zA-Z]* という正規表現は、英文字以外の文字が1回以上繰り返されている、というパターンを表します。 この正規表現を Python の式の中で用いるときは、r'[^a-zA-Z][^a-zA-Z]*' という構文を用います。 先頭の r については、以下の説明を参照してください。

[37]:
list1 = re.split(' ', "He has three pets a cat a dog and a giraffe doesn't he")
print(list1)
list2 = re.split(r'[^a-zA-Z][^a-zA-Z]*', 'Hello, World! How are you?')
print(list2)
['He', 'has', 'three', 'pets', 'a', 'cat', 'a', 'dog', 'and', 'a', 'giraffe', "doesn't", 'he']
['Hello', 'World', 'How', 'are', 'you', '']

この例のように、返されたリストに空文字列が含まれる場合がありますので、注意してください。

r を付ける理由

さて、以上のような正規表現は、'hello*' のようにPythonの文字列として re.splitre.sub などの関数に与えればよいのですが、 以下のように文字列の前に r を付けることが推奨されます。

[38]:
r'hello*'
[38]:
'hello*'

r'hello*' の場合は r を付けても付けなくても同じなのですが、 以下のように r を付けるとエスケープすべき文字がエスケープシーケンスになった文字列が得られます。

[39]:
r'[ \t\n]+'
[39]:
'[ \\t\\n]+'

\t\\t に変わったことでしょう。\\ はバックスラッシュを表すエスケープシーケンスです。 \t はタブという文字を表しますが、\\t はバックススラッシュと t という2文字から成る文字列です。 この場合、正規表現を解釈する段階でバックスラッシュが処理されます。

特に \ という文字そのものを正規表現に含めたいときは \\ と書いた上で r を付けてください。

[40]:
r'\\t t/'
[40]:
'\\\\t t/'

この場合、文字列の中に \ が2個含まれており、正規表現を解釈する段階で正しく処理されます。すなわち、\ という文字そのものを表します。

練習

英語の文書が保存された text-sample.txt というファイルから読み込み、出現する単語のリストを返す関数 get_engsentences を作成してください。 ただし、重複する単語を削除してはいけませんが、空文字列は除きます。また、リストは返す前に中身を昇順に並べ替えてください。

以下のセルの ... のところを書き換えて解答してください。

[41]:
import ...
def get_engsentences():
    ...
  File "/tmp/ipykernel_9427/3360045867.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[42]:
list1 = get_engsentences()
print(len(list1) == 289)
print(list1[0] == 'a')
print(list1[100] == 'in')
print(list1[288] == 'would')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/400148248.py in <module>
----> 1 list1 = get_engsentences()
      2 print(len(list1) == 289)
      3 print(list1[0] == 'a')
      4 print(list1[100] == 'in')
      5 print(list1[288] == 'would')

NameError: name 'get_engsentences' is not defined

練習

英語の文書が保存された text-sample.txt というファイルから読み込み、出現する単語のリストを返す関数 get_engsentences2 を作成してください。 ただし、空文字列は除きます。また、リストは返す前に中身を昇順に並べ替えてください。

以下のセルの ... のところを書き換えて解答してください。

[43]:
import ...
def get_engsentences2():
    ...
  File "/tmp/ipykernel_9427/2256989314.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[44]:
list1 = get_engsentences2()
print(len(list1) == 149)
print(list1[0] == 'a')
print(list1[100] == 'proclaim')
print(list1[148] == 'would')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/1837623858.py in <module>
----> 1 list1 = get_engsentences2()
      2 print(len(list1) == 149)
      3 print(list1[0] == 'a')
      4 print(list1[100] == 'proclaim')
      5 print(list1[148] == 'would')

NameError: name 'get_engsentences2' is not defined

その他の反復演算

閉包以外の反復演算を説明します。

a? は、正規表現 a を高々1回反復する文字列とマッチします。たとえば、a(bc)?a|abc と同じ意味の正規表現です。

[45]:
match1 = re.search('colou?r', 'colour')
print(match1)
match1 = re.search('colou?r', 'color')
print(match1)
<re.Match object; span=(0, 6), match='colour'>
<re.Match object; span=(0, 5), match='color'>

a+ は、正規表現 a を1回以上反復する文字列とマッチします。つまり、a+aa* と同じ意味の正規表現です。

[46]:
match1 = re.search('boo+', 'boooo!')
print(match1)
match1 = re.search('boo+', 'bo!')
print(match1)
match1 = re.search('a+', 'abcdef')
print(match1)
match1 = re.search('a+', 'aabbcc')
print(match1)
match1 = re.search('a+', 'cde')
print(match1)
match1 = re.search('[^a-zA-Z]+', 'abc12345efg67hi89j0k')
print(match1)
match1 = re.search('[a-zA-Z]+', 'abc12345efg67hi89j0k')
print(match1)
<re.Match object; span=(0, 5), match='boooo'>
None
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 2), match='aa'>
None
<re.Match object; span=(3, 8), match='12345'>
<re.Match object; span=(0, 3), match='abc'>

上記の例を * を使う形に書き換えてみてください。

a{x,y}、は正規表現 ax 回以上かつ y 回以下繰り返す文字列とマッチします。

[47]:
match1 = re.search('bo{3,5}', 'booooooo!')
print(match1)
match1 = re.search('bo{3,5}', 'boo!')
print(match1)
match1 = re.search('a{2,5}', 'bacaad')
print(match1)
match1 = re.search('[0-9]{1,3},[0-9]{3,3}', '1,298円')
print(match1)
match1 = re.search('[0-9]{1,3},[0-9]{3,3}', '298円')
print(match1)
<re.Match object; span=(0, 6), match='booooo'>
None
<re.Match object; span=(3, 5), match='aa'>
<re.Match object; span=(0, 5), match='1,298'>
None

メタ文字

以下では、良く使うメタ文字(特殊文字)を紹介します。

.(ピリオド)は、あらゆる文字にマッチします。

[48]:
match1 = re.search('.', 'Hello')
print(match1)
match1 = re.search('3.*9', '1234567890')
print(match1)
match1 = re.search('ha(.|..)', 'He has an apple and they have pineapples.')
print(match1)
<re.Match object; span=(0, 1), match='H'>
<re.Match object; span=(2, 9), match='3456789'>
<re.Match object; span=(3, 6), match='has'>

ただし、文字クラスの中で . を用いても、あらゆる文字とはマッチせず、* の場合と同様に、ピリオドとマッチします。

[49]:
match1 = re.search('[.]', 'Hello')
print(match1)
match1 = re.search('[.]', '3.141592')
print(match1)
None
<re.Match object; span=(1, 2), match='.'>

\t は タブを表します。

[50]:
match1 = re.search('b\t', 'a        b       c       d')
print(match1)
<re.Match object; span=(2, 4), match='b\t'>

\s空白文字(スペース、タブ、改行など)を表します。

[51]:
match1 = re.search('b\s', 'a        b       c       d')
print(match1)
match1 = re.search('a\s\s\s', 'a      b      c      d')
print(match1)
match1 = re.search('b\s*', 'a         b      c      d')
print(match1)
<re.Match object; span=(2, 4), match='b\t'>
<re.Match object; span=(0, 4), match='a\t\u3000 '>
<re.Match object; span=(4, 7), match='b\t\u3000'>

\S\s 以外の全ての文字を表します。

[52]:
match1 = re.search('b\S', 'a        b       bc      d')
print(match1)

<re.Match object; span=(4, 6), match='bc'>

\w[a-zA-Z0-9_] と同じ意味です。

[53]:
match1 = re.search('\w\w', 'abcde')
print(match1)
match1 = re.search('b\w*g', 'abcdefgh')

<re.Match object; span=(0, 2), match='ab'>

\W\w 以外の全ての文字を表します。すなわち、[^a-zA-Z0-9_] と同じ意味です。

[54]:
match1 = re.search('g\W*', 'ab defg  hi jklm no p')
print(match1)
match1 = re.search('\W\w*\W', 'ab defg  hi jklm no p')
print(match1)
<re.Match object; span=(6, 9), match='g  '>
<re.Match object; span=(2, 8), match=' defg '>

\d[0-9] と同じ意味です。

[55]:
match1 = re.search('\d\d\d-\d\d\d\d', '153-8902')
print(match1)
match1 = re.search('\d*-\d*', '153-8902')
print(match1)
match1 = re.search('\d\d-\d\d\d\d-\d\d\d\d', '03-5454-6828')
print(match1)
match1 = re.search('\d*-\d*-\d*', '03-5454-6828')
print(match1)
<re.Match object; span=(0, 8), match='153-8902'>
<re.Match object; span=(0, 8), match='153-8902'>
<re.Match object; span=(0, 12), match='03-5454-6828'>
<re.Match object; span=(0, 12), match='03-5454-6828'>

\D\d 以外の全ての文字を表します。すなわち、[^0-9] と同じ意味です。

[56]:
match1 = re.search('\D*', 'He has 10 apples.')
print(match1)

<re.Match object; span=(0, 7), match='He has '>

練習

文字列から数字列を切り出して、それを整数とみなして足し合せた結果を整数として返す関数 sumnumbers を定義してください。

[57]:
import ...
def sumnumbers(s):
    ...
  File "/tmp/ipykernel_9427/2526788762.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[58]:
print(sumnumbers(' 2  33 45, 67.9') == 156)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/939576310.py in <module>
----> 1 print(sumnumbers(' 2  33 45, 67.9') == 156)

NameError: name 'sumnumbers' is not defined

練習

文字列 str1 を引数として取り、str1 を構成する文字列が A, C, G, T の4種類の文字以外の文字を含むかどうか調べて、これら以外を含む場合は False、そうでない場合は True を返す関数 check_ACGTstr を作成してください。 ただし、大文字と小文字は区別しません。また、空列の場合は False を返してください。

以下のセルの ... のところを書き換えて解答してください。

[59]:
import ...
def check_ACGTstr(str1):
    ...
  File "/tmp/ipykernel_9427/390249491.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[60]:
print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
print(check_ACGTstr(':ACaacgta24FgtGH') == False)
print(check_ACGTstr('') == False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/3681665936.py in <module>
----> 1 print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
      2 print(check_ACGTstr(':ACaacgta24FgtGH') == False)
      3 print(check_ACGTstr('') == False)

NameError: name 'check_ACGTstr' is not defined

練習

文字列 str1 を引数として取り、 str1 を構成する文字列が「日本の郵便番号」を表す文字列になっている場合は、 True を返し、そうでない場合は False を返す関数 check_postalcode を作成してください。 ただし、「日本の郵便番号」は abc-defg という形になっており、a, b, c, d, e, d, f, g はそれぞれ 0 から 9 までの値になっています。

以下のセルの ... のところを書き換えて解答してください。

[61]:
import ...
def check_postalcode(str1):
    ...
  File "/tmp/ipykernel_9427/1253453412.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[62]:
print(check_postalcode('113-8654') == True)
print(check_postalcode('119-110') == False)
print(check_postalcode('abc-defg') == False)
print(check_postalcode('〒153-0041') == False)
print(check_postalcode('113-86547') == False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/1584138777.py in <module>
----> 1 print(check_postalcode('113-8654') == True)
      2 print(check_postalcode('119-110') == False)
      3 print(check_postalcode('abc-defg') == False)
      4 print(check_postalcode('〒153-0041') == False)
      5 print(check_postalcode('113-86547') == False)

NameError: name 'check_postalcode' is not defined

練習

文字列 str1 を引数として取り、str1 を構成する文字列が「本郷の内線番号」を表す文字列になっている場合は、 True を返し、そうでない場合は False を返す関数 check_extension を作成してください。 ただし、「本郷の内線番号」は 2abcd という形になっており、a, b, c, d はそれぞれ 0 から 9 までの値になっています。

以下のセルの ... のところを書き換えて解答してください。

[63]:
import ...
def check_extension(str1):
    ...
  File "/tmp/ipykernel_9427/3524781894.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[64]:
print(check_extension('24115') == True)
print(check_extension('46858') == False)
print(check_extension('☎46666') == False)
print(check_extension('467890') == False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/3174993402.py in <module>
----> 1 print(check_extension('24115') == True)
      2 print(check_extension('46858') == False)
      3 print(check_extension('☎46666') == False)
      4 print(check_extension('467890') == False)

NameError: name 'check_extension' is not defined

正規表現のエスケープシーケンス

丸括弧 () や演算子 (|, *) など正規表現の中で特殊な役割を果たす記号のマッチを行いたい場合、文字列のエスケープシーケンスのように \ を前につけてやる必要があります。

[65]:
match1 = re.search('03(5454)6666', '03(5454)6666') #電話番号。つけないと丸括弧として扱われないのでマッチしない
print(match1)
match1 = re.search('03(5454)6666', '0354546666') # 括弧が含まれない文字列にマッチ
print(match1)
match1 = re.search('03\(5454\)6666', '03(5454)6666') # \(と\)で左右の丸括弧として扱われるのでマッチする
print(match1)
match1 = re.search('3*4+5=17', '3*4+5=17') #計算式。*と+が演算子扱いされているのでマッチしない
print(match1)
match1 = re.search('3*4+5=17', '33345=17') #\がないと、たとえば、このような文字列とマッチする
print(match1)
match1 = re.search('3\*4\+5=17', '3*4+5=17') #意図した文字列にマッチ
print(match1)
match1 = re.search('|ω・`)チラ ', '|ω・`)チラ ') #顔文字。 空列にマッチしてしまう
print(match1)
match1 = re.search('\|ω・`)チラ ', '|ω・`)チラ ') #意図した文字列にマッチ
print(match1)
None
<re.Match object; span=(0, 10), match='0354546666'>
<re.Match object; span=(0, 12), match='03(5454)6666'>
None
<re.Match object; span=(0, 8), match='33345=17'>
<re.Match object; span=(0, 8), match='3*4+5=17'>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 8), match='|ω・`)チラ '>

特殊な意味を持つ記号は次の14個です。

. ^ $ * + ? { } [ ] \ | ( )

これらの特殊記号が含まれる場合(かつ意図したマッチの結果が得られない場合)には、エスケープシーケンスを使う(エスケープする)べき(可能性がある)ことも考慮に入れておいてください。

正規表現に関する関数とメソッド

以下では更に幾つかの関数とメソッドを紹介します。

findall

findall は、正規表現R にマッチする 文字列A 中の全ての文字列を、リストに格納して返します。

具体的には次のように実行します。

re.findall(正規表現R, 文字列A)
[66]:
list1 = re.findall('had', 'James while John had had had had had had had had had had had a better effect on the teacher.')
#James, while John had had 'had', had had 'had had'; 'had had' had had a better effect on the teacher.
print(list1) #全てのhadを抜き出す
list1 = re.findall('p[^ .]*', 'Peter Piper picked a peck of pickled peppers.', re.I)
print(list1)# pで始まる全ての単語を取得する, 大文字小文字を区別しない
['had', 'had', 'had', 'had', 'had', 'had', 'had', 'had', 'had', 'had', 'had']
['Peter', 'Piper', 'picked', 'peck', 'pickled', 'peppers']

finditer

finditer は、正規表現R にマッチする 文字列A 中の全ての matchオブジェクトを、特殊なリスト(のようなもの)に格納して返します。

具体的には次のように実行します。

re.finditer(正規表現R, 文字列A)

返値は特殊なリスト(のようなもの)であり、for文の in の後ろに置いて使ってください。

[67]:
print('1:正規表現 had の結果:')
iter1 = re.finditer('had', 'James while John had had had had had had had had had had had a better effect on the teacher.')
#James, while John had had 'had', had had 'had had'; 'had had' had had a better effect on the teacher.
for match in iter1:
    print(match) #全てのhadを抜き出す
print('2:正規表現 p[^ .]* の結果:')
iter1 = re.finditer('p[^ .]*', 'Peter Piper picked a peck of pickled peppers.', re.I)
for match in iter1:
    print(match)# pで始まる全ての単語を取得する, 大文字小文字を区別しない
1:正規表現 had の結果:
<re.Match object; span=(17, 20), match='had'>
<re.Match object; span=(21, 24), match='had'>
<re.Match object; span=(25, 28), match='had'>
<re.Match object; span=(29, 32), match='had'>
<re.Match object; span=(33, 36), match='had'>
<re.Match object; span=(37, 40), match='had'>
<re.Match object; span=(41, 44), match='had'>
<re.Match object; span=(45, 48), match='had'>
<re.Match object; span=(49, 52), match='had'>
<re.Match object; span=(53, 56), match='had'>
<re.Match object; span=(57, 60), match='had'>
2:正規表現 p[^ .]* の結果:
<re.Match object; span=(0, 5), match='Peter'>
<re.Match object; span=(6, 11), match='Piper'>
<re.Match object; span=(12, 18), match='picked'>
<re.Match object; span=(21, 25), match='peck'>
<re.Match object; span=(29, 36), match='pickled'>
<re.Match object; span=(37, 44), match='peppers'>

group

matchオブジェクトのメソッド group は、正規表現にマッチした文字列を(部分的に)取り出します。正規表現内に丸括弧を用いると、括弧内の正規表現とマッチした文字列を取得できるようになっています。なお、group によるこの操作を、括弧内の文字列をキャプチャするといいます。

i 番目のキャプチャした値を取得するには次のようにします。i = 0 の場合は、マッチした文字列全体を取得できます。

matchオブジェクト.group(i)
[68]:
import re
match1 = re.search('03-5454-(\d\d\d\d)', '03-5454-6666')
print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1)) # 内線番号の取得
match1 = re.search('([^@]*)@[^.]*(\.[^.]*)?\.u-tokyo\.ac\.jp', 'accountname@test.ecc.u-tokyo.ac.jp') # \.はピリオドを表します
print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1)) # アカウント名の取得
match1 = re.search('([^@]*)@[^.]*(\.[^.]*)?\.u-tokyo\.ac\.jp', 'accountname@test.u-tokyo.ac.jp') # \.はピリオドを表します
print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1)) # アカウント名の取得
match1 = re.search("href=\"([^\"]*)\"", '<a href="http://www.u-tokyo.ac.jp" target="_blank">U-Tokyo</a>')# \"はダブルクォートを表します
print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1)) # リンク先URLの取得

マッチした文字列= 03-5454-6666  キャプチャした文字列= 6666
マッチした文字列= accountname@test.ecc.u-tokyo.ac.jp  キャプチャした文字列= accountname
マッチした文字列= accountname@test.u-tokyo.ac.jp  キャプチャした文字列= accountname
マッチした文字列= href="http://www.u-tokyo.ac.jp"  キャプチャした文字列= http://www.u-tokyo.ac.jp

マッチに失敗した場合は、matchオブジェクトが返らずに None が返るので、それを確かめずに group を使おうとするとエラーが出ますので注意してください。

[69]:
match1 = re.search('03-5454-(\d\d\d\d)', '03-5454-666') #マッチしない文字列
print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1))
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_9427/260316147.py in <module>
      1 match1 = re.search('03-5454-(\d\d\d\d)', '03-5454-666') #マッチしない文字列
----> 2 print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1))

AttributeError: 'NoneType' object has no attribute 'group'

たとえば、if文でエラーを回避します。

[70]:
match1 = re.search('03-5454-(\d\d\d\d)', '03-5454-666')
if match1 != None:
    print('マッチした文字列=', match1.group(0), ' キャプチャした文字列=', match1.group(1))
else:
    print('マッチしていません')
マッチしていません

練習

文字列 str1 を引数として取り、str1 を構成する文字列が A, C, G, T の4種類の文字以外の文字を含むかどうか調べて、これら以外を含む場合は False を、そうでない場合は True を返す関数 check_ACGTstr を作成してください。 ただし、大文字と小文字は区別しません。また、空列の場合は False を返してください。

以下のセルの ... のところを書き換えて解答してください。

[71]:
import ...
def check_ACGTstr(str1):
    ...
  File "/tmp/ipykernel_9427/390249491.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が全て True になることを確認してください。

[72]:
print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
print(check_ACGTstr(':ACaacgta24FgtGH') == False)
print(check_ACGTstr('') == False)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/3681665936.py in <module>
----> 1 print(check_ACGTstr('AcCGTAGCacATcGgAaaTtGCacT') == True)
      2 print(check_ACGTstr(':ACaacgta24FgtGH') == False)
      3 print(check_ACGTstr('') == False)

NameError: name 'check_ACGTstr' is not defined

練習

xmlファイル B1S.xml は http://www.natcorp.ox.ac.uk から入手できるイギリス英語のコーパスのファイルです。

B1S.xmlに含まれるwタグで囲まれる英単語をキー key に、その wタグの属性posの値を key の値とする辞書を返す関数 get_pos を作成してください。ただし、一般にwタグは、次のような形式で記述されます。

<w pos="PRON" ( > 記号以外の何らかの文字列)>(英単語) </w>

たとえば、以下のような具合です。

<w pos="VERB" hw="have" c5="VHI">have </w>

以下のセルの ... のところを書き換えて解答してください。

[73]:
import ...
def get_pos():
    ...
  File "/tmp/ipykernel_9427/1340085115.py", line 1
    import ...
           ^
SyntaxError: invalid syntax

上のセルで解答を作成した後、以下のセルを実行し、実行結果が True になることを確認してください。

[74]:
print(get_pos()['They '] == 'PRON')
print(get_pos()['know '] == 'VERB')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_9427/2030927862.py in <module>
----> 1 print(get_pos()['They '] == 'PRON')
      2 print(get_pos()['know '] == 'VERB')

NameError: name 'get_pos' is not defined

練習の解答

[75]:
import re
def check_monthstr(str1):
    reg_month = '((0(1|2|3|4|5|6|7|8|9))|10|11|12)' #
    #reg_month = '01|02|03|04|05|06|07|08|09|10|11|12' # としてもよい
    match1 = re.search(reg_month, str1) # 文字列を「含む」なので、(matchではなく)searchを使う
    return match1
[76]:
import re
def check_timestr(str1):
    reg_hour = '((0(0|1|2|3|4|5|6|7|8|9))|10|11)' #「時」部分の正規表現
    #reg_hour = '((0[0-9]|10|11)' # 文字クラスを使ってと表すこともできます(文字クラスは後で学習します
    reg_min = '((0|2|3|4|5)(0|1|2|3|4|5|6|7|8|9))' #「分」部分の正規表現
    #reg_min = '([0-5][0-9])' # 文字クラスを使ってと表すこともできます(文字クラスは後で学習します
    reg_time = reg_hour + ':' + reg_min # 時部分と分部分を、「:」を挟んで結合した新しい正規表現
    #print(reg_time)
    match1 = re.search(reg3, str1) # 文字列を「含む」なので、(matchではなく)searchを使う
    return match1
[77]:
import re
def check_ipv4str(str1):
    reg_0to9 = '(0|1|2|3|4|5|6|7|8|9)' # 0から9の数を表す正規表現
    #reg_0to9 = '[0-9]' # 文字クラスを使ってと表すこともできます(文字クラスは後で学習します
    reg_0_1 = '(0|1)' + reg_0to9 + reg_0to9 # 先頭の文字が0もしくは1だったときの正規表現(000から199まで)
    reg_20_24 = '2(0|1|2|3|4)' + reg_0to9 # 先頭が20,21,22,23,24だったときの正規表現(200から249まで)
    #reg_20_24 = '2[0-4]' + reg_0to9 # 文字クラスを使ってと表すこともできます
    reg_25 = '25(0|1|2|3|4|5)' # 先頭が25だったときの正規表現(250から255まで)
    #reg_25 = '25[0-5]' # 文字クラスを使ってと表すこともできます
    reg_000_255 = '(' + reg_0_1 + '|' + reg_20_24 + '|' + reg_25 + ')'# aaa(000から255)を表す正規表現
    #print(reg_000_255)
    reg_ip = reg_000_255 + ':' + reg_000_255 + ':' + reg_000_255 + ':' + reg_000_255 # aaa:bbb:ccc:dddを表す正規表現
    #print(reg_ip)
    match1 = re.search(reg_ip, str1) # 文字列を「含む」なので、(matchではなく)searchを使う
    return match1
[78]:
import re
def check_monthdaystr(str1):
    reg_month_31 = '(01|03|05|07|08|10|12)' #ddが01から31になるmm
    reg_month_30 = '(04|06|09|11)' #ddが01から30になるmm
    reg_1to9 = '(1|2|3|4|5|6|7|8|9)' # [1-9]でもよい
    reg_0to9 = '(0|1|2|3|4|5|6|7|8|9)' # [0-9]でもよい
    reg_day_01to09 = '(0' + reg_1to9 + ')' # ddが01から09になる場合
    reg_day_10to19 = '(1' + reg_0to9 + ')' # ddが10から19になる場合
    reg_day_20to29 = '(2' + reg_0to9 + ')' # ddが21から29になる場合
    reg_day_01to29 = reg_day_01to09 + '|' + reg_day_10to19 + '|' + reg_day_20to29 # ddが01から29になる場合
    reg_day_01to30 = reg_day_01to29 + '|' + '30' # ddが01から30になる場合
    reg_day_01to31 = reg_day_01to30 + '|' + '31' # ddが01から31になる場合
    reg_monthday_31 = reg_month_31 + '/(' + reg_day_01to31 + ')' # mmとddを組み合わせる(01-31の場合)
    reg_monthday_30 = reg_month_30 + '/(' + reg_day_01to30 + ')' # mmとddを組み合わせる(01-30の場合)
    reg_monthday_29 = '02/(' + reg_day_01to29 + ')' # mmとddを組み合わせる(01-29の場合はmmは02のみ)
    # 文字列を「含む」なので、(matchではなく)searchを使う
    match1 = re.search(reg_monthday_31, str1) # 問題文の条件3を満たす文字列とマッチするかどうか
    if match1 != None:
        return match1
    match1 = re.search(reg_monthday_30, str1) # 問題文の条件4を満たす文字列とマッチするかどうか
    if match1 != None:
        return match1
    match1 = re.search(reg_monthday_29, str1) # 問題文の条件5を満たす文字列とマッチするかどうか
    return match1
[79]:
import re
def sumnumbers(s):
    numbers = re.split('[^0-9]+', s)
    numbers.remove('')
    n = 0
    for number in numbers:
        n += int(number)
    return n
[80]:
import re
def get_engsentences():
    word_list = [] # 結果を格納するリスト
    with open('text-sample.txt', 'r') as f:
        file_str = f.read() #ファイルの中身を文字列に格納
    str_list = re.split(r'[^a-zA-Z][^a-zA-Z]*', file_str) # 文字列を単語に区切る
    for word in str_list: #`re.split(r'[^a-zA-Z][^a-zA-Z]*', f.read())` は、ファイル全体の文字列を単語に区切ります。
    #for word in re.split(r'[^a-zA-Z][^a-zA-Z]*', f.read()): # と一行にまとめてもよい
        if word != '': #空文字列を除く
            word = word.lower() #単語(文字列)の中の大文字を小文字に変換します
            word_list.append(word) #リストに追加
            #word_list.append(word.lower())でも大丈夫
    word_list.sort() # sortメソッドは破壊的
    return word_list
[81]:
import re
def get_engsentences2():
    word_dict = {} # 重複する単語を削除する為に辞書を使ってみる
    with open('text-sample.txt', 'r') as f:
        file_str = f.read() #ファイルの中身を文字列に格納
    str_list = re.split(r'[^a-zA-Z][^a-zA-Z]*', file_str) # 文字列を単語に区切る
    for word in str_list: #`re.split(r'[^a-zA-Z][^a-zA-Z]*', f.read())` は、ファイル全体の文字列を単語に区切ります。
        if word != '': #空文字列を除く
            word = word.lower() #単語(文字列)の中の大文字を小文字に変換します
            word_dict[word] = 'anthing good' #wordという単語があったことを辞書に記録する(wordに対応する値は何でもよい)
            #word_dict[word.lower()] = 'anthing good'でも大丈夫
    word_list = [] # 結果を格納するリスト
    for word in word_dict:
        word_list.append(word)
    word_list.sort()
    return word_list
[82]:
import re
def check_ACGTstr(str1):
    reg_ACGT = '(A|C|G|T)+' # A,C,G,Tを表す正規表現  # +→* だと空文字列がマッチしてしまう
    #reg_ACGT = '(A|C|G|T)(A|C|G|T)*' # A,C,G,Tを表す正規表現
    #reg_ACGT = '[ACGT]+' # A,C,G,Tを表す正規表現
    match1 = re.search(reg_ACGT, str1, re.I) # re.I を入れて、大文字と小文字を区別しない
    if match1 != None and str1 == match1.group(): # str1全体とマッチした文字列が等しいかチェック
        return True
    return False
#別解
#def check_ACGTstr(str1):
#    reg_ACGT = '(A|C|G|T)+$' # A,C,G,Tを表す正規表現  # +→* だと空文字列がマッチしてしまう
#    #reg_ACGT = '(A|C|G|T)(A|C|G|T)*$' # A,C,G,Tを表す正規表現
#    #reg_ACGT = '[ACGT]+$' # A,C,G,Tを表す正規表現
#    match1 = re.search(reg_ACGT, str1, re.I) # re.I を入れて、大文字と小文字を区別しない
#    if match1 != None: # str1全体とマッチした文字列が等しいかチェック
#        return True
#    return False
[83]:
import re
def check_postalcode(str1):
    reg1 = '[0-9]{3,3}-[0-9]{4,4}'
    #reg1 = '\d{3,3}-\d{4,4}' #でも可
    #reg1 = '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]' #でも可
    match1 = re.match(reg1, str1)
    if match1 == None:
        return False
    if match1.group() == str1:
        return True
    return False
#別解
#def check_postalcode(str1):
#    reg1 = '[0-9]{3,3}-[0-9]{4,4}$' #ドル記号を使って行末からマッチを調べる
#    #reg1 = '\d{3,3}-\d{4,4}$' #でも可
#    #reg1 = '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$' #でも可
#    match1 = re.match(reg1, str1)
#    if match1 == None:
#        return False
#    return True
[84]:
import re
def check_extension(str1):
    reg1 = '2[0-9]{4,4}'
#    reg1 = '2\d{4,4}' #でも可
#    reg1 = '2[0-9][0-9][0-9][0-9]' #でも可
    match1 = re.match(reg1, str1)
    if match1 == None:
        return False
    if match1.group() == str1:
        return True
    return False
#別解
#def check_extension(str1):
#    reg1 = '2[0-9]{4,4}$'
#    #reg1 = '2\d{4,4}$' #でも可
#    #reg1 = '2[0-9][0-9][0-9][0-9]$' #でも可
#    match1 = re.match(reg1, str1)
#    if match1 == None:
#        return False
#    return True
[85]:
import re
def get_pos():
    str_file = 'B1S.xml'
    with open(str_file, 'r', encoding='utf-8') as f:
        str_script = f.read() # ファイルの中身を1つの文字列に格納する
    #print(str_script)
    itr1 = re.finditer("<w[^>]*pos=\"([^>\"]*)\"[^>]*>([^<]*)</w>", str_script) # 正規表現を使ってwタグ周辺の文字列をマッチ
    dic1 = {} # 辞書初期化
    for m1 in itr1:
        #print(m1)
        #print(m1.group(1), m1.group(2))
        dic1[m1.group(2)] = m1.group(1) # groupを使ってマッチした文字列をキャプチャする
    return dic1