前回 は基調講演とカンファレンスの全体像について紹介しました。今回と次回ではPythonそのものの講演について紹介したいと思います。
今回はCPythonというC言語で実装されたPythonの講演(一般的にPythonというときはCPythonを指します)について紹介します。
CPython
CPythonの講演で紹介するのは、すべてRaymond Hettinger 氏が発表されたことについてです。Raymond氏の講演は、Pythonでコーディングする上でのテクニックや考え方など、どれも示唆に富む内容で筆者にとっては興味深かった内容でした。
Raymond Hettinger氏
Advanced Python: 高度なPythonテクニック
「Advanced Python」というハンズオントレーニングでは、サンプルコードを見ながら、自分でコーディングしてみるといった、実際に手を動かすプログラムがありました。このハンズオンから、いくつかTIPSを紹介します。
なお、本講演のAdvanced Python には、事前説明のビデオはありますが、実際のハンズオンのビデオがまだアップロードされていないようです。
ハンズオン
最適化
3つの最適化のテクニックについて紹介されていました。
グローバルな参照(組み込み関数、モジュール、グローバル変数)をローカルの参照に置き換える
束縛メソッドを使う
内部ループにおけるピュアPythonの関数呼び出しを最小化する
まずは、グローバル参照と束縛メソッドの2つをdis モジュールを使って、Pythonのバイトコードにディスアセンブルして検証してみます。
>>> from dis import dis
>>> class Foo ( object ):
... def bar ( self ):
... pass
>>> f = Foo ()
>>> dis ( lambda : f . bar ())
1 0 LOAD_GLOBAL 0 ( f )
3 LOAD_ATTR 1 ( bar )
6 CALL_FUNCTION 0
9 RETURN_VALUE
>>> dis ( lambda given_f_bar : given_f_bar ())
1 0 LOAD_FAST 0 ( given_f_bar )
3 CALL_FUNCTION 0
6 RETURN_VALUE
無名関数でf.bar を直接参照しようとした場合と、引数として渡すと仮定したときのバイトコード命令の比較です。後者の引数として渡すと仮定するとLOAD_GLOBAL
とLOAD_ATTR
の命令がなくなります。
Pythonは関数(メソッド)がファーストクラスオブジェクトなので、ローカル変数に割り当てることができます。この特性を利用して、例えば、ループ内でのグローバルな参照や属性参照をループの外へ切り出すことで高速化を図ることができます。「 Pythonの内包表記はなぜ速い? 」の記事がとても分かりやすいです。
3つ目の、内部ループにおけるピュアPythonの関数呼び出しを最小化するというのは、なるべく組み込み関数やC言語で実装された関数を使うということです。
例えば、2つの引数を受け取って乗倍して返す組み込み関数pow()
と、無名関数で実装した場合を比較すると違いが明らかです。
$ python - m timeit "map(lambda x, y: x ** y, [1,2,3], [1,2,3])"
1000000 loops , best of 3 : 1.85 usec per loop
$ python - m timeit "map(pow, [1,2,3], [1,2,3])"
1000000 loops , best of 3 : 1.38 usec per loop
さらにベクトル化 により、リスト内包表記よりもmap() の方が速いようなことも言及されていました。例えば、次のようなコードです。
>>> [ ord ( i ) for i in string . letters ]
>>>
>>> map ( ord , string . letters )
筆者の環境(Mac OS X 10.6.7)で検証してみたところ、組み込み関数を使う場合はmap() の方が速いのですが、ピュアPythonの関数だと、リスト内包表記の方が速かったりします。Python List Comprehension Vs. Map でも議論されているので、そちらも参考にしてください。
時間計測
timeit モジュールを利用するサンプルの1つとして、任意の関数の実行時間を計測するサンプルプログラムです。
def use_listcomp ( data ):
return [ i ** 2 for i in data ]
def use_itertools ( data ):
from itertools import imap , repeat
return list ( imap ( pow , data , repeat ( 2 )))
if __name__ == '__main__' :
from timeit import Timer
from random import random
n = 10000
data = [ random () for i in range ( n )]
setup = "from __main__ import use_listcomp, use_itertools, data"
for func in use_listcomp , use_itertools :
stmt = '{0.__name__}(data)' . format ( func )
print func . __name__ , min ( Timer ( stmt , setup ). repeat ( 7 , 20 ))
このプログラムを実行すると、リスト内包表記を使った処理とitertools
モジュールのツールを使った処理の時間を計測して、実行時間が最も小さかったものを表示します。
$ python get_timing . py
( 'use_listcomp' , 0.022819042205810547 )
( 'use_itertools' , 0.028668880462646484 )
筆者は、普段IPythonのtimeit の仕組みで実行時間を計測していますが、こういうやり方もあるんだなと参考になりました。
ディクショナリで考える
Pythonは、すべてディクショナリで考えられるからやってみようといった話も紹介されました。例えば、xという変数はglobals() が返すディクショナリでアクセスできます。
>>> x = 1
>>> x == globals ()[ 'x' ]
True
>>> globals ()[ 'x' ] = 2
>>> x
2
ハンズオンでは、次のクラスをディクショナリで実装してみるのに挑戦しました。通常のクラスで実装したサンプルコードです。
class Animal ( object ):
def __init__ ( self , name ):
self . name = name
def walk ( self ):
print '{} is Walking' . format ( self . name )
class Dog ( Animal ):
def bark ( self ):
print 'Woof'
if __name__ == '__main__' :
d = Dog ( 'Shiro' )
print d . name
d . bark ()
d . walk ()
実際、オブジェクトの属性は__dict__ という特殊メソッドでアクセスできるディクショナリに格納されています。
>>> Animal . __dict__ . keys ()
>>> [ '__module__' , 'walk' , '__dict__' , '__weakref__' , '__doc__' , '__init__' ]
>>> d . __class__ . __dict__ . keys ()
>>> [ '__module__' , 'bark' , '__doc__' ]
>>> d . __dict__
>>> { 'name' : 'shiro' }
Pythonにおけるクラスの仕組みを理解するために、クラスをディクショナリで作るのに取り組んでみましょう。次のサンプルコードが与えられました。特殊メソッドの__ (2重アンダースコア)を奇妙に思う方もいるかもしれませんが、実装そのものは違和感なく自然に書けてしまうところが、Pythonのクラスとディクショナリの密接な関係を感じさせます。
from pprint import pprint
def init_function ( self , name ):
self [ 'name' ] = name
def walk_function ( self ):
name = get_attribute ( self , 'name' )
print '{} is Walking' . format ( name )
def bark_function ( self ):
print 'Woof'
Animal = dict (
__name__ = 'Animal' ,
__bases__ = (),
__doc__ = 'Generic animal class' ,
__init__ = init_function ,
walk = walk_function ,
)
Dog = dict (
__name__ = 'Dog' ,
__bases__ = ( Animal ,),
__doc__ = "Man's best frient" ,
bark = bark_function ,
)
def class_lookup ( klass , key ):
if key in klass :
return klass [ key ]
for klass in klass [ '__bases__' ]:
result = class_lookup ( klass , key )
if result is not None :
return result
return None
def make_instance ( klass , * args ):
instance = dict ( __class__ = klass )
init = class_lookup ( klass , '__init__' )
if init is not None :
init ( instance , * args )
return instance
if __name__ == '__main__' :
d = make_instance ( Dog , 'Shiro' )
pprint ( d )
このプログラムの実行結果です。Dogディクショナリが次のように表現されます。__class__
にDogディクショナリ、__bases__ にAnimalディクショナリをセットすることで継承階層を実現しています。
$ python think_dict . py
{ '__class__' : { '__bases__' : ({ '__bases__' : (),
'__doc__' : 'Generic animal class' ,
'__init__' : < function init_function at 0x1004979b0 >,
'__name__' : 'Animal' ,
'walk' : < function walk_function at 0x1004977d0 >},),
'__doc__' : "Man's best frient" ,
'__name__' : 'Dog' ,
'bark' : < function bark_function at 0x1004a12a8 >},
'name' : 'Shiro' }
ここからは自分で挑戦する3つの練習問題が与えられました。もっと適切な実装があるかもしれませんが、筆者も挑戦してみました。最初の練習問題は、属性を参照する関数の実装です。
def get_attribute ( instance , attrname ):
if attrname in instance :
return instance [ attrname ]
return class_lookup ( instance [ '__class__' ], attrname )
get_attribute ( d , 'bark' )( d )
get_attribute ( d , 'walk' )( d )
メソッドを取得して呼び出した実行結果です。
Woof
Shiro is Walking
次の練習問題は、super()を定義することです。
def super_function ( instance , attrname ):
for klass in instance [ '__class__' ][ '__bases__' ]:
_super = class_lookup ( klass , attrname )
if _super is not None :
return _super
raise AttributeError
def override_walk_function ( self ):
super_function ( self , 'walk' )( self )
print 'overridden!'
d [ 'walk' ] = override_walk_function
get_attribute ( d , 'walk' )( d )
Dogディクショナリのオブジェクト(インスタンス)でwalk
をoverride_walk_function()
としてオーバーライドして、親のメソッドを呼び出すようにしました。
Shiro is Walking
overridden !
最後の練習問題は、シンプルなhelp() 関数を提供します。
def help_function ( obj ):
_obj = obj [ '__class__' ] if obj . get ( '__class__' ) else obj
klasses = ( _obj ,) + _obj [ '__bases__' ]
help_info = {}
for klass in klasses :
klass_name = klass [ '__name__' ]
help_info [ klass_name ] = dict ( method ={}, attribute ={})
for key , value in klass . iteritems ():
if key != '__bases__' :
_attr = 'method' if callable ( value ) else 'attribute'
help_info [ klass_name ][ _attr ][ key ] = value
pprint ( help_info )
help_function ( d )
表示の整形を行っていませんが、クラス毎に属性とメソッドを別々に取り出しました。
{ 'Animal' : { 'attribute' : { '__doc__' : 'Generic animal class' ,
'__name__' : 'Animal' },
'method' : { '__init__' : < function init_function at 0x1004979b0 >,
'walk' : < function walk_function at 0x1004977d0 >}},
'Dog' : { 'attribute' : { '__doc__' : "Man's best frient" , '__name__' : 'Dog' },
'method' : { 'bark' : < function bark_function at 0x1004a12a8 >}}}
Pythonの名前空間はディクショナリで実現されていて、モジュールもクラスもインスタンスも一貫して扱えるところがPythonの簡潔な仕組みを支える要素でもあります。
Fun With Python’s Newer Tools: Pythonの新しいツールで遊ぼう
「Fun With Python’s Newer Tools」という講演では、( 比較的)最近に追加された言語の仕組みや標準ライブラリのツールについて発表されました。この講演の内容を紹介します。
なお、本講演はFUN WITH PYTHON’S NEWER TOOLS から視聴できます。
collections.Counter
collections.Counter は、投票のような集計や、その集計結果の加工、演算に使われます。C++言語のmultisetとSmalltalk/Objective Cのbagクラスを参考にしているそうです。
>>> from collections import Counter
>>> c = Counter ()
>>> c [ "a" ] += 1
>>> c [ "b" ] += 1
>>> c [ "a" ] += 1
>>> c
Counter ({ 'a' : 2 , 'b' : 1 })
collections.Counterの実装
Counter
は、ディクショナリのサブクラスで実装されています。とてもシンプルな実装なのでCounter
のソースを読んでみると、API開発のお手本にも良さそうです。次の2つの特殊メソッドについて言及されていました。
__missing__
先に紹介したCounterのサンプルコードでc[“a”] += 1
でKeyError
が発生しないことに気付いた方もいると思います。__missing__ という特殊メソッドを定義することで、ディクショナリのキーが存在しないときの処理をフックできます。Counter
では、次のようにゼロを返す実装になっています。
def __missing__ ( self , key ):
'The count of elements not in the Counter is zero.'
return 0
__delitem__
Counter
に存在しないキーを削除しようとしたときは、KeyError
を発生させないようになっています。キーが存在するときはdict
にその処理を委譲しています。
def __delitem__ ( self , elem ):
'Like dict.__delitem__() but does not raise KeyError for missing values.'
if elem in self :
dict . __delitem__ ( self , elem )
collections.namedtuple
collections.namedtuple は、普通のタプルのように使えて、フィールド名でも要素にアクセスできます。既存のコードをクライアント側に影響を与えずに改善するツールとして便利だと紹介されていました。
namedtupleのシンプルな実装例は次になります。__slots__ に空のタプルをセットすることで、インスタンス化するときに属性情報をもった__dict__
を作成せず、メモリを節約する狙いがあります。これは最適化のテクニックの1つです。
from operator import itemgetter
class TestResults ( tuple ):
__slots__ = ()
_field = ( 'failed' , 'attempted' )
def __new__ ( _cls , failed , attempted ):
'Create new instance fo TestResults(failed, attempted)'
return tuple . __new__ ( _cls , ( failed , attempted ))
def __repr__ ( self ):
'Return nicely formatted representation string'
return self . __class__ . __name__ + '(failed=%r, attempted=%r)' % self
failed = property ( itemgetter ( 0 ), doc = 'Alias for field number 0' )
attempted = property ( itemgetter ( 1 ), doc = 'Alias for field number 1' )
collections.namedtupleの応用例
フィールド構造を拡張する
先ほど紹介したTestResults を_fields を使って拡張します。
>>> LabeledResults = namedtuple ( 'LabeledResutls' ,
TestResults . _fields + ( 'test_name' ,) )
インスタンスのプロトタイプとして使う
プロトタイプとして定義し_replace() メソッドで要素を変更できます。
>>> cell = namedtuple ( 'Cell' , 'color size border' )
>>> prototype = cell ( color = 'red' , size = 10 , border = False )
>>> prototype
Cell ( color = 'red' , size = 10 , border = False )
>>> intro = prototype . _replace ( size = 20 )
>>> intro
Cell ( color = 'red' , size = 20 , border = False )
サブクラス化する
>>> class Point ( namedtuple ( 'Point' , 'x y' )):
... __slots__ = ()
... @property
... def hypot ( self ):
... return ( self . x ** 2 + self . y ** 2 ) ** 0.5
... def __str__ ( self ):
... return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (
... self . x , self . y , self . hypot )
列挙型(enum)を作る
>>> color = namedtuple ( 'Color' ,
... 'red orange yellow' ). _make ( range ( 3 ))
>>> color . red
0
>>> color . yellow
2
これらのサンプルは標準ライブラリドキュメントでも紹介されています。
LRUキャッシュ
functools.lru_cache がPython 3.2から追加されています。デコレーターの応用例にMemoizeデコレーター (メモ化 )がありますが、それをもう少し便利にするツールです。lru_cacheは、途中の計算結果を保持するキャッシュのサイズを設けて、最後に使われてから最も時間が経過したエントリを削除します。
>>> from functools import lru_cache
>>> @lru_cache ( maxsize = None )
... def fib ( n ):
... if n < 2 :
... return n
... return fib ( n - 1 ) + fib ( n - 2 )
...
>>> print ([ fib ( n ) for n in range ( 16 )])
[ 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 , 89 , 144 , 233 , 377 , 610 ]
>>> print ( fib . cache_info ())
CacheInfo ( hits = 28 , misses = 16 , maxsize = None , currsize = 16 )
>>> fib . cache_clear ()
>>> fib . cache_info ()
CacheInfo ( hits = 0 , misses = 0 , maxsize = None , currsize = 0 )
※ このサンプルコードはPython 3.2でのみ実行できます。
このサンプルも標準ライブラリドキュメントから引用しています。cache_info
やcache_clear
がデコレートされる関数の属性に追加されます。
Python Tips、Tricks、And Idioms:Pythonのコツ、トリック、イディオム
「Python Tips、Tricks、And Idioms」という講演では、次の項目について標準ドキュメントから要点を抜き出したPythonのチートシートを紹介するような内容でした。いくつか抜き出して紹介します。
Python固有の機能とイディオム
比較とソート
イテレーターを使ったプログラミング
動的なプログラミング
コレクション型
superができること
最適化とハック
デバッガ
開発ツールとその環境
本講演はPYTHON TIPS、TRICKS、AND IDIOMS から視聴できます。
key functioon
比較とソート
次のようなソート関数(メソッド)があります。
sorted()
list.sort()
min()
max()
heapq.nsmallest()
heapq.nlargest()
bisect.bisect()
bisect.insrot()
これらはx < y
の比較をするときに__lt__ という特殊メソッドを呼び出します。例えば、ソートされる順序を降順にしたい場合、次のように__lt__ の返り値を反転させることで実現できます。
>>> from operator import lt
>>> class MyInt ( int ):
... def __init__ ( self , num ):
... self . num = num
... def __lt__ ( self , x ):
... return not lt ( self . num , x )
...
>>> sorted ( map ( MyInt , [ 1 , 5 , 3 , 2 , 4 ]))
[ 5 , 4 , 3 , 2 , 1 ]
またbisect モジュール以外のソート関数(メソッド)は、key
引数でソートに使う値を指定することで、ソート順序をカスタマイズすることもできます。
>>> sorted ( "cbAdaB" )
[ 'A' , 'B' , 'a' , 'b' , 'c' , 'd' ]
>>>
>>> sorted ( "cbAdaB" , key = str . lower )
[ 'A' , 'a' , 'b' , 'B' , 'c' , 'd' ]
operator モジュールと組み合わせても便利です。リスト内のタプルの2番目の要素でソートします。
>>> from operator import itemgetter
>>> sorted ([( 3 , 3 ), ( 1 , 2 ), ( 2 , 1 )], key = itemgetter ( 1 ))
[( 2 , 1 ), ( 1 , 2 ), ( 3 , 3 )]
オブジェクトの属性でソートします。
>>> from operator import attrgetter
>>> from collections import namedtuple
>>> user = namedtuple ( 'User' , 'name age' )
>>> sorted ([ user ( 't2y' , 32 ), user ( 'rokujyouhitoma' , 27 )],
... key = attrgetter ( 'age' ))
[ User ( name = 'rokujyouhitoma' , age = 27 ), User ( name = 't2y' , age = 32 )]
引数でソート順序を逆にできます。
>>> sorted ([ 1 , 2 , 3 ], reverse = True )
[ 3 , 2 , 1 ]
ソートのサンプルについてはSorting HOW TO も参考にしてください。
イテレーターを使ったプログラミング
Pythonという言語がシンプル且つ簡潔たらしめる重要な概念がイテレーター であると説明されていました。__getitem__
をサポートするシーケンスや__iter__
をサポートする任意のオブジェクトが、繰り返し可能なオブジェクト(Iterable)になります(参考:第1回レポートイテレータープロトコルとジェネレーター ) 。
ファイルを10バイトずつ読み込むサンプルコードが紹介されていました。普通に実装すると次のようになります。
>>> with open ( file_name ) as f :
... while True :
... block = f . read ( 10 )
... if block = '' :
... break
この処理をイテレーターを使って実装すると次のようになります。
>>> from functools import partial
>>> with open ( file_name ) as f :
... for block in iter ( partial ( f . read , 10 ), '' ):
... do_something
functools.partial は、一部の引数を渡して、関数の部分適用を行い、残りの引数を受け取る関数を生成します。一般にカリー化 と呼ばれるものです。iter は、オブジェクトからイテレーターを生成します。iter
の第2引数(sentinel)は、戻り値が一致したときにStopIteration を発生させる(ループを終了させる)条件として使えます。
後者の実装は、イテレーターを利用することで、ファイルの読み込みと終了条件をループ外へ切り出し、本来の目的であるblock
の処理のみをループ内に記述できます。筆者はpartial
やiter
を使ったことがなく、こういう使い方があるんだと知り、感心しました。
コレクション型
コレクション型を扱うcollections モジュールは、Python 2.4で追加されました。その後、徐々に機能が追加されてきているようです。
デック(deque)
デック(deque)は2.4で追加されました。両端から要素を追加・取り出せるキューです。
>>> from collections import deque
>>> d = deque ()
>>> d . append ( 1 )
>>> d . append ( 2 )
>>> d
deque ([ 1 , 2 ])
>>> d . popleft ()
1
名前付きタプル(namedtuple)
名前付きタプル(namedtuple)は2.5で追加されました。フィールド名でもアクセスできるタプルです。
>>> from collections import namedtuple
>>> Point = namedtuple ( 'Point' , 'x y' )
>>> p = Point ( 1 , 2 )
>>> p . x , p . y
( 1 , 2 )
デフォルトディクショナリ(defaultdict)
デフォルトディクショナリ(defaultdict)は2.6で追加されました。ファクトリ関数でデフォルト値を生成するディクショナリです。
>>> from collections import defaultdict
>>> d = defaultdict ( list )
>>> d [ 'a' ]. append ( 1 )
>>> d
defaultdict (< type 'list' >, { 'a' : [ 1 ]})
カウンタ(Counter)
カウンタ(Counter)は2.7で追加されました。数の集計を意図したディクショナリです。
>>> from collections import Counter
>>> c = Counter ()
>>> c [ 'a' ] += 1
>>> c
Counter ({ 'a' : 1 })
順序付きディクショナリ(OrderedDict)
順序付きディクショナリ(OrderedDict)は2.7で追加されました。要素を追加した順序を維持するディクショナリです。
>>> from collections import OrderedDict
>>> d = { 'apple' : 1 , 'macbook' : 2 , 'iphone' : 3 }
>>> d
{ 'iphone' : 3 , 'apple' : 1 , 'macbook' : 2 }
>>> OrderedDict ( sorted ( d . items (), key = lambda ( k , v ): k ))
OrderedDict ([( 'apple' , 1 ), ( 'iphone' , 3 ), ( 'macbook' , 2 )])
手元のコードを見返してみると、こういったツールを使うとシンプルに実装できる処理が見つかるかもしれません。覚えておくと、役に立つときがくるでしょう。
ハック
ある程度大きな既存のディクショナリのキーや値の探索を高速化するハックだそうです。
>>> d = { 'key' : '既存の大きなディクショナリ' , ...}
>>> d . update ( dict ( d ))
dict.update
メソッドは、内部的にもつ領域を疎にするから速くなるそうです。ただし、小さなディクショナリだと、キャッシュの働きにより効果がないそうです。興味がある方は、Improve dictionary lookup performance(Python recipe) に、そのオリジナルのレシピがあるので、その補足も参考にしてください。
The Art Of Subclassing:アート・オブ・サブクラス
「The Art Of Subclassing」という講演は、Pythonにおけるクラスやオブジェクトの仕組みを考察するものでした。
クラス、サブクラス、インスタンスの概念を再確認する
ユースケース、原則、デザインパターンを議論する
super()の動作とその使い方を確認する
Bloom Filterの事例研究
なお、本講演はTHE ART OF SUBCLASSING から視聴できます。
サブクラス化のパターン
フレームワークで使われるパターンとしてSimpleHTTPServer とcmd モジュールを紹介されていました。
SimpleHTTPServer は、do_GET
やdo_HEAD
といったスタブ メソッドをもつリクエストハンドラを提供します。親クラスのBaseHTTPServer
やSocketServer
からリクエストハンドラの他の機能を継承することで、ユーザがGET
やHEAD
に対する処理をサブクラスで簡単に拡張できます。
リクエストに対して、サブクラスで定義したスタブメソッドを動的にディスパッチする方法は、cmd
モジュールの方が分かりやすいのでそちらで説明します。
行指向のコマンドインタープリターを書くフレームワークを提供するCmd
クラスのonecmd メソッドではgetattr
を使って、self
(インスタンス)から動的にメソッドを取得します。
def onecmd ( self , line ):
...
try :
func = getattr ( self , 'do_' + cmd )
except AttributeError :
return self . default ( line )
return func ( arg )
次のように子クラスでdo_
に続くメソッドを実装することで、getattr
で任意のメソッドが取得できます。
>>> from cmd import Cmd
>>> class MyCmd ( Cmd ):
... def do_print ( self , args ):
... print "do_print" , args
>>>
>>> c = MyCmd ()
>>> c . onecmd ( "print hello" )
do_print hello
SimpleHTTPServer のリクエストハンドラによるdo_GET
やdo_HEAD
の処理も本質的にはこれと同じです。
OCP(open/closed principle)とネームマングリング
クラスのメソッド名の先頭に__ (2重アンダースコア)を付けると、
クラス名を付けた名前にリネームされます。これをネームマングリング と言います。
class MyDict ( dict ):
def __init__ ( self , iterable ):
self . items_list = []
self . __update ( iterable )
def update ( self , iterable ):
for item in iterable :
self . item_list . append ( item )
__update = update
このクラスをインスタンス化すると、update
メソッドのエイリアスが
__update
ではなくd._MyDict__update
として作成されます。
>>> d = MyDict ([])
>>> d . _MyDict__update
< bound method MyDict . update of {}>
ここで__update
を定義している意図は、サブクラスでupdate
がオーバーライドされても、__init__
の処理が変更されないようにするためです。
class MySubDict ( MyDict ):
def update ( self , iterable ):
pass
>>> d = MyDict ([ 1 , 2 , 3 ])
>>> d . items_list
[ 1 , 2 , 3 ]
>>> d2 = MySubDict ([ 1 , 2 , 3 ])
>>> d2 . items_list
[ 1 , 2 , 3 ]
オブジェクト指向プログラミングにおけるクラスの、
拡張に対して開いて(open)いなければならない
修正に対して閉じて(closed)いなければならない
という設計上の原則をOCP(open/closed principle) と呼びます。
このテクニックはMyDict
の内部的な呼び出しをサブクラスの拡張から保護するための方法で、collections.OrderedDict でも使われています。OCPの実装例の1つとして紹介されていました。
Pythonのsuper()ができること
super
は新スタイルクラス (Python3ではデフォルト)でのみ使える機能で、親または兄弟クラスのメソッドを呼び出すプロキシオブジェクトを返します。super
を使って委譲することで、サブクラスにてデフォルトの移譲先を変更できます。
>>> from collections import Counter , OrderedDict
>>> class OrderedCounter ( Counter , OrderedDict ):
... pass
>>> OrderedCounter ( 'abracadabra' )
OrderedCounter ({ 'a' : 5 , 'b' : 2 , 'r' : 2 , 'c' : 1 , 'd' : 1 })
このサンプルは、Counter
とOrderedDict
を継承してOrderedCounter
を定義します。Counter
はdict
のサブクラスで、その__init__()
とupdate()
メソッドの中でsuper
を使ってその処理を委譲します。Counter
とOrderedDict
の実装の一部が次になります。
class Counter ( dict ):
def __init__ ( self , iterable = None , ** kwds ):
super ( Counter , self ). __init__ ()
self . update ( iterable , ** kwds )
def update ( self , iterable = None , ** kwds ):
...
super ( Counter , self ). update ( iterable )
...
class OrderedDict ( dict ):
def __init__ ( self , * args , ** kwds ):
...
self . __update (* args , ** kwds )
update = MutableMapping . update
__update = update
新スタイルクラスのメソッド解決順序(MRO)は__mro__ で調べられます。
>>> OrderedCounter . __mro__
(< class '__main__.OrderedCounter' >,
< class 'collections.Counter' >,
< class 'collections.OrderedDict' >,
< type 'dict' >,
< type 'object' >)
メソッドを調べる順番がOrderedCounter
-> Counter
-> OrderedDict
の順番であるのが分かります。
実際の呼び出しは次のようになります。Counter.__init__
内にsuper
の呼び出しがあるのでOrdereedDict.__init__()
が呼ばれます。その次にself.update()
(Counter.update
)メソッド内でもsuper
の呼び出しがあるのでOrdereedDict.update()
が呼ばれます。
説明が前後してしまいますが、Counter
のメソッド解決順序(MRO)を調べると、Counter
-> dict
であることが分かります。
>>> Counter . __mro__
(< class 'collections.Counter' >,
< type 'dict' >,
< type 'object' >)
つまりCounter
の__init__()
メソッドとupdate()
メソッド内のsuper
の移譲先が、OrderedCounter(Counter、OrderedDict)
を定義したことにより、dict
からOrderedCounter
に変更されたことが分かります。
詳細に興味がある方はPython’s super() considered super! とそこで紹介されているサンプルコードHow to use super() effectively – Python 2.7 version を参照してください。
次回「#3 PyPyについての講演、ハンズオン、スプリント 」に続きます。