LoginSignup
5
2

More than 5 years have passed since last update.

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

Posted at

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

やろうとしていること: 動的に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:

5
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2