Posted at

Pythonで動的に関数(メソッド)を定義する

More than 3 years have passed since last update.

ハマってしまったので、どなたかの参考になれば幸いです。

やろうとしていること: 動的に3つのことなるpathを表示する関数を定義する

def funcの中が実際に関数が呼ばれたときに評価されるため、tの値がforを処理した最後の値になってしまっている。

>>> path_freq = [{"path": "/pickup/6162449", "freq": 1, "name": "a"}, {"path": "/pickup/6162466", "freq": 1, "name": "b"}, {"path": "/pickup/6162461", "freq":1, "name": "c"}]

>>> task_list = {}
>>> for t in path_freq:
... def func():
... print(t)
... task_list.update({func: t['path']})
...
>>> task_list
{<function func at 0x101184848>: '/pickup/6162461', <function func at 0x101184938>: '/pickup/6162466', <function func at 0x1011848c0>: '/pickup/6162449'}
>>> for k,v in task_list.items():
... k()
...
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}

Rubyで似たようなことを試す

[2] pry(main)> path_freq = [{"path" => "/pickup/6162449", "freq" => 1, "name" => "a"}, {"path" => "/pickup/6162466", "freq" => 1, "name" => "b"}, {"path" => "/pickup/6162461", "freq" => 1, "name" => "c"}]

=> [{"path"=>"/pickup/6162449", "freq"=>1, "name"=>"a"},
{"path"=>"/pickup/6162466", "freq"=>1, "name"=>"b"},
{"path"=>"/pickup/6162461", "freq"=>1, "name"=>"c"}]
[3] pry(main)> path_freq.each do |t|
[3] pry(main)* define_method :hoge do
[3] pry(main)* puts t
[3] pry(main)* end
[3] pry(main)* task_list.merge!({hoge => t})
[3] pry(main)* end
{"path"=>"/pickup/6162449", "freq"=>1, "name"=>"a"}
{"path"=>"/pickup/6162466", "freq"=>1, "name"=>"b"}
{"path"=>"/pickup/6162461", "freq"=>1, "name"=>"c"}
=> [{"path"=>"/pickup/6162449", "freq"=>1, "name"=>"a"},
{"path"=>"/pickup/6162466", "freq"=>1, "name"=>"b"},
{"path"=>"/pickup/6162461", "freq"=>1, "name"=>"c"}]
[4] pry(main)> task_list
=> {nil=>{"path"=>"/pickup/6162461", "freq"=>1, "name"=>"c"}}
[5] pry(main)> hoge
{"path"=>"/pickup/6162461", "freq"=>1, "name"=>"c"}
=> nil

PythonとRubyは言語仕様が似てると思ってたけど、一概にそうとも言えないとこの辺りで思いしる。

PythonではメソッドはObjectとして扱われるけど、Rubyでは違う。Rubyの場合は、define_methodでhogeメソッドを定義した段階で、この例ではmain Objectのメソッドとして定義されてしまう。hogeメソッドをObjectとして配列に入れたりできない。ちなみに、task_listのkeyにnilが入っているのは、Rubyではputsの戻り値がnilだから。

ならば、別の方法でメソッドを定義してみる。

Pythonでsetaddrでclassにメソッドを追加してみるも、同じ挙動。

>>> class Test(): 

... pass
>>> path_freq = [{"path": "/pickup/6162449", "freq": 1, "name": "a"}, {"path": "/pickup/6162466", "freq": 1, "name": "b"}, {"path": "/pickup/6162461", "freq":
1, "name": "c"}]
>>> task_list = []
>>> for t in path_freq:
... def func():
... print(t)
... setattr(Test, t['name'], func)
... task_list.append(func)
...
>>> for f in task_list:
... f()
...
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
>>> for t in path_freq:
... def func(self):
... print(t)
... setattr(Test, t['name'], func)
...
>>> Test().a()
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
>>> Test().b()
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}
>>> Test().c()
{'path': '/pickup/6162461', 'freq': 1, 'name': 'c'}

関数のSignatureは遅延評価されないのではと、引数のデフォルト値でListの値を渡すようにしてみた。

>>> path_freq = [{"path": "/pickup/6162449", "freq": 1, "name": "a"}, {"path": "/pickup/6162466", "freq": 1, "name": "b"}, {"path": "/pickup/6162461", "freq":1, "name": "c"}]

>>> task_list = {}
>>> for t in path_freq:
... def func(path=t['path']):
... print(path)
... task_list.update({func: t['path']})
...
>>> for k,v in task_list.items():
... k()
...
/pickup/6162461
/pickup/6162466
/pickup/6162449

:thumbsup: