メタプログラミング
Rubyでプログラム内で関数を定義するには、Module#define_methodを使用する(Mudule#module_evalやModule#instance_evalを使用した方法もあるが省略)。
irb(main):001:0> class GeneratedFetcher irb(main):002:1> def fetch(how_many) irb(main):003:2> puts "Fetching #{how_many ? how_many : "all"}." irb(main):004:2> end irb(main):005:1> irb(main):006:1* [["one", 1], ["ten", 10], ["all", nil]].each do |name, number| irb(main):007:2* define_method("fetch_#{name}") do irb(main):008:3* fetch(number) irb(main):009:3> end irb(main):010:2> end irb(main):011:1> end => [["one", 1], ["ten", 10], ["all", nil]] irb(main):012:0> GeneratedFetcher.instance_methods - Object.instance_methods => ["fetch", "fetch_one", "fetch_ten", "fetch_all"] irb(main):013:0> GeneratedFetcher.new.fetch_one Fetching 1. => nil irb(main):014:0> GeneratedFetcher.new.fetch_all Fetching all. => nil irb(main):015:0>
Pythonでは、以下の方法で実現できる。
>>> def set_fetch_func(obj, name, n): ... def fetch_factory(n): ... def fetch_n(self=None): ... print 'Fetching %s.' % {None: 'all'}.get(n, str(n)) ... return fetch_n ... setattr(obj, 'fetch_%s' % name, fetch_factory(n)) # ここがポイント! ... >>> class Foo: pass ... >>> for name, n in [['one', 1], ['ten', 10], ['all', None]]: ... set_fetch_func(Foo, name, n) ... >>> dir(Foo) ['__doc__', '__module__', 'fetch_all', 'fetch_one', 'fetch_ten'] >>> Foo().fetch_one() Fetching 1. >>> Foo().fetch_all() Fetching all.
ポイントは、文字列からオブジェクト(変数、関数)の値を取得や設定ができれば良い訳で、Pythonでは、以下の方法で行う。
- 取得: getattr()、__dict__、locals()、globals()など。
- 設定: setattr()、__dict__、locals()、globals()など。
オブジェクトがユーザ定義の新スタイル型ならobject.__setattr__でも設定可能(本来の使用方法は、属性の値を設定したタイミングをフックするためのもの)。また、Pythonで関数定義を文字列のみで定義できるか分からないが、可読性の点で、関数全体ではなく一部のみを上記の取得・設定方法で実現した方が良いと思う。また、ポイントとしては、実装を変更したい部分のみを引数部分に追い出す(委譲する)ということを、どう実現するかが重要である。
Pythonでは、変数も関数もRubyみたいに区別がないし、基本的に関数定義の方法もdef文を使用するか、lambdaを使用するかくらいで、RubyのModule#define_methodみたいに特別な方法が用意されていないので単純だが少々可読性は低いし、短いコードで書けないと思う。特に、インスタンスメソッドは第一引数にselfを必要とするので、そこら辺が分かりづらい。文法や仕組みがシンプルであることを十分生かして書けないとPythonの単純さや分かりやすさというメリットが生かされないので注意したい。