魅惑的な Python: Python におけるテキスト処理

初めての方へ

他の人気のある幾つかのスクリプト言語と同様、Python は、テキスト・データをスキャンしたり、取り扱ったりするのに優れたツールです。この記事は、Python が初めてというプログラマーのために、Python のテキスト処理機能を要約したものです。この記事では、正規表現の一般的な概念をいくつか説明し、テキスト処理において、正規表現をいつ使用すべき (またはすべきでない) かについてアドバイスします。

David Mertz (mertz@gnosis.cx), President, Gnosis Software, Inc.

David Mertz photoDavid Mertzは、ニーチェを傍らに、これらが昔の言語学者の思想なのだと書きたいのですが、その嘘は自ずと化けの皮がはがれることでしょう。ただ、おそらく彼の(まさしくここで只で宣伝してもらえる)近刊書Text Processing in Pythonは、いつの日にか、言語学のサイバー版と間違えられることになるのではないでしょうか。Davidのメール・アドレスはmertz@gnosis.cx です。その生活は、http://gnosis.cx/publish/ でじっくり観察できます。今回のコラム、以前のコラム、あるいは今後のコラムについて、ご意見やご提案があればお寄せください。



2000年 9月 01日

Python とは何か?

Python は、Guido van Rossum により開発された、自由に使用できる、非常に高水準のインタープリター言語です。Python は、明解な構文に加えて、優れたオブジェクト指向 (オプションではありますが) の意味付け機能を併せ持っています。Python は、幅広い使用可能性をもち、高度の移植可能性を特徴としています。


ストリング -- 不易 (immutable) シーケンス

多くの高水準なプログラミング言語のように、可変長ストリングが Python の基本型です。Python は、プログラマーがあまり考慮を払う必要がない場合には、メモリーを「舞台裏で」ストリング (または他の 値) を保持するために割り当てます。Python にはまた、他の高水準言語 (HLL) にはないストリング処理機能が幾つかあります。

Python において、ストリングは「不易シーケンス」です。プログラムは、どんなシーケンスであっても、ストリングのエレメントやサブシーケンスを参照することができます。しかし、タプルのようにストリングは「決まった場所で」変更することはできません。Python は、柔軟な「スライス」命令でサブシーケンスを参照します。このフォーマットは、スプレッドシートの行または列の範囲に似ています。以下の対話式セッションが示しているのは、ストリングとスライスの使用法です。

ストリングとスライス
>>> s ="mary had a little lamb"
>>> s[0]         # index is zero-based
'm'
>>> s[3] ='x'   # changing element in-place fails
Traceback (innermost last):
  File"<stdin>", line 1,in ?
TypeError: object doesn't support item assignment
>>> s[11:18]     # 'slice' a subsequence
'little '
>>> s[:4]        # empty slice-begin assumes zero
'mary'
>>> s[4]         # index 4 is not included in slice [:4]
' '
>>> s[5:-5]      # can use "from end" index with negatives
'had a little'
>>> s[:5]+s[5:]  # slice-begin & slice-end are complimentary
'mary had a little lamb'

もう 1 つの優れたストリング命令は、簡単な in キーワードです。これにより、2 つの分かりやすく有益な構成が提供されます。

in キーワード
>>> s ="mary had a little lamb"
>>>for cin s[11:18]:print c, # print each char in slice
...
l i t t l e
>>>if'x'in s:print'got x'  # test for char occurrence
...
>>>if'y'in s:print'got y'  # test for char occurrence
...
got y

Python には、ストリング・リテラルを構成する方法が幾つかあります。開始記号および終了記号が対応している限り、単一引用符または二重引用符を使用することができます。また、この他にもよく使われる有用な引用のバリエーションがあります。ストリングに行の中断、または組み込まれた引用がある場合、三重引用符を使うと、ストリングを定義するのが簡単です。以下がその例です。

