文字列操作と正規表現
文字列操作では本当にPython言語が輝く場所の一つです。この節は、いくつかのPythonの組み込みの文字列メソッドと整形される演算を、正規表現に対して究極的に使いやすい題材への早いガイドに動く前に、網羅します。このような文字列操作形式はしばしば、データサイエンスの仕事の文脈で生じます、そしてそれはこの文脈でPythonの大きな一つの特典です。
Pythonの文字列は、シングルまたはダブルクォーテーション(それらは機能的に等価です)をつかって定義されます:
In [1]:
x = 'a string'
y = "a string"
x == y
Out[1]:
True
くわえて、三つの引用符文法を使って複数行文字列を定義することができます:
In [2]:
multiline = """
one
two
three
"""
これで、Pythonの文字列操作ツールのいくつかを駆け足でとりあげましょう。
Python上の単純な文字列操作
基本的な文字列操作のために、Pythonの組み込み文字列メソッドが究極的に便利となります。もしCや他の低レベル言語で動作する背景をおもちなら、究極的に気分の変わるPythonメソッドの単純さに好んで気づくことでしょう。Pythonの文字列型とメソッドのいくつかを前に紹介しました。ここではちょっとより深く入ります。
文字列の整形: ケースの当てはめ
Pythonは文字列のケースを調整することがかなりやりやすいです。ここではupper(), lower(), capitalize(), title(), そしてswapcase()メソッドを見ます、例として以下の汚い文字列を使います:
In [3]:
fox = "tHe qUICk bROWn fOx."
文字列全体を大文字または小文字に変換するために、upper()またはlower()メソッドおのおのが使えます:
In [4]:
fox.upper()
Out[4]:
'THE QUICK BROWN FOX.'
In [5]:
fox.lower()
Out[5]:
'the quick brown fox.'
一般的な整形の必要性は各単語のちょうど最初の文字、またはおそらく各文の最初の文字を大文字にすることです。これはtitle()やcapitalize()メソッドでなされます:
In [6]:
fox.title()
Out[6]:
'The Quick Brown Fox.'
In [7]:
fox.capitalize()
Out[7]:
'The quick brown fox.'
このケースはswapcase()メソッドを使って交換されます:
In [8]:
fox.swapcase()
Out[8]:
'ThE QuicK BrowN FoX.'
文字列整形:空白の追加と削除
他の一般的な必要性として、文字列の最初または最後から空白を削除することです(さもなければ他の文字列)。基本的な文字を削除するメソッドは、strip()メソッドです、それは行の最初と最後から空白を除くのです:
In [9]:
line = ' this is the content '
line.strip()
Out[9]:
'this is the content'
右または左に対してちょうど空白を除くために、rstrip()またはlstrip()をそれぞれ使ってみましょう:
In [10]:
line.rstrip()
Out[10]:
' this is the content'
In [11]:
line.lstrip()
Out[11]:
'this is the content '
空白よりも他の文字を削除するためには、のぞみの文字をstripメソッドに渡せます:
In [12]:
num = "000000000000435"
num.strip('0')
Out[12]:
'435'
空白と他の文字を追加するこの操作の反対は、center(),ljust(),rjust()メソッドを使って、完成されます。
例えば、与えられた空白の数の中で与えられた文字列を中央にするcenter()メソッドを使えます:
In [13]:
line = "this is the content"
line.center(30)
Out[13]:
' this is the content '
同様に、ljust()とrjust()は、与えれた長さに対する空白の範囲内で、文字列を左寄せまたは右寄せにします:
In [14]:
line.ljust(30)
Out[14]:
'this is the content '
In [15]:
line.rjust(30)
Out[15]:
' this is the content'
すべてのこれらのメソッドは加えて、空白を埋めるのに使われる、任意の文字を受け入れます。たとえば:
In [16]:
'435'.rjust(10, '0')
Out[16]:
'0000000435'
0埋めはありふれた必要性なので、Pythonはzfill()を提供します、それは0で右埋めする特別なメソッドです:
In [17]:
'435'.zfill(10)
Out[17]:
'0000000435'
部分文字列を探して置換
もし文字列の中のある文字の占めているのを探したいなら、find()/rfind(), index()/rindex()メソッドは最高の組み込みのメソッドです。
それらは文字列にある部分文字列または文字の最初の位置を探すという点で、find()とindex()はとてもにています、そして、部分文字列のインデックスを返します:
In [18]:
line = 'the quick brown fox jumped over a lazy dog'
line.find('fox')
Out[18]:
16
In [19]:
line.index('fox')
Out[19]:
16
find()とindex()の間の唯一の違いは、探す文字列がないときの振る舞いです:index()はValueErrorをあげるのに対して、find()は-1を返します。
In [20]:
line.find('bear')
Out[20]:
-1
In [21]:
line.index('bear')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-21-4cbe6ee9b0eb> in <module>()
----> 1 line.index('bear')
ValueError: substring not found
文字列の最初よりもむしろ終わりから最初の位置を探すことをのぞいて、関連するrfind()とrindex()は似たように動きます:
In [22]:
line.rfind('a')
Out[22]:
35
文字列のはじまりまたはおわりで部分文字列をチェックする特別な場合について、Pythonはstartswith()とendswith()メソッドを提供しています:
In [23]:
line.endswith('dog')
Out[23]:
True
In [24]:
line.startswith('fox')
Out[24]:
False
さらに一つのステップにいって、新しい文字列で与えれた部分文字列を置換するために、replace()メソッドを使えます。ここで、"brown"を"red"で置き換えましょう:
In [25]:
line.replace('brown', 'red')
Out[25]:
'the quick red fox jumped over a lazy dog'
replace()関数は新しい文字列を返します、そして入力のすべての場所を置き換えるでしょう:
In [26]:
line.replace('o', '--')
Out[26]:
'the quick br--wn f--x jumped --ver a lazy d--g'
機能的にreplace()に対してより柔軟なアプローチに関して、Flexible Pattern Matching with Regular Expressions の中で 正規表現の話をみてください。
文字列を分割して部分化すること
もし部分文字列を探しくてその場所に基づいて文字列を分割したいなら、partition()やsplit()メソッドはあなたが探しているものです。両方とも部分文字列のシーケンスをかえすでしょう。
partition()メソッドは三つの要素でタプルを返します:分割点の最初のインスタンスの前の部分文字列、分割点自身、そしてあとの部分文字列:
In [27]:
line.partition('fox')
Out[27]:
('the quick brown ', 'fox', ' jumped over a lazy dog')
rpartition()メソッドはにていますが、文字列の右から探します。
split()メソッドは多分もっと使いやすいです:それは分割点のすべてのインスタンスを探して、間の部分文字列を返します。デフォルトは、文字列の中の個別の単語のリストを返して、任意の空白を分割することです:
In [28]:
line.split()
Out[28]:
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']
関連したメソッドはsplitlines()です、それは新しい文字を分割します。有名な17世紀の詩人の松尾芭蕉作のHaikuでこれをやりましょう:
In [29]:
haiku = """matsushima-ya
aah matsushima-ya
matsushima-ya"""
haiku.splitlines()
Out[29]:
['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']
split()をやりなおしたいなら、join()メソッドを使うことができるのに留意しましょう、それは分割点と反復可能なものから組み立てられる文字列を返します:
In [30]:
'--'.join(['1', '2', '3'])
Out[30]:
'1--2--3'
特別な文字"\n"(改行)を前に分割した行を一緒にするために使って入力を戻すのが一般的な形式です:
In [31]:
print("\n".join(['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']))
matsushima-ya
aah matsushima-ya
matsushima-ya
文字列の整形
先のメソッドで、文字列から値を取り出し、文字列を望みの形式に操作する方法を学びました。文字列メソッドの他の利用は、他の型の値に対する文字列表現を操作することで、文字列表現はいつもstr()関数を使うことでわかります。例えば:
In [32]:
pi = 3.14159
str(pi)
Out[32]:
'3.14159'
より複雑な形式で、あなたは基本的なPythonのセマンティクス、つまり演算子、でアウトライン化されるように文字列の算術を使いたいとなるかもしれません、
In [33]:
"The value of pi is " + str(pi)
Out[33]:
'The value of pi is 3.14159'
これをするより柔軟な方法は、既定の文字列を使うことで、それは中括弧で記述される特別な印を伴う文字列です、特別な印は、挿入される整形された文字列の値に入ります。これは基本的な例です:
In [34]:
"The value of pi is {}".format(pi)
Out[34]:
'The value of pi is 3.14159'
{}マーカーの中で、そこで確かに表したいことの、情報もまた含めます。もし数を含めたいなら、挿入する引数のインデックスを表しましょう:
In [35]:
"""First letter: {0}. Last letter: {1}.""".format('A', 'Z')
Out[35]:
'First letter: A. Last letter: Z.'
文字列を含むなら、任意のキーワードの引数のキーを示しましょう:
In [36]:
"""First letter: {first}. Last letter: {last}.""".format(last='Z', first='A')
Out[36]:
'First letter: A. Last letter: Z.'
最後に、数値の入力について、どうやって値が文字列に変換されるかを管理する既定のコードを含めます。例えば、小数点の後に3桁の浮動小数点として数値を表示するために、次を書きます:
In [37]:
"pi = {0:.3f}".format(pi)
Out[37]:
'pi = 3.142'
前のように、ここでは"0"は挿入される値のインデックスを表します。コードを規定する":"マークは次のとおりです。".3f"は望んだ精度に符号化できます、つまり小数点を超えた3桁と浮動小数点形式です。
この決まったフォーマットの形式はとても柔軟で、ここでは例がかろうじて利用できる整形のオプションの表層をこすります。これら整形文字列のシンタックスについてのより多くの情報について、PythonオンラインドキュメントのFormat Specification"節をみてください。
正規表現を用いた柔軟なパターンマッチング
Pythonのstr型のメソッドは、文字列データの整形や分割や操作のための力強いツールの集合をあなたに与えます。しかし、Pythonの組み込みの正規表現モジュールではより力強いツールさえ利用可能です。正規表現は大きな題材です:その題材でかかれた全体的な本(Jeffrey E.F. Friedl's Mastering Regular Expression, 3rd Editionを含みます)があることがあるので、ただの小節の中でちゃんと説明することが難しいでしょう。
ここで私の目的は、Pythonでそれらを使う方法の基本的なアイデアと同じように正規表現を使ってあてはめられるかもしれない種の問題に対しての思案を与えることです。Further Resources on Regular Expressionでより学ぶためのいくつかの参照を提案します。
基本的に、正規表現は文字列における柔軟なパターンマッチングの意味です。もししばしばコマンドラインを使うなら、多分""文字をつかった柔軟なマッチングのこの類に親しめるでしょう、それはワイルドカードとして作用します。例えば、間に任意の文字にマッチさせるために""ワイルドカードを使うことによってファイル名にPythonをもったすべてのIPython notebook(すなわち、.ipynb拡張子のファイル)をリストできます。
In [38]:
!ls *Python*.ipynb
01-How-to-Run-Python-Code.ipynb 02-Basic-Python-Syntax.ipynb
正規表現はこのワイルドカードのアイデアを幅広い範囲の柔軟な文字列マッチングのシンタックスへ生み出します。背正規表現に対するPythonインタフェースは組み込みのreモジュールに含まれます、簡単な例として、文字列のsplit()メソッドの機能性を重複するのにこれを使いましょう:
In [39]:
import re
regex = re.compile('\s+')
regex.split(line)
Out[39]:
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']
ここで、正規表現をまずコンパイルしています、それで文字列を分割するためにそれを使いました。Pythonのsplit()メソッドはただ空白間のすべての部分文字列のリストを返すだけなのに対して、正規表現のsplit()メソッドは、入力のパタンに対してマッチする間のすべての部分文字列のリストを返します。
この場合、入力は"\s+"です、"\s"は任意の余白(空白、タブ、改行、その他)にマッチする特別な文字です、そして"+"はそれを先行する要素の一つまたはそれ以上を示す文字です。このように、正規表現は一つまたはそれ以上の空白を構成する人にの部分文字列にマッチします。
split()メソッドはここで、基本的にこのパターンマッチングのふるまいの上に組み立てられる便利な手続きです。
より基本的なものはmatch()メソッドです、それは文字列のはじまりがパターンにマッチするかどうかを伝えるでしょう:
In [40]:
for s in [" ", "abc ", " abc"]:
if regex.match(s):
print(repr(s), "matches")
else:
print(repr(s), "does not match")
' ' matches
'abc ' does not match
' abc' matches
split()のように、(str.index()やstr.find()のように)最初のマッチを見つけたり、または(str.replace()のように)見つけて置換する便利な手続きに似ているものがあります。
再び前からの行を使います:
In [41]:
line = 'the quick brown fox jumped over a lazy dog'
これで、str.index()またはstr.find()のように多くを操作するregex.search()メソッドを見てみましょう:
In [42]:
line.index('fox')
Out[42]:
16
In [43]:
regex = re.compile('fox')
match = regex.search(line)
match.start()
Out[43]:
16
同様に、regex.sub()メソッドはstr.replace()と同じように操作します:
In [44]:
line.replace('fox', 'BEAR')
Out[44]:
'the quick brown BEAR jumped over a lazy dog'
In [45]:
regex.sub('BEAR', line)
Out[45]:
'the quick brown BEAR jumped over a lazy dog'
ちょっとした考え方で、他の本来の文字列演算はまた正規表現のようにキャストされます。
より洗練された例
しかし、あなたはたずねるかもしれないですね、なぜより直感的で単純な文字列メソッドよりもむしろ、より複雑で明らかな正規表現のシンタックスを使わないのかと。利点は正規表現がよりはるかなる柔軟性を提供することです。
ここで、より複雑な例を考えましょう:メールアドレスにマッチする共通のタスクです。単純に一つの正規表現(解読できない何か)を書くことによって、はじめてみましょう、そしてどのようになるか通してみてみましょう。さあいきます:
In [46]:
email = re.compile('\w+@\w+\.[a-z]{3}')
これを使うことにより、もしドキュメントからの行を与えているなら、すばやくメールアドレスのようなものを取り出せます。
In [47]:
text = "To email Guido, try guido@python.org or the older address guido@google.com."
email.findall(text)
Out[47]:
['guido@python.org', 'guido@google.com']
(これらのアドレスが全体的にお化粧されていることに留意しましょう、それらは多分Guidoに連絡するよりよい方法です)
たぶん出力でアドレスを隠すために、これらのメールアドレスを他の文字列で置き換えるような、さらなる操作ができます:
In [48]:
email.sub('--@--.--', text)
Out[48]:
'To email Guido, try --@--.-- or the older address --@--.--.'
最後に、もし本当に任意のメールアドレスにマッチさせたいなら、先行する正規表現はシンプルすぎます。例えば、接尾部のいくつかの共通ドメインのうち一つでおわる算術文字でつくられるアドレスだけを許すでしょう。そう、例えば、ここで使われるピリオドがアドレスの部分を探すだけを意味します:
In [49]:
email.findall('barack.obama@whitehouse.gov')
Out[49]:
['obama@whitehouse.gov']
これは、もし注意深くなければ、いかに許さない正規表現をなしえるかを示します。もしオンライン上で探すなら、あなたは、すべての有効なメールアドレスにマッチするであろう、正規表現のためのいくつかの提言を見つけられます、しかし、用心してください、それらはここで使ったシンプルな表現よりはるかに多くのものが含まれています。
正規表現シンタックスの基本
正規表現シンタックスはこの短い節の題材よりはるかに大きすぎます。依然としてちょっとした近しいもので長い道をいきましょう。ここで私は基本構造をみてきました、そして、より学べる完全なリソースをリストします。私の願いは次のはやい教材があなたがこれらのリソースを効果的に使えるようにすることです。
単純な文字列は直接マッチされます
もし文字や桁数に対しての単純な文字列上の正規表現を組み立てるなら、正確な文字列にマッチするでしょう。
In [50]:
regex = re.compile('ion')
regex.findall('Great Expectations')
Out[50]:
['ion']
特別な意味をもついくつかの文字
単純な文字や数値は直接マッチする一方で、正規表現の中で特別な意味をもつ役に立つ文字があります。それらは:
. ^ $ * + ? { } [ ] \ | ( )
これらのうちいくつかの意味はすぐに説明するでしょう。同時に、もし任意の文字に直接マッチしたいならバックスラッシュでエスケープできることを知るべきでしょう:
In [51]:
regex = re.compile(r'\$')
regex.findall("the cost is $20")
Out[51]:
['$']
r'$'の中の序文のrはそのままの文字列を示します、標準的なPythonの文字列では、バックスラッシュは特別な文字を示すのに使われます。例えば、タブは"\t"で示されます:
In [52]:
print('a\tb\tc')
a b c
そのような代替は、そのままの文字列の中で作られません:
In [53]:
print(r'a\tb\tc')
a\tb\tc
この理由から、あなたは正規表現でバックスラッシュを使ってもそうでなくても、そのままの文字列を使うことはよい実践です。
特別な文字は文字グループにマッチできます
正規表現中の""文字は特別な文字をエスケープするように、つまりそれらを普通の文字に変えるように、それはまた普通の文字に特別な意味を与えるのに使われます。これら特別な文字は特別な文字のグループにマッチします、そしてまえにそれらをみています。まえのメールアドレス正規表現の中で、"\w"文字を使いました、これは任意のアルファベットと数字を組み合わせた文字にマッチする特別な印です。同様に、単純なsplit()の例で、私たちはまた"\s"を見ました、これは任意の空白文字を示す特別な印です。
一緒にこれらを出すことで、それらの間の空白で任意の二つの文字・桁にマッチするであろう正規表現を作れます:
In [54]:
regex = re.compile(r'\w\s\w')
regex.findall('the fox is 9 years old')
Out[54]:
['e f', 'x i', 's 9', 's o']
この例は正規表現の柔軟性と力で暗示しはじめます。次の表は一般的に使いやすいこれら文字のいくつかを一覧します:
Character | Description |
---|---|
"\d" | Match any digit |
"\D" | Match any non-digit |
"\s" | Match any whitespace |
"\S" | Match any non-whitespace |
"\w" | Match any alphanumeric |
"\W" | Match any non-alphanumeric char |
これは包括的な一覧または記述ではないです、より詳細については、Pythonの正規表現シンタックスドキュメントを見てください。
大括弧は個別の文字グループにマッチします
もしあなたに特別十分でない組み込みの文字グループがないなら、興味のある任意の文字の集合を決めるために大括弧を使うことができます。例えば、次は任意の小文字の母音にマッチするでしょう:
In [55]:
regex = re.compile('[aeiou]')
regex.split('consequential')
Out[55]:
['c', 'ns', 'q', '', 'nt', '', 'l']
同様に、例えば範囲を特定するためのダッシュを使えます:例えば[a-z]は任意の小文字のマッチするでしょう、"[1-3]"は"1","2","3"のいずれかに実際にマッチするでしょう、一つの桁によって従った先頭文字からなる特定の数値のコードをドキュメントから抽出する必要があるかもしれません。以下のようにこうできます:
In [56]:
regex = re.compile('[A-Z][0-9]')
regex.findall('1043879, G2, H6')
Out[56]:
['G2', 'H6']
ワイルドカードは繰り返す文字にマッチします
もしある文字列にマッチしたいなら、つまり、一行中の三つの文字と数値のキャラクタ、例えば"\w\w\w"とかけます。これはありふれたもののようですが、繰り返しにマッチする特定のシンタックスがあります、それは数を使った中括弧です:
In [57]:
regex = re.compile(r'\w{3}')
regex.findall('The quick brown fox')
Out[57]:
['The', 'qui', 'bro', 'fox']
任意の数の繰り返しにマッチするのに利用できる印もまたあります、例えば"+"キャラクタは、先行するものの一つまたはそれ以上の繰り返しにマッチするでしょう:
In [58]:
regex = re.compile(r'\w+')
regex.findall('The quick brown fox')
Out[58]:
['The', 'quick', 'brown', 'fox']
以下は、正規表現の使用で利用できる繰り返しのマーカーの表です:
Character Description Example
? Match zero or one repetitions of preceding "ab?" matches "a" or "ab"
* Match zero or more repetitions of preceding "ab*" matches "a", "ab", "abb", "abbb"...
+ Match one or more repetitions of preceding "ab+" matches "ab", "abb", "abbb"... but not "a"
{n} Match n repetitions of preeeding "ab{2}" matches "abb"
{m,n} Match between m and n repetitions of preceding "ab{2,3}" matches "abb" or "abbb"
念頭にあるこれらの基本で、メールアドレスのマッチに戻りましょう:
In [59]:
email = re.compile(r'\w+@\w+\.[a-z]{3}')
いまはこの意味を理解できるでしょう:確かな三つの小文字まで続くピリオド"."(バックスラッシュエスケープの必要性に留意しましょう)まで続く一つまたはそれ以上の数字文字キャラクタ"\w"、まで続く"@"のサイン、まで続く一つまたはそれ以上の数値文字キャラクタ("\w+")が欲しかったのです。
さてもしObamaメールアドレスにマッチするためにこれを修正したいなら、大括弧の記述を使ってそうできます:
In [60]:
email2 = re.compile(r'[\w.]+@\w+\.[a-z]{3}')
email2.findall('barack.obama@whitehouse.gov')
Out[60]:
['barack.obama@whitehouse.gov']
"\w+"を"[\w.]"にかえましたので、任意の文字数値キャラクタまたはピリオドにマッチするでしょう。このより柔軟なパターンで、より広いメールアドレスにマッチできます(依然としてすべてでないですが、あなたは他のこの種の短いものを識別化できますか?)
括弧が抽出のためのグループを示します
メールアドレスをマッチするもののように複合の正規表現について、しばしばすべてのマッチよりもむしろ構成要素を抽出したいです。これは結果をまとめる括弧を使うことでなされます:
In [61]:
email3 = re.compile(r'([\w.]+)@(\w+)\.([a-z]{3})')
In [62]:
text = "To email Guido, try guido@python.org or the older address guido@google.com."
email3.findall(text)
Out[62]:
[('guido', 'python', 'org'), ('guido', 'google', 'com')]
みているように、このグルーピングは実際にメールアドレブの部分構成要素のリストを抽出します。
さらなる続きにいき、"(?P)"シンタックスを使って抽出された構成要素に名前をつけれます。それはグループがPython辞書のように抽出可能という場合です。
In [63]:
email4 = re.compile(r'(?P<user>[\w.]+)@(?P<domain>\w+)\.(?P<suffix>[a-z]{3})')
match = email4.match('guido@python.org')
match.groupdict()
Out[63]:
{'domain': 'python', 'suffix': 'org', 'user': 'guido'}
(ここで扱われない力強い正規表現シンタックスのいくつかと同じように)これらのアイデアを結合することは、Pythonの文字列からの情報をはやく柔軟にして抽出できます。
正規表現についてのさらなる資料
上記のディスカッションはこの大きな題材の単に早い(そして完全には遠い)扱いです。もしもっと学びたいなら、次の資料をお勧めします:
Pythonのreパッケージドキュメントです:私はちょうどそれらを使う毎回について正規表現を使う方法を即座に忘れることに気づきます。今基本に戻すなら、私はこのページが、正規表現内での各々の特別な文字やシーケンスが意味することを呼び起こす、信じられない価値のある資料にになていると気づきます。Pythonの公式な正規表現のハウツーがあります:Pythonの正規表現に対するより説話的なアプローチです。
Mastering Regular Expressions (OReilly, 2006) は、その題材についての500ページ以上の本です。もし本当にこの題材の完全な扱いを欲しいなら、これはあなたのための資料です。
大きな視野で実施中の正規表現と文字列操作のいくつかの例については、Pandasをみましょう:Labeled Column-oriented Data は、Pandasパッケージの中で文字列データの表を通してこれらの種類の表現を適用するのを見るものです。