そうだ! EuroPython 2011へ行こう

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

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

ディクショナリで考える

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の簡潔な仕組みを支える要素でもあります。

著者プロフィール

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

一介のプログラマ。

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

Twitter:@t2y

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