三重引用符の使い方
>>> s2 ="""Mary had a little lamb
... its fleece was white as snow
... and everywhere that Mary went
... the lamb was sure to go"""
>>>print s2
Mary had a little lamb
its fleece was white as snow
and everywhere that Mary went
the lamb was sure to go

単一引用符または三重引用符で囲まれたストリングは両方とも、前に "r" 文字 を置くことで、Python によってインタープリットされない正規表現の特殊文字であることを示します。以下がその例です。

"r- ストリング" の使い方
>>> s3 ="this \n and \n that"
>>>print s3
this
 and
 that
>>> s4 = r"this \n and \n that"
>>>print s4
this \nand \n that

別の状況ではエスケープ文字を構成することもあるバックスラッシュが、"r- ストリング" においては、正規のバックスラッシュとして扱われます。これについては、後ほど正規表現に関するところで詳しく説明します。


ファイルとストリング変数

「テキスト処理」という場合、通常、ファイルのコンテンツを処理することを指しています。ストリング変数が扱える場合、テキスト・ファイルのコンテンツを、ストリング変数に読み取ることは、Python では非常に簡単です。ファイル・オブジェクトにより、3 つの "read" メソッドが提供されます。それらは、 .read() 、 .readline() 、および .readlines() です。これらのそれぞれが、一度に読み取られるデータ量を限定するために引数を用いることもありますが、普通は引数なしで使用されます。 .read() は、一度にファイル全体を読み取り、通常、ファイルのコンテンツをストリング変数に 入れるのに使用されます。一方、 .read() は、きわめてダイレクトにファイル・コンテンツをストリング表記します。そのため、このメソッドは順次行方式処理の場合は不要であり、ファイルが使用可能メモリーより大きな場合は使うことはできません。

.readline() と .readlines() は、非常に似ています。両方とも、以下のような構成で使用されます。

Python .readlines() の例
fh = open('c:\\autoexec.bat')
for linein fh.readlines():
   print line

.readline() と .readlines() との相違は、.readlines() が .read() のように一度にファイル全体を読み取るということです。 .readlines() は、自動的にファイルのコンテンツを Python の for ... in ... 構造によって処理できる行のリストへ構文解析します。一方、 .readline() は、一度に単一の行しか読み取らず、一般に .readlines() よりずっと遅くなります。 .readline() は、一度にファイル全体を読み取るのに十分なメモリーがない場合のみに使用すべきです。

ファイルを取り扱うのにスタンダード・モジュールを使用している場合、 cStringIO モジュールを使用して ストリングを「仮想ファイル」へ変えることができます ( StringIO モジュールは、モジュールをサブクラス化する必要がある場合に 代わりに使用することができますが、初心者がこれを行う必要はないでしょう)。以下がその例です。

cStringIO モジュール
>>>import cStringIO
>>> fh = cStringIO.StringIO()
>>> fh.write("mary had a little lamb")
>>> fh.getvalue()
'mary had a little lamb'
>>> fh.seek(5)
>>> fh.write('ATE')
>>> fh.getvalue()
'mary ATE a little lamb'

しかしながら、実ファイルとは異なり、 cStringIO 「仮想ファイル」は永続的ではないということを覚えておいてください。保管するためのステップ (たとえば、仮想ファイルを実ファイルへ書き込んだり、 shelve モジュールやデータベースを使用するなど) を行わない場合、仮想ファイルはプログラムの終了時に消えてしまいます。


スタンダード・モジュール: string

string モジュールは、おそらく Python 1.5.* 標準配布版の中で最も一般的に役に立つモジュールでしょう。実際、 string モジュール機能の多くが、Python 1.6 以降では組み込まれたストリング・メソッドになるようです (この記事の執筆時点では詳細は発表されていません)。最も確かなのは、テキスト処理タスクを実行するプログラムはいずれもこの行で始まることです。

string の使用を開始する方法
import string

