 | レベル: 初級 David Mertz, Ph.D (mertz@gnosis.cx), Author, Gnosis Software, Inc.
2001年 2月 01日 V2.0のリリースに伴い、Pythonプログラマーは最近、ぴかぴか光る新しいおもちゃを手に入れました。Python 2.0は、以前のPython版の長所を生かして構築されていますが、多くの新しい便利さと機能も追加されています。この記事で著者は、Pythonの最新版の印象と、それを効果的に使用するためのいくつかのヒントを述べています。
2000年10月にリリースされたPython 2.0には、多くの新しい言語機能が取り入れられているほか、いくつかの新しい標準モジュールも組み込まれています。Guido van Rossumの長所の1つ、つまり、Pythonコミュニティーで、「慈しみのある人生の独裁者 (BDFL)」という愛情のこもった称号がまさにぴったりの長所は、Pythonの変更に対する彼の保守主義です。彼は各Python版間の変更はまず行いません。たとえ変更する場合でも、事前に何か月、何年もかけて検討し議論します。これはPythonの大きな逆方向互換性や順方向互換性に役立つほか、各種のプラットフォームやバージョンにまたがってPythonプログラムを実行する場合の 互換性にも役立ちます。つまり、Python 2.0は、Python 1.5xの言語定義からのかなり大きな飛躍を示しています。幸いにも、Python 2.0はまだ多くの逆方向互換性を保持していますから、たいていの場合、変更部分には非常に "Python的な" 特性が残されています。
ところで、短命のPython 1.6が2000年9月にリリースされたということは、注目するに値します。このリリースはちょっとした珍しい面を持っています。つまりこのリリースは、Pythonの中心開発チームの契約上の義務から生まれたもので、この開発チームは、1.6/2.0の開発と同じ時期に新しい組織上の本拠地を見つけました。Python 1.6は、大部分がPython 2.0と類似していますが、新しい版をインストールする場合には、Python 2.0をインストールすることをお勧めします。
変更箇所の要約については、参考文献を参照してください。この記事では、わたしが最も重要で面白いと思っていることについて個人的な評価を記載しています。皆さんにとって興味のある変更部分については、一部取り上げていないものがあるかもしれません。
リスト内包とzip()
わたしにとって最も刺激的なPython 2.0の新機能は、リスト内包 の追加です。(数学の心得のある読者であればだれでも、この機能が、Zermelo-Frankel集合論の内包の原理にならって、他の関数言語で「ZF内包」とも呼ばれていることに気付くでしょう。)
多くの読者は、上のパラグラフで「他の関数言語」という変わった言葉を使っていることに気付かれたでしょう。あなたは、Pythonプログラマーとして、Python 1.0以降、(混合した) 関数言語を使ってプログラミングをしてきました。もちろん、あなたが、lambda()、map()、reduce()、filter() などの組み込み関数を使用するのを習慣にしていなければ、これらの関数機能は使用していないでしょう。しかし、これらの関数機能を使用している場合でさえ、Pythonでは常に、関数パラダイムについて考えなくても済むようにしています。
いずれにしても、リスト内包はPythonの組み込み関数が実行する機能の大部分を実行しますが、その実行は、読み取りも理解も簡単なもっとコンパクトな方法で行われます。それでは、活動中の簡単なリスト内包の例から始めましょう。
リスト内包の例
>>> xs = (1,2,3,4,5)
>>> ys = (9,8,7,6,5)
>>> bigmuls = [(x,y)for xin xsfor yin ysif x*y > 25]
>>>print bigmuls
[(3,9), (4,9), (4,8), (4,7), (5,9), (5,8), (5,7), (5,6)]
|
この例では、タプルのリストを作成しました。ここでは、各タプル・エレメントが他のリストから取り出され、各リスト・エレメントが何らかの特性を満たしています。if 文節がなければ、順列だけを作成します (これだけでも役立つ場合が多い)。しかし、if 文節があれば、リストのfilter() 型の枝取りを作成することができます。ちなみに、1つのリスト内包で複数のif 文節を使用することができます。
リスト内包機能には、基本的に新規の機能はありません。確かにPython 1.5xでも同じ結果を出すことができますが、明確性が劣ります。たとえば、以下のようなより冗長な (かつ明確性に欠ける) 技法を用いても、同じことが行えます。
V1.5x版の技法の比較
>>># Functional-style spaghetti for list comprehension
>>> filter(lambda (x,y): x*y > 25,
..... map(None, xs*len(ys),
..... reduce(lambda s,t: s+t,
..... map(lambda y: [y]*len(xs), ys))))
[(3, 9), (4, 9), (5, 9), (4, 8), (5, 8), (4, 7), (5, 7), (5, 6)]
>>># Nested loop procedural style for list comprehension
>>> bigmuls = []
>>>for xin xs:
.....for yin ys:
.....if x*y > 25:
..... bigmuls.append((x,y))
>>>print bigmuls
[(3, 9), (4, 9), (4, 8), (4, 7), (5, 9), (5, 8), (5, 7), (5, 6)]
|
この例では、ネストされたプロシージャー・ループの方が、関数型の呼び出しよりも明確です (おそらく読者は、この関数アプローチの方が優れていることに気付くでしょう) 。しかし、どちらのアプローチも、リスト内包型よりもはるかに明確性が劣ります。
一部のプログラマーの作業では、リスト内包が、多くの関数型組み込み機能の代わりをするだけでなく、多くのネスト・ループの代わりもします。
Python 2.0に含まれている1つの新規の組み込み関数は、リスト内包と併用すると特に有効です。ジッパーの歯を想像すれば、zip() の機能が推察できます。つまり、複数のシーケンスがタプルのリストに組み込まれています (各タプルが、各呼び出しシーケンスからのエレメントを1つずつ持っています)。完全なリスト順列を使用するリスト内包は必要ではないが、複数リストの対応するエレメントを使用するリスト内包のみが必要な場合に、この方法がしばしば役立ちます。たとえば、以下のとおりです。
The zip() function
>>> zip(xs,ys)
[(1, 9), (2, 8), (3, 7), (4, 6), (5, 5)]
>>> [(x,y)for (x,y)in zip(xs, ys)if x*y > 20]
[(3, 7), (4, 6), (5, 5)]
|
ユニコード・サポート
Python 2.0へのもう1つの追加機能はユニコード・サポートです。ユーザー・プログラムで各国間共通文字セットを使用する必要がある場合は、この機能が不可欠です。もちろん、わたしと同じように、あなたがASCII以外の文字に対する特定の要件がなければ、ユニコード・サポートは実際上関係ありません。幸いなことに、Python 2.0でのユニコードのインプリメンテーションは非常によく設計されていますので、他の操作の邪魔になることは一切ありません。
ユニコード・ストリングはいくつかの方法で表現することができます。エスケープ文字の場合は、シーケンス "\uHHHH" が使用できます。ここで、HHHH は4桁の16進数です。これより長いストリングを入力する場合は、新規のユニコード引用構文u"string" を使用することができます。このスタイルは、r"string" 引用スタイルとよく似ています。この引用スタイルは、エスケープ・コードをPythonレベルで解決せずに正規表現を構成する場合に使用されます (それは、正規表現が同じエスケープ・コードの一部を使用するからです)。もちろん、ユニコード引用構文を使用するためには、引用語句間にユニコード文字を入力できるテキスト・エディターをインストールする必要があります。
8ビット・ストリングとユニコード・ストリング間の変換 (および異なるユニコード・エンコード間の変換も) は、新規のコーデックス・モジュールを使用して行われます。
関数/メソッド呼び出し構文
もう1つのすばらしい構文の機能強化が、関数呼び出しについて行われました。現在では、引数のタプルまたはキーワード引数のディクショナリー (あるいはその両方) を使用して、直接関数を呼び出せるようになっています。リスト内包の場合と同様、基本的に新しい機能は追加されていませんが、いくつかの共通作業の表現がより明確かつ簡潔になっています。もちろん、Pythonの各メソッドはクラス・インスタンスにだけ結合される関数ですので、関数やメソッドの働きが変わることはありません。
Pythonプログラマーは、関数定義内で追加の定位置引数やキーワード引数を定義するための前の構文に慣れるでしょう。たとえば、以下のようになります。
追加の定位置引数およびキーワード引数の定義
>>>def myfunc(this, that, *extras, **keywords):
....print"Required arguments:", this, that
....print"Extra arguments:",
....for argin extras:print arg,
....print"\nDictionary arguments:"
....for key,valin keywords.items():print"**", key,"=", val
....
>>> myfunc(1)
Traceback (innermost last):
File"<interactive input>", line 1,in ?
TypeError:not enough arguments; expected 2, got 1
>>> myfunc(1,2)
Required arguments: 1 2
Extra arguments:
Dictionary arguments:
>>> myfunc(1,2,3,4,5)
Required arguments: 1 2
Extra arguments: 3 4 5
Dictionary arguments:
>>> myfunc(1,2,3, spam=17, eggs=32)
Required arguments: 1 2
Extra arguments: 3
Dictionary arguments:
** spam = 17
** eggs = 32
|
Python 2.0では、関数定義で使用する場合と同じ関数呼び出し の規則が追加されています。たとえば、以下のとおりです。
関数呼び出しの規則
>>> argdict = {'spam':'tasty','eggs':'over easy'}
>>> arglist = [1,2,3,4,5]
>>> myfunc(*arglist, **argdict)
Required arguments: 1 2
Extra arguments: 3 4 5
Dictionary arguments:
** spam = tasty
** eggs = over easy
|
同じ結果を出すこと (名前付きリスト、つまり、おそらくは実行時に動的に作成された名前付きリストを介して引数を渡すこと) は、Pythonでは常に可能でした。しかし、新規の呼び出し構文は、従来のapply() 関数の使用に比べ、より便利になっています。
増加割り当て
Pythonに、割り当てのためのショートカットが設けられました。このショートカットは、C、Perl、Awk、Java、その他各種言語のプログラマーに役立つでしょう。演算子を等号の先頭に付けておき、その元の値に基づいて、変数の割り当て値を変更できるようになりました。たとえば、以下のとおりです。
割り当てでの新しいショートカット
>>> i = 1
>>> i += 1 ; i
2
>>> i *= 3 ; i
6
>>> i /= 2 ; i
3
>>> str ="Spam and eggs"
>>> str +="...and sausage and spam and bacon" ; str
'Spam and eggs...and sausage and spam and bacon'
|
セマンティクスの上からは、これらの増加演算子は、通常割り当ての左側にある左側の変数を反復し、対応する演算子と第2オペランドをその後に続ける場合とまったく同じ操作をします。したがってそういう意味で、これは統語上の味付けに過ぎません。
しかし増加割り当ては、実際は、パフォーマンスの向上である点に注目してください。わたし自身はまだそのベンチマークを行っていませんが、検討結果では、増加割り当てによって検索とオブジェクト割り振りの一部が節約されるように思われます。数値の場合は、このことは重要でありません。しかし、マルチ・メガバイトのストリングを扱うような場合は、増加割り当てを使用することにより、処理速度が向上し、メモリーの使用を減らすことがあります。
ガーベッジ・コレクション
Pythonのメモリー管理は、日常のPythonプログラマーにとって相当に悩ましい問題であると思われます。オブジェクトがどの名前からもアクセスできないようになったときは、Pythonでは、伝統的に参照カウント方式を使用してそれらのオブジェクトを削除してきました。しかし、プログラムで周期的な参照を使用する場合、参照カウント方法論は、理論上メモリーをリークする傾向があります。たとえば、以下のコードは参照カウントを破壊します。
Pythonでの周期的な参照
>>>class MyClass:pass
....
>>> myobject = MyClass()
>>> myobject.me = myobject
>>>del myobject
|
この時点では、myobject へのアクセスは不可能ですが、それは削除されていません。なぜならば、参照カウントは2回増分されたが、減分は1回しか行われていないからです。
これは感心しないことだと思われるかもしれませんが、ほとんどのプログラマーは、このようなコードが原因で実際の問題にぶつかることはありません。多くの場合、周期参照が使用されることはまずありませんし、たとえ使用されたとしても、たいていメモリー・リークは小さいものです (危険な振る舞いの人工的なケースを簡単に作成することができます。たとえば、myobject.big='#'*10**6 を上記の例に追加します)。
いずれの場合もPython 2.0は、マーク・アンド・スイープ・ガーベッジ・コレクション (GC) のためのコンパイル時オプションを 追加します。ほとんどのPython 2.0配布プログラムは、このオプションを使ってコンパイルできると思われますが、必要であれば、ガーベッジ・コレクションをオフにするユーザー独自のPython版をコンパイルすることもできます。どちらにせよ、参照カウントは引き続き使用されますので、これは単に、上記のようなリークがクリーンアップされるかどうかの問題です。
組み込みシステムのような一部のプラットフォームでは、GCが望ましくない場合があります。ガーベッジ・コレクションはいくらかのCPUサイクルを取ります (多くはなく、いくらかです)。おそらくこれはもっと重要なことですが、参照カウントのプログラム振る舞いが明確であるのに対して、ガーベッジ・コレクションでは明確ではありません。つまり、ガーベッジ・コレクションの場合、いくらかのCPUサイクルが取られるタイミングがつかめません。したがって、GC版のPythonを使用すれば、同じプログラムが実行と実行の間で違った振る舞い (タイミングの点で) を生じさせる原因になります。
これらの問題は理論的には魅力がありますが、ほとんどのプログラマーの場合、今後はこれらの問題を無視したほうがよいでしょう。ユーザーが選択したどのPython配布プログラムでも、使用するプラットフォームでまず間違いなく正しく稼働します。したがって、GCを使用可能にしたり使用不可にする理由を正確に理解できていない場合は、このことをあまり気にしないようお勧めします。
印刷方向
van Rossumと他のチーム・メンバーたちはPython 2.0について優れた仕事をしましたが、1つの欠点もPythonに残しました。それはほどほどに役に立ちますが、わたしの意見では (また他の多くのPythonプログラマーたちの意見でもありますが)、必要もないのに、そこにはまったく新しい (しかも嫌な) 統語機能を取り入れています。しかし、ほとんどのプログラマーは、この欠点は単なる策略ではないかと疑っています。つまり、Pythonの残りの部分がいかに分かりやすくすばらしい出来になっているかを、はっきりと目立つようにするためだと言うのです。
print ステートメントは、ファイル・オブジェクトの.write() メソッドにはない、ちょっとしたマジックを行います (また、sys.stdoutは、print が書き込む先の別のファイル・オブジェクトです)。print ステートメントでは、それぞれが任意のPython型の複数の引数を使用することができます。末尾コンマを使用すれば、print ステートメント間で行継続が行えるので便利です。一方、デフォルトでは、すべてがそれぞれの行に書き込まれます。総体的に言って、print は、何らかの情報をプログラムからコンソールへ運ぶための手軽な手段です。
多くのPythonプログラマーは、このprint 魔法を使って、他のファイル・オブジェクト (たとえば、sys.stderr、正規ファイル、各種のモジュールで提供される "ファイルに似た" オブジェクトなど) に書き込みをしたいと 思っています。わたしは、これを行うための適切な方法は、.print() メソッドをファイル・オブジェクトに追加して、そこでこの魔法を実行することだと思います。しかし、Python 2.0は、"リダイレクト演算子">> をprint ステートメントに追加して、この機能を追加します。たとえば、以下のとおりです。
printステートメントでのリダイレクト演算子
>>>import sys
>>>print >> sys.stderr,"spam", [1,2,3], 45.2
spam [1, 2, 3] 45.2
|
これで機能します。そしてすばらしい機能を追加しますが、その反面、Pythonを他の特定プログラム言語の "実行可能な回線ノイズ" に似た感じに近づけます。
参考文献
著者について  | 
|  | David Mertz氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。David Mertz氏の連絡先はmertz@gnosis.cxです。 |
記事の評価
|  |