動的に生成という言い方が正しいかはわからないけど、実行中に関数名を動的に決定したい。いくつかやり方はあるけど、例えば exec を使用して、適当な dict に入れてやればよい。

def dynamic_generated_fn():
    created_fns = {}

    fn_code = """def {name}(x):
    return x + '_called_by_{name}'"""

    fn_name1 = 'foo'
    fn_name2 = 'bar'

    exec(fn_code.format(name=fn_name1), {}, created_fns)
    print(created_fns[fn_name1]('test'))

    exec(fn_code.format(name=fn_name2), {}, created_fns)
    print(created_fns[fn_name2]('test'))
$ python test.py
test_called_by_foo
test_called_by_bar

fn_name1 あるいは fn_name2 という変数を使って、関数を呼び出している。スタックトレースにも fn_name が出力される。

def dynamic_generated_fn_error():
    created_fns = {}

    fn_code = """def {name}(x):
    raise RuntimeError('{name}')"""

    fn_name = 'foo'

    exec(fn_code.format(name=fn_name), {}, created_fns)
    print(created_fns[fn_name]('test'))
$ python test.py
Traceback (most recent call last):
  File "test.py", line 31, in <module>
    dynamic_generated_fn_error()
  File "test.py", line 27, in dynamic_generated_fn_error
    print(created_fns[fn_name]('test'))
  File "<string>", line 2, in foo
RuntimeError: foo

ファイル名は "<string>" となってしまうが、ちゃんと関数名が "foo" になっている。

このままだと、外の関数を呼び出すことができない。

def dynamic_generated_fn_in_fn_error():
    created_fns = {}

    def add_phrase(x):
        return x + '_add'

    fn_code = """def {name}(x):
    return add_phrase(x)"""

    fn_name = 'foo'

    exec(fn_code.format(name=fn_name), {}, created_fns)
    print(created_fns[fn_name]('test'))
$ python test.py
Traceback (most recent call last):
  File "test.py", line 46, in <module>
    dynamic_generated_fn_in_fn_error()
  File "test.py", line 42, in dynamic_generated_fn_in_fn_error
    print(created_fns[fn_name]('test'))
  File "<string>", line 2, in foo
NameError: name 'add_phrase' is not defined

外の関数を呼び出したいときは functools で部分適用すればよい。ドキュメント → functools#partial

def dynamic_generated_fn_in_fn():
    created_fns = {}

    def add_phrase(x):
        return x + '_add'

    import functools
    fn_code = """def {name}(fn, x):
    return fn(x)"""

    fn_name = 'foo'

    exec(fn_code.format(name=fn_name), {}, created_fns)
    generated_fn = functools.partial(created_fns[fn_name], add_phrase)
    print(generated_fn('test'))
$ python test.py
test_add

内部で呼び出したい関数の個数分、適用する必要が出てくるが、まぁしょうがない。

フルコードは Gist に上げた。

2020-07-24