そうだ! EuroPython 2011へ行こう

#2CPythonについてのハンズオン、講演

前回は基調講演とカンファレンスの全体像について紹介しました。今回と次回ではPythonそのものの講演について紹介したいと思います。

今回はCPythonというC言語で実装されたPythonの講演(一般的にPythonというときはCPythonを指します)について紹介します。

CPython

CPythonの講演で紹介するのは、すべてRaymond Hettinger氏が発表されたことについてです。Raymond氏の講演は、Pythonでコーディングする上でのテクニックや考え方など、どれも示唆に富む内容で筆者にとっては興味深かった内容でした。

Raymond Hettinger氏
Raymond Hettinger氏
特に注記がない場合、サンプルコードはPython 2.7.2で実行しています。

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_GLOBALLOAD_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() の方が速い?
>>> 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つの練習問題が与えられました。もっと適切な実装があるかもしれませんが、筆者も挑戦してみました。最初の練習問題は、属性を参照する関数の実装です。

# Exercise 1: implement get_attribute function
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()を定義することです。

# Exercise 2: implement super function
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ディクショナリのオブジェクト(インスタンス)walkoverride_walk_function()としてオーバーライドして、親のメソッドを呼び出すようにしました。

Shiro is Walking
overridden!

最後の練習問題は、シンプルなhelp()関数を提供します。

# Exercise 3: implement simple 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) # same as help_function(Dog)

表示の整形を行っていませんが、クラス毎に属性とメソッドを別々に取り出しました。

{'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”] += 1KeyErrorが発生しないことに気付いた方もいると思います。__missing__という特殊メソッドを定義することで、ディクショナリのキーが存在しないときの処理をフックできます。Counterでは、次のようにゼロを返す実装になっています。

def __missing__(self, key):
  'The count of elements not in the Counter is zero.'
  # Needed so that self[missing_item] does not raise KeyError
  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_infocache_clearがデコレートされる関数の属性に追加されます。

Python Tips、Tricks、And Idioms:Pythonのコツ、トリック、イディオム

「Python Tips、Tricks、And Idioms」という講演では、次の項目について標準ドキュメントから要点を抜き出したPythonのチートシートを紹介するような内容でした。いくつか抜き出して紹介します。

  • Python固有の機能とイディオム
  • 比較とソート
  • イテレーターを使ったプログラミング
  • 動的なプログラミング
  • コレクション型
  • superができること
  • 最適化とハック
  • デバッガ
  • 開発ツールとその環境

本講演はPYTHON TIPS、TRICKS、AND IDIOMSから視聴できます。

key functioon
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の処理のみをループ内に記述できます。筆者はpartialiterを使ったことがなく、こういう使い方があるんだと知り、感心しました。

コレクション型

コレクション型を扱う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から視聴できます。

サブクラス化のパターン

フレームワークで使われるパターンとしてSimpleHTTPServercmdモジュールを紹介されていました。

SimpleHTTPServerは、do_GETdo_HEADといったスタブメソッドをもつリクエストハンドラを提供します。親クラスのBaseHTTPServerSocketServerからリクエストハンドラの他の機能を継承することで、ユーザがGETHEADに対する処理をサブクラスで簡単に拡張できます。

リクエストに対して、サブクラスで定義したスタブメソッドを動的にディスパッチする方法は、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_GETdo_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つとして紹介されていました。

Tip:
Raymond氏は__(2重アンダースコア)「あんだーあんだー」と読んでいました。また、カリフォルニアの人たちには「だんだー」と読む人もいるそうです。

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})
画像

このサンプルは、CounterOrderedDictを継承してOrderedCounterを定義します。Counterdictのサブクラスで、その__init__()update()メソッドの中でsuperを使ってその処理を委譲します。CounterOrderedDictの実装の一部が次になります。

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 # let subclasses override update without breaking __init__

新スタイルクラスのメソッド解決順序(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に変更されたことが分かります。

Tip
Python3ではsuperを引数なしで呼び出せます。その際は、第1引数に現在のクラスが、第2引数に現在のメソッドの第1引数が使われます。例えば、このCounterのサンプルの場合、super(Counter、self).__init__()super().__init__()は同じです。

詳細に興味がある方はPython’s super() considered super!とそこで紹介されているサンプルコードHow to use super() effectively – Python 2.7 versionを参照してください。

次回#3 PyPyについての講演、ハンズオン、スプリントに続きます。

おすすめ記事

記事・ニュース一覧