PythonでRubyのStruct風の構造体を実現する

Rubyでは,Structクラスを用いることによって,特定のフィールドを持つクラスを簡単に作成することができます。

Foo = Struct.new("Foo", "foo", "bar")
f = Foo.new(1, 2)
p f.foo # 1
p f.bar # 2

一方、Pythonの場合、namedtupleというライブラリで同様の機能が提供されています。
namedtupleはPython2.6以降で利用可能です。

from collections import namedtuple
Foo = namedtuple("Foo", "foo bar")
f = Foo(1, 2)
print f.foo # 1
print f.bar # 2

ただし、Pythonではtupleがイミュータブル(書き換え不可能)なオブジェクトであるのと同様に、このnamedtupleも属性の値を変更することができません。

f.foo = 0 # AttributeError: can't set attribute

Pythonではクラス定義の中で__slots__という名前の変数に値を入れることによって、特定の名前の属性のみを持つクラスを作成することができます。

class Foo(object):
  __slots__ = ("foo", "bar")

f = Foo()
try:
  f.foo  = 1
  f.bar  = 2
  f.hoge = 3
except AttributeError, e:
  print e # 'Foo' object has no attribute 'hoge'

これを利用することで、RubyのStructのように特定の名前の属性のみを持ち、書き換え可能なオブジェクトのクラスを実現することができます。

from itertools import chain, repeat, izip

def Struct(name, *fields):
  def __init__(self, *args):
    if len(args) > len(fields):
      raise TypeError("__init__() takes at most %d arguments (%d given)" % (len(fields)+1, len(args)+1))
    args = chain(args, repeat(None))
    for field, value in izip(fields, args):
      setattr(self, field, value)
    
  attrs = dict()
  attrs["__slots__"] = fields
  attrs["__init__"] = __init__
  return type(name, (object,), attrs)

if __name__ == "__main__":
  Foo = Struct("Foo", "foo", "bar")
  f = Foo(1, 2)
  print "f.foo =", f.foo
  print "f.bar =", f.bar
  f.foo = 0                     # OK
  try:
    f.baz = 0                   # NG
  except AttributeError, e:
    print e

実行結果は以下のようになります。

f.foo = 1
f.bar = 2
'Foo' object has no attribute 'baz'