Python:アクセサの生成

Pythonの練習がてら、アクセサの生成をやってみる。どうせ探したらいっぱいコードが転がってるだろうし、練習にはうってつけかな、と。

まず、ダメそうだけど、Rubyをやってる人からするとこうかきたい、というコード。

1class Test(Accessor):
2  attr_accessor("__test", "__test2", "test3", "_test4")
3
4  def __init__(self):
5    self.__test = "test_value"
6    self.__test2 = "test2_value"
7    self.test3 = "test3_value"
8    self._test4 = "test4_value"

こんな感じ。まぁ、絶対にダメそうだ(笑 でも組み込みとはいえ、classmethodやstaticmethodみたいなのもあるから無理やりにならできるのかもしれない。

1def test():
2  print sys._getframe(1).f_code.co_name
3
4class Test:
5  test()
6
7#=> 文字列"Test"を出力

こんな感じで呼び出しもとのクラス名は取得できる。ならevalすれば、というところなのだが予想通り

1def test():
2  exec "cls = %s" % sys._getframe(1).f_code.co_name
3
4class Test:
5  test()
6
7#=> NameError: name 'Test' is not defined

この時点ではクラスオブジェクトの生成が完了していないので無理なようだ。Rubyのようにはいかない。

結局のところ__metaclass__を使うことになる。 マニュアルにもズバリ > メタクラスは限りない潜在的利用価値を持っています。これまで試されてきたアイデアには、ログ記録、インタフェースのチェック、自動デリゲーション、 自動プロパティ生成 、プロキシ、フレームワーク、そして自動リソースロック/同期といったものがあります。

と書いてあるのだ。

  • 後からもアクセサを追加したい(メソッドとしても独立させたい)
  • 当然、読み出しと書き込み、そして両方を行えるインターフェイス
  • オーバーライドはできないとまずい

というアクセサ生成としては至極当たり前なことを考えつつ、コードを書いてみる。

 1# vim: fileencoding=utf-8
 2from itertools import * 
 3
 4def property_accessor(cls, *names): 
 5  map(lambda n : _add_setter(cls, n[0], n[1]) or 
 6                 _add_getter(cls, n[0], n[1]),izip(names, _real_names(cls, names)))
 7
 8def property_reader(cls, *names):
 9  map(lambda n : _add_getter(cls, n[0], n[1]),izip(names, _real_names(cls, names)))
10
11def property_writer(cls, *names):
12  map(lambda n : _add_setter(cls, n[0], n[1]),izip(names, _real_names(cls, names)))
13
14def _real_names(cls, names) :
15  cls_name = cls.__name__
16  return imap(lambda n : n.startswith("__") and "_%s%s"%(cls_name, n) or n, names)
17
18def _add_setter(cls, name, real_name) :
19  setter_name = "set_%s" % name.lstrip("_")
20  if cls.__dict__.has_key(setter_name): return 
21  setattr(cls, setter_name, lambda self, v: setattr(self, real_name, v))
22
23def _add_getter(cls, name, real_name) :
24  getter_name = "get_%s" % name.lstrip("_")
25  if cls.__dict__.has_key(getter_name): return 
26  setattr(cls, getter_name, lambda self: getattr(self, real_name))
27
28class AccessorType(type):
29  def __new__(cls, class_name, class_bases, classdict):
30    cls = type.__new__(cls, class_name, class_bases, classdict)
31    list = ["__accessor__", "__reader__", "__writer__"]
32    methods = imap(lambda n: eval("property_%s"%n.strip("_")), list)
33    map(lambda n: 
34          classdict.has_key(n[0]) and
35            n[1](cls, *classdict[n[0]]), izip(list, methods))
36    return cls
37
38class Accessor:
39  __metaclass__ = AccessorType
40
41class Test(Accessor):
42  __accessor__ = ["__test2", "test3", "_test4"]
43  __reader__   = ["__test"]
44
45  def __init__(self):
46    self.__test = "test_value"
47    self.__test2 = "test2_value"
48    self.test3 = "test3_value"
49    self._test4 = "test4_value"
50
51  def get_test3(self) :
52    return "changed_test3_value"
53
54obj = Test()
55
56print obj.get_test2()
57# => "test2_value"
58print obj.get_test3()
59# => "changed_test3_value"
60
61property_writer(Test, "__test")
62obj.set_test("new_test_value")
63print obj.get_test()
64# => "new_test_value"

テストもろくにしてないし、汚いコードだけどなんとなくそれっぽい動き。だいたい40行くらいで実装できる。パフォーマンスを考えなければitertoolsがなくても大丈夫。

そして、回答をさがしてみる。とりあえず「python accessor __metaclass__」あたりでググるとすぐにコードが見つかる。

見事なくらい同じようなコード。ただ、自分で書いたやつのほうが、オーバーライドできる、あとからもアクセサが生成できる、という点ではいい感じ。

感想

ここらへんのメタプログラミングはやっぱりRubyのほうが柔軟性があってかつ、一貫しているので楽かな。 Pythonはやっぱり関数をオブジェクトとして扱いやすい、というのが楽。Rubyのobj.some_methodでメソッドが呼び出せるのは便利だが、その点ではやっぱキツい。ループもPythonほうが好きだな。

ま、やっぱり個人の好み、としかいいようがないなあ。最近ではWindowsの小物はほとんどpythonで書いてるけど(インストーラーがあって関連付けまでしてくれて、楽)、Linuxのほうはそうでもないし。

comments powered by Disqus