Python: 関数を引数とする関数の作り方(+関数の引数を省略したい)
一応,混乱のないようにタイトルを書いたつもりですが.
今回の内容は,Pythonで関数を作る話というよりは,Pythonで"関数"を引数とする関数を作る話です.「そんなことはわかっている」という人は,そのままお読みください.「普通の関数の作り方が知りたいのだが.」っという人は下記のリンクを見てください.
話を戻します. 関数の引数は用途に応じて,数値,文字列,アドレス値など様々ですが,本記事では関数を引数とする方法について述べます.また,引数とした関数の引数の一部を省略する方法についても述べます.これは,matlabでは,eval関数や@関数を使うことで出来る(また,他の言語でも基本的に出来る).これは非常に便利なので,Pythonもできないと困る.ということで調べてました.
どうやらPythonではfunctoolsのparticalとmapを使うことによって可能らしい.mapの使い方が地味に面倒(まあ,Python全般がちょこちょこ面倒だが).
ここでは,二つの数字を足すだけの関数add_func
# add def add_func(n, m): val = n + m; print(" ==in add_func=="); print(" val=", val); return val;
を対象に,以下の4つを試す.
- 普通に関数として(直接的に)実行する
- 関数を引数に指定して間接的に実行する
- particalとmapを使って間接的に実行する
- particalとmapを使って間接的に実行する(一部引数を省略する)
[ソースコード] test3_func.py - Google ドライブ
以降,実行コード内を分割して説明していく(とりあえず実行したい場合は,上記コードを実行).
1. 普通に関数として(直接的に)実行する
以下にコード.特に関数を引数にしているわけでもないので普通に実行するだけ.
n = 10; m = 20; print(""); # 関数を直接実行する print("*** 直接実行 ***** "); add_func(n,m); print("");
これを実行すると
といった結果が出力される.引数n=10, m=20が足されてval=30が出力されている.
2. 関数の引数に指定して間接的に実行する
関数を引数にする関数を作るといっても,表面上は意識することはない.次のようにadd_funcを間接的に実行するのための関数test_func
def test_func(func, n, m): print("==in test_func=="); val = func(n, m); print(" func(n,m)=", val);
を新たに定義する.test_func()は,見ての通り内部でval =add_func(n,m)と書くことでadd_func()を呼び出している.matlabだとval=eval(add_func, n, m)とする必要があるので,evalを使わない分,Pythonのほうがスマート.これを実行するためのコードは
# (functools, map)を使わない方法 print("*** functools+map 未使用 ***** "); test_func(add_func, n, m); print("");
で,これを実行すると
が出力される.出力結果から,test_func→add_funcという順序で実行されていることがわかる. この方法で,関数を引数として扱うことが出来る.ただ,この方法だとadd_funcの引数をtest_funcの引数に使わなければならない.これは結構手間だし,記述が冗長になるので避けたい.要するに,元の関数はどうあれ,test_funcを使う段階で,mが特定値で良いのなら省略したい.例えば,test_func(add_func, n)みたいに.これは,functoolsのparticalとmapを使えば出来るらしい.
3. particalとmapを使って間接的に実行する
てはじめに,2.と同様のことをparticalとmapを使って実現する.内容としては同じだが,functools.particalを使うためにmapを使う必要があり,そのためにtest_func()の中身を少し修正しなくてはならない.この関数(test2_func)を以下に載せる.
def test2_func(func, n, m ): print("==in test2_func=="); val = list(map(func,[n],[m]))[0]; print(" func(n,m)=", val);
違いがあるのは,3行目.正直,mapに関しては「第一引数に,他の引数を与えてその結果全てを取得する」関数という程度としてしか認識していない.mapの返し値は実体を持っていないらしく,これを配列にするためlistが追加で必要. 肝心の実行部分は
# (functools, map)を使う方法 print("*** functools+map 使用 ***** ") add_func_obj = functools.partial(add_func); # オブジェクト化 test2_func(add_func_obj, n, m); print("");
となる.test2_func()を実行する前に add_func_obj = functools.partial(add_func); と書き,関数をオブジェクト化する.ちなみに,add_func_objをprintで実行すると
と出力される(色々見れるのは楽).先ほどのコードを実行すると,
が出力され,2.0と同じ結果が得られることがわかる.これだけ見ると,particalを使うメリットはないが,この方法だと引数の省略が出来る.次はその話.
4. particalとmapを使って間接的に実行する(一部引数を省略する)
まあ,3.冒頭のリンク先に書いてあるが,この方法だと引数にした関数の引数を省略することが出来る.流れ的には,メイン部分を先に出したほうが良い気がするが,今までと同じで関数→メイン部分の順で載せる. まず,経由する関数は
def test3_func(func, n ): print("==in test2_func=="); val = list(map(func,[n]))[0]; print(" func(n,m)=", val);
となる.減らす引数(m)がなくなっているだけ.次に,メイン部分は
# (functools, map)を使う方法+関数型引数の一部を固定する print("*** functools+map 使用(一部値指定)***** ") add_func_obj = functools.partial(add_func, m=10); test3_func(add_func_obj, n); print("");
となる.3.と比べると三行目が異なり,particalの第二引数にm=10が追加されている.感覚的には,m=10を含んだadd_funcのオブジェクト化と捉えている.add_func_objをprintで実行すると
が出力される.引数の一部が指定されているのが見て取れる.で,肝心の実行結果は
が出力される.今までと違いm=10なので,n+m=20となっている.
まとめ
以上が,関数型の引数を使う方法と,関数型引数の一部引数を省略する方法.個人的な感想としては,単に関数を引数として使う場合(2.)には,Pythonのほうが楽に書ける.一方,引数指定の省略する場合(4.)には,matlabのが楽に書ける.といった印象.関数自体(ライブラリ含む)の作りやすさを考慮すると,Pythonが有利か?