そうだ! EuroPython 2011へ行こう

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

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

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がデコレートされる関数の属性に追加されます。

著者プロフィール

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

一介のプログラマ。

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

Twitter:@t2y

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