一般的な経験から、 string モジュールを使用してある処理を行うことができる 場合、その方法がその処理を行う正しい 方法なのです。 re (正規表現) とは対照的に、 string 関数は、通常ずっと高速で、多くの場合、理解し保守するのにもっと容易です。C で書かれた高速モジュールをはじめとする第三者の Python モジュールは、特殊化された処理には使用できますが、移植性や親しみやすさといった点から考えると、可能な場合はできるだけ string を使用することをお勧めします。他の言語に慣れた人にとっても、多少の例外はありますが、string では歯がゆいということは思いの他多くはありません。

string モジュールには、関数、メソッド、およびクラスのような、幾つかの型があります。さらに、共通定数のストリングも含まれます。以下がその例です。

例 1 string の使い方
>>>import string
>>> string.whitespace
'\011\012\013\014\015 '
>>> string.uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

これらの定数は自分で書くことができますが、 string バージョンを使用すれば多かれ少なかれその定数が各国語、および その Python スクリプトを実行するプラットフォームに応じて正しいことを保証します。

string にはまた、共通の方法でストリングを変換する関数が含まれています (これを結合させて、非共通の変換を幾つかつくることもできます)。以下がその例です。

例 2 string の使い方
>>>import string
>>> s ="mary had a little lamb"
>>> string.capwords(s)
'Mary Had A Little Lamb'
>>> string.replace(s,'little','ferocious')
'mary had a ferocious lamb'

ここでは具体的に示されていない変換が、他にもたくさんあります。詳細については、Python のマニュアルをご覧ください。

さらに、 string 関数を使用して、サブストリングの長さや位置のようなストリング属性を引き渡すこともできます。以下がその例です。

例 3 string の使い方
>>>import string
>>> s ="mary had a little lamb"
>>> string.find(s,'had')
5
>>> string.count(s,'a')

最後に、 string は非常に Python らしい独特の機能を提供しています。 .split() と .join() のペアにより、ストリングとタプルの間での即時の変換が可能になります。これは大変役に立つものです。使用法は、簡単明快です。

例 4 string の使い方
>>>import string
>>> s ="mary had a little lamb"
>>> L = string.split(s)
>>> L
['mary','had','a','little','lamb']
>>> string.join(L,"-")
'mary-had-a-little-lamb'

もちろん実際には、リストとともに .join() 以外の何か他のものを、すぐ後に一緒に使う場合もあります (おそらく、よく使われる for ... in ... 構造を含むもの)。


標準モジュール: re

re モジュールにより、少し古い Python コードで使われていた regex および regsub モジュールは使われなくなりました。 regex には、まだいくつか限られたケースで利点が残っていますが、その利点は小さく、新しいコードで使用する価値はありません。このような使われなくなったモジュールは、将来的な Python のリリースからは除去されるようです。また、リリース 1.6 には、改良されたインターフェース互換性のある re モジュールが含まれます。そのため、正規表現には re を使うようにしてください。

正規表現は、複雑です。正規表現については、これを 1 つのトピックとして本が書けるほどで、実際、多くの人々が書いています。この記事では、正規表現の「ゲシュタルト (形態)」を捕らえ、読者にそれを把握してもらおうとしています。

正規表現は、テキスト内で発生するパターンを記述する簡潔な方法です。決まった文字が現われますか? 特定の順番ですか? サブパターンが、所定の回数繰り返されますか? 他のサブパターンでは、一致状態が起こらなくなりますか? 概念として、これはユーザーが自然言語の中で直感的に記述する方法と似ていなくもありません。秘けつは、正規表現の簡潔な構文の中でのこの記述のエンコード方式にあります。

正規表現を取り込む場合、たとえたった 1 行か 2 行のコードしか含まれなくても、それはプログラミング上の問題として扱ってください。これらの行は、効果的に 小さなプログラムを形成しています。

非常に小さなコードから開始してください。その一番基本的なレベルでは、いずれの正規表現にも特定の「文字クラス」のマッチングが含まれています。最も単純な文字クラスは単一文字で、これはリテラルとしてパターンに組み込まれています。しばしば、文字クラスのマッチングをしてください。中括弧で文字を囲むことでクラスを示すことができます。中括弧の中には、文字のセットとダッシュで示される文字範囲の両方を入れることができます。さらに、プラットフォームや各国語に応じて正確な、多くの名前付き文字クラスを使用することもできます。以下がその例です。

