そうだ! EuroPython 2011へ行こう

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

この記事を読むのに必要な時間:およそ 12 分

コレクション型

コレクション型を扱う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重アンダースコア)「あんだーあんだー」と読んでいました。また,カリフォルニアの人たちには「だんだー」と読む人もいるそうです。

著者プロフィール

森本哲也(もりもとてつや)

一介のプログラマ。

自分で設計して,自分で開発して,自分で直せるような独立したプログラマを目指している。OSSコミュニティのゆるい人のつながりが性にあっていてPythonプログラミングが好き。共訳書に『エキスパート Pythonプログラミング』(アスキーメディアワークス)がある。

Twitter:@t2y

ブログ:http://d.hatena.ne.jp/t2y-1979/