Subclassing:アート・オブ・サブクラス前回は基調講演とカンファレンスの全体像について紹介しました。今回と次回ではPythonそのものの講演について紹介したいと思います。今回はCPythonというC言語で実装されたPythonの講演(一般的にPythonというときはCPythonを指します)について紹介します。Hettinger氏が発表されたことについてです。Raymond氏の講演は、Pythonでコーディングする上でのテクニックや考え方など、どれも示唆に富む内容で筆者にとっては興味深かった内容でした。2.7.2で実行しています。Python」というハンズオントレーニングでは、サンプルコードを見ながら、自分でコーディングしてみるといった、実際に手を動かすプログラムがありました。このハンズオンから、いくつかTIPSを紹介します。Pythonには、事前説明のビデオはありますが、実際のハンズオンのビデオがまだアップロードされていないようです。ハンズオン最適化3つの最適化のテクニックについて紹介されていました。グローバルな参照(組み込み関数、モジュール、グローバル変数)をローカルの参照に置き換える束縛メソッドを使う内部ループにおけるピュアPythonの関数呼び出しを最小化するまずは、グローバル参照と束縛メソッドの2つをdisモジュールを使って、Pythonのバイトコードにディスアセンブルして検証してみます。参照しようとした場合と、引数として渡すと仮定したときのバイトコード命令の比較です。後者の引数として渡すと仮定するとLOAD_GLOBALとLOAD_ATTRの命令がなくなります。Pythonは関数(メソッド)がファーストクラスオブジェクトなので、ローカル変数に割り当てることができます。この特性を利用して、例えば、ループ内でのグローバルな参照や属性参照をループの外へ切り出すことで高速化を図ることができます。「⁠Pythonの内包表記はなぜ速い?」の記事がとても分かりやすいです。3つ目の、内部ループにおけるピュアPythonの関数呼び出しを最小化するというのは、なるべく組み込み関数やC言語で実装された関数を使うということです。2つの引数を受け取って乗倍して返す組み込み関数pow()と、無名関数で実装した場合を比較すると違いが明らかです。loopさらにベクトル化により、リスト内包表記よりもmap()の方が速いようなことも言及されていました。次のようなコードです。10.6.7)で検証してみたところ、組み込み関数を使う場合はmap()の方が速いのですが、ピュアPythonの関数だと、リスト内包表記の方が速かったりします。Mapでも議論されているので、そちらも参考にしてください。時間計測timeitモジュールを利用するサンプルの1つとして、任意の関数の実行時間を計測するサンプルプログラムです。20))このプログラムを実行すると、リスト内包表記を使った処理とitertoolsモジュールのツールを使った処理の時間を計測して、実行時間が最も小さかったものを表示します。0.028668880462646484)筆者は、普段IPythonのtimeitの仕組みで実行時間を計測していますが、こういうやり方もあるんだなと参考になりました。ディクショナリで考えるPythonは、すべてディクショナリで考えられるからやってみようといった話も紹介されました。xという変数はglobals()が返すディクショナリでアクセスできます。x2ハンズオンでは、次のクラスをディクショナリで実装してみるのに挑戦しました。通常のクラスで実装したサンプルコードです。d.walk()実際、オブジェクトの属性は__dict__という特殊メソッドでアクセスできるディクショナリに格納されています。'shiro'}Pythonにおけるクラスの仕組みを理解するために、クラスをディクショナリで作るのに取り組んでみましょう。次のサンプルコードが与えられました。特殊メソッドの__(2重アンダースコア)を奇妙に思う方もいるかもしれませんが、実装そのものは違和感なく自然に書けてしまうところが、Pythonのクラスとディクショナリの密接な関係を感じさせます。pprint(d)このプログラムの実行結果です。Dogディクショナリが次のように表現されます。Animalディクショナリをセットすることで継承階層を実現しています。'Shiro'}ここからは自分で挑戦する3つの練習問題が与えられました。適切な実装があるかもしれませんが、筆者も挑戦してみました。最初の練習問題は、属性を参照する関数の実装です。'walk')(d)メソッドを取得して呼び出した実行結果です。Walking次の練習問題は、super()を定義することです。'walk')(d)Dogディクショナリのオブジェクト(インスタンス)でwalkをoverride_walk_function()としてオーバーライドして、親のメソッドを呼び出すようにしました。Walkingoverridden!最後の練習問題は、シンプルなhelp()関数を提供します。help_function(Dog)表示の整形を行っていませんが、クラス毎に属性とメソッドを別々に取り出しました。0x1004a12a8>}}}Pythonの名前空間はディクショナリで実現されていて、モジュールもクラスもインスタンスも一貫して扱えるところがPythonの簡潔な仕組みを支える要素でもあります。Tools」という講演では、(⁠比較的)最近に追加された言語の仕組みや標準ライブラリのツールについて発表されました。この講演の内容を紹介します。TOOLSから視聴できます。collections.Countercollections.Counterは、投票のような集計や、その集計結果の加工、演算に使われます。Cのbagクラスを参考にしているそうです。1})collections.Counterの実装Counterは、ディクショナリのサブクラスで実装されています。シンプルな実装なのでCounterのソースを読んでみると、API開発のお手本にも良さそうです。次の2つの特殊メソッドについて言及されていました。1でKeyErrorが発生しないことに気付いた方もいると思います。__missing__という特殊メソッドを定義することで、ディクショナリのキーが存在しないときの処理をフックできます。Counterでは、次のようにゼロを返す実装になっています。0__delitem__Counterに存在しないキーを削除しようとしたときは、KeyErrorを発生させないようになっています。キーが存在するときはdictにその処理を委譲しています。elem)collections.namedtuplecollections.namedtupleは、普通のタプルのように使えて、フィールド名でも要素にアクセスできます。既存のコードをクライアント側に影響を与えずに改善するツールとして便利だと紹介されていました。namedtupleのシンプルな実装例は次になります。__slots__に空のタプルをセットすることで、インスタンス化するときに属性情報をもった__dict__を作成せず、メモリを節約する狙いがあります。これは最適化のテクニックの1つです。_fieldsを使って拡張します。)インスタンスのプロトタイプとして使うプロトタイプとして定義し_replace()メソッドで要素を変更できます。color.yellow2これらのサンプルは標準ライブラリドキュメントでも紹介されています。3.2から追加されています。デコレーターの応用例にMemoizeデコレーター(メモ化)がありますが、それをもう少し便利にするツールです。lru_cacheは、途中の計算結果を保持するキャッシュのサイズを設けて、最後に使われてから最も時間が経過したエントリを削除します。3.2でのみ実行できます。このサンプルも標準ライブラリドキュメントから引用しています。cache_infoやcache_clearがデコレートされる関数の属性に追加されます。Idioms」という講演では、次の項目について標準ドキュメントから要点を抜き出したPythonのチートシートを紹介するような内容でした。いくつか抜き出して紹介します。IDIOMSから視聴できます。functioon比較とソート次のようなソート関数(メソッド)があります。yの比較をするときに__lt__という特殊メソッドを呼び出します。ソートされる順序を降順にしたい場合、次のように__lt__の返り値を反転させることで実現できます。1]またbisectモジュール以外のソート関数(メソッド)は、key引数でソートに使う値を指定することで、ソート順序をカスタマイズすることもできます。'd']operatorモジュールと組み合わせても便利です。リスト内のタプルの2番目の要素でソートします。3)]オブジェクトの属性でソートします。age=32)]引数でソート順序を逆にできます。TOも参考にしてください。イテレーターを使ったプログラミングPythonという言語がシンプル且つ簡潔たらしめる重要な概念がイテレーターであると説明されていました。__getitem__をサポートするシーケンスや__iter__をサポートする任意のオブジェクトが、繰り返し可能なオブジェクト(Iterable)になります(参考:第1回レポートイテレータープロトコルとジェネレーター⁠)⁠。ファイルを10バイトずつ読み込むサンプルコードが紹介されていました。普通に実装すると次のようになります。breakこの処理をイテレーターを使って実装すると次のようになります。do_somethingfunctools.partialは、一部の引数を渡して、関数の部分適用を行い、残りの引数を受け取る関数を生成します。一般にカリー化と呼ばれるものです。iterは、オブジェクトからイテレーターを生成します。iterの第2引数(sentinel)は、戻り値が一致したときにStopIterationを発生させる(ループを終了させる)条件として使えます。後者の実装は、イテレーターを利用することで、ファイルの読み込みと終了条件をループ外へ切り出し、本来の目的であるblockの処理のみをループ内に記述できます。筆者はpartialやiterを使ったことがなく、こういう使い方があるんだと知り、感心しました。2.4で追加されました。その後、徐々に機能が追加されてきているようです。デック(deque)デック(deque)は2.4で追加されました。両端から要素を追加・取り出せるキューです。左端から取り出す1名前付きタプル(namedtuple)名前付きタプル(namedtuple)は2.5で追加されました。フィールド名でもアクセスできるタプルです。2)デフォルトディクショナリ(defaultdict)デフォルトディクショナリ(defaultdict)は2.6で追加されました。ファクトリ関数でデフォルト値を生成するディクショナリです。[1]})カウンタ(Counter)カウンタ(Counter)は2.7で追加されました。数の集計を意図したディクショナリです。1})順序付きディクショナリ(OrderedDict)順序付きディクショナリ(OrderedDict)は2.7で追加されました。要素を追加した順序を維持するディクショナリです。2)])手元のコードを見返してみると、こういったツールを使うとシンプルに実装できる処理が見つかるかもしれません。覚えておくと、役に立つときがくるでしょう。ハックある程度大きな既存のディクショナリのキーや値の探索を高速化するハックだそうです。d.update(dict(d))dict.updateメソッドは、内部的にもつ領域を疎にするから速くなるそうです。小さなディクショナリだと、キャッシュの働きにより効果がないそうです。recipe)に、そのオリジナルのレシピがあるので、その補足も参考にしてください。Subclassing」という講演は、Pythonにおけるクラスやオブジェクトの仕組みを考察するものでした。SUBCLASSINGから視聴できます。サブクラス化のパターンフレームワークで使われるパターンとしてSimpleHTTPServerとcmdモジュールを紹介されていました。SimpleHTTPServerは、do_GETやdo_HEADといったスタブメソッドをもつリクエストハンドラを提供します。親クラスのBaseHTTPServerやSocketServerからリクエストハンドラの他の機能を継承することで、ユーザがGETやHEADに対する処理をサブクラスで簡単に拡張できます。リクエストに対して、サブクラスで定義したスタブメソッドを動的にディスパッチする方法は、cmdモジュールの方が分かりやすいのでそちらで説明します。行指向のコマンドインタープリターを書くフレームワークを提供するCmdクラスのonecmdメソッドではgetattrを使って、self(インスタンス)から動的にメソッドを取得します。func(arg)次のように子クラスでdo_に続くメソッドを実装することで、getattrで任意のメソッドが取得できます。helloSimpleHTTPServerのリクエストハンドラによるdo_GETやdo_HEADの処理も本質的にはこれと同じです。クラス名を付けた名前にリネームされます。これをネームマングリングと言います。__updateではなくd._MyDict__updateとして作成されます。{}>ここで__updateを定義している意図は、サブクラスでupdateがオーバーライドされても、__init__の処理が変更されないようにするためです。principle)と呼びます。このテクニックはMyDictの内部的な呼び出しをサブクラスの拡張から保護するための方法で、collections.OrderedDictでも使われています。OCPの実装例の1つとして紹介されていました。Tip:Raymond氏は__(2重アンダースコア)を「あんだーあんだー」と読んでいました。カリフォルニアの人たちには「だんだー」と読む人もいるそうです。Pythonのsuper()ができることsuperは新スタイルクラス(Python3ではデフォルト)でのみ使える機能で、親または兄弟クラスのメソッドを呼び出すプロキシオブジェクトを返します。superを使って委譲することで、サブクラスにてデフォルトの移譲先を変更できます。1})このサンプルは、CounterとOrderedDictを継承してOrderedCounterを定義します。Counterはdictのサブクラスで、その__init__()とupdate()メソッドの中でsuperを使ってその処理を委譲します。CounterとOrderedDictの実装の一部が次になります。__init__新スタイルクラスのメソッド解決順序(MRO)は__mro__で調べられます。OrderedDictの順番であるのが分かります。実際の呼び出しは次のようになります。Counter.__init__内にsuperの呼び出しがあるのでOrdereedDict.__init__()が呼ばれます。その次にself.update()(Counter.update)メソッド内でもsuperの呼び出しがあるのでOrdereedDict.update()が呼ばれます。dictであることが分かります。'object'>)つまりCounterの__init__()メソッドとupdate()メソッド内のsuperの移譲先が、OrderedCounter(Counter、OrderedDict)を定義したことにより、dictからOrderedCounterに変更されたことが分かります。TipPython3ではsuperを引数なしで呼び出せます。その際は、第1引数に現在のクラスが、第2引数に現在のメソッドの第1引数が使われます。このCounterのサンプルの場合、super(Counter、self).__init__()とsuper().__init__()は同じです。versionを参照してください。PyPyについての講演、ハンズオン、スプリント」に続きます。Ltd.ページ内容の全部あるいは一部を無断で利用することを禁止します⁠。個別にライセンスが設定されている記事等はそのライセンスに従います。