文字クラス
>>>import re
>>> s ="mary had a little lamb"
>>>if re.search("m", s):print"Match!"     # char literal
...
Match!
>>>if re.search("[@A-Z]", s):print"Match!"# char class
...    # match either at-sign or capital letter
...
>>>if re.search("\d", s):print"Match!"    # digits class
...

文字クラスを正規表現の「原子」として考えることもできますが、常にこれらの原子を「分子」にグループ化すべきでしょう。これは、grouping とrepetition の組み合わせを用いて行うことができます。グルーピングは、括弧により示されます。括弧内に含まれるいずれの副次式も、さらなるグルーピングや反復のための 原子であるかのように取り扱われます。反復は、次の幾つかの演算子の 1 つによって示されます。たとえば、"*" は「ゼロまたはそれ以上」 を意味します。"+" は、「1 つまたはそれ以上」を意味します。"?" は、「ゼロまたは 1」を意味します。たとえば、以下の例を見てください。

正規表現の例
ABC([d-w]*\d\d?)+XYZ

あるストリングがこの表現に一致するためには、"ABC" で始まり、"XYZ" で終わるものを含んでいる必要があります -- しかし、中間に何を持たなければならないでしょうか? その中間の 副次式は、 ([d-w]*\d\d?) です。その後には、「1 つまたはそれ以上」の演算子が続いています。そのため、ストリングの中間は 1 つ (または 2 つ、または 1,000 個) の 括弧内の副次式とマッチングするものからできていなければなりません。ストリング "ABCXYZ" は、マッチングしません。なぜなら、中間に必要なものがないからです。

この中にある副次式はどうなっていますか? それは、ゼロまたは 1 つ以上 の文字で始まり、範囲 d-w にあります。ゼロ文字が有効にマッチングされることに気を付けてください。これは、記述する際に英語の "some" を使用している場合、直感に反するかもしれません。次に、そのストリングは、正確に 1 桁の 数字を持っていなければなりません。それから、ゼロまたは 1 桁の 追加数字がなければなりません。(最初の数字クラスには、反復演算子はないので、ただ一回しか現われません。2 番目の数字クラスには、"?" 演算子があります。) 要するに、これは「1 桁か、または 2 桁の数字」 に変換されます。正規表現によりマッチングされるストリングには、以下のものがあります。

サンプル表現をマッチングするストリング
ABC1234567890XYZ
ABCd12e1f37g3XYZ
ABC1XYZ

以下は、正規表現によってマッチングされない いくつかの表現です ( なぜ、これらはマッチングされないか考えてみてください)。

サンプル表現をマッチングしないストリング
ABC123456789dXYZ
ABCdefghijklmnopqrstuvwXYZ
ABcd12e1f37g3XYZ
ABC12345%67890XYZ
ABCD12E1F37G3XYZ

正規表現の作成や理解に慣れるのには、少し練習する必要があります。しかしながら、一度、正規表現をマスターしたならば、自由自在に表現することができるでしょう。すなわち、実際にはもっと簡単な (しかも高速の) ツール (たとえば string のような) を使用して解決できる問題を解く場合には、直接正規表現を使用するほうが容易な場合がしばしばあります。

参考文献

  • Mastering Regular Expressions 、Jeffrey E. F. Friedl (O'Reilly and Associates、1997) は、正規表現に関する標準的で最も信頼できる参考書です。
  • 今でも広く使われ、非常に役に立つ初期のテキスト処理ツールへの良い入門書としては、 Sed & Awk、 Dale Dougherty and Arnold Robbins (O'Reilly and Associates、1997) をご覧ください。
  • Python 用の高速のテキスト操作ツールについては、 mxTextTools、を読んでください。
  • 正規表現の詳細については、

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=230456
ArticleTitle=魅惑的な Python: Python におけるテキスト処理
publish-date=09012000