グレーな道を進む

半端研究者の備忘録と日記です.主に,アルゴリズムやプログラムに関する話題を書こうと思っています.

Python: 関数を引数とする関数の作り方(+関数の引数を省略したい)

一応,混乱のないようにタイトルを書いたつもりですが.

今回の内容は,Pythonで関数を作る話というよりは,Pythonで"関数"を引数とする関数を作る話です.「そんなことはわかっている」という人は,そのままお読みください.「普通の関数の作り方が知りたいのだが.」っという人は下記のリンクを見てください.

Pythonを学ぼう 第24回 関数の基本 - ほぷしぃ

話を戻します. 関数の引数は用途に応じて,数値,文字列,アドレス値など様々ですが,本記事では関数を引数とする方法について述べます.また,引数とした関数の引数の一部を省略する方法についても述べます.これは,matlabでは,eval関数や@関数を使うことで出来る(また,他の言語でも基本的に出来る).これは非常に便利なので,Pythonもできないと困る.ということで調べてました.

どうやらPythonではfunctoolsのparticalmapを使うことによって可能らしい.mapの使い方が地味に面倒(まあ,Python全般がちょこちょこ面倒だが).

ここでは,二つの数字を足すだけの関数add_func

# add    
def add_func(n, m):
    val = n + m;
    print(" ==in add_func==");
    print(" val=", val);
    return  val;

を対象に,以下の4つを試す.

  1. 普通に関数として(直接的に)実行する
  2. 関数を引数に指定して間接的に実行する
  3. particalとmapを使って間接的に実行する
  4. particalとmapを使って間接的に実行する(一部引数を省略する)

[ソースコード] test3_func.py - Google ドライブ

以降,実行コード内を分割して説明していく(とりあえず実行したい場合は,上記コードを実行).

1. 普通に関数として(直接的に)実行する

以下にコード.特に関数を引数にしているわけでもないので普通に実行するだけ.

n = 10;
m = 20;
print("");

# 関数を直接実行する
print("*** 直接実行 ***** ");
add_func(n,m);
print("");

これを実行すると

f:id:gara14:20160811204124p:plain

といった結果が出力される.引数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("");

で,これを実行すると

f:id:gara14:20160811204128p:plain

が出力される.出力結果から,test_func→add_funcという順序で実行されていることがわかる. この方法で,関数を引数として扱うことが出来る.ただ,この方法だとadd_funcの引数をtest_funcの引数に使わなければならない.これは結構手間だし,記述が冗長になるので避けたい.要するに,元の関数はどうあれ,test_funcを使う段階で,mが特定値で良いのなら省略したい.例えば,test_func(add_func, n)みたいに.これは,functoolsのparticalとmapを使えば出来るらしい.

3. particalとmapを使って間接的に実行する

参考:functools.Partical

てはじめに,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で実行すると

f:id:gara14:20160811204145p:plain

と出力される(色々見れるのは楽).先ほどのコードを実行すると,

f:id:gara14:20160811204137p:plain

が出力され,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で実行すると

f:id:gara14:20160811204149p:plain

が出力される.引数の一部が指定されているのが見て取れる.で,肝心の実行結果は

f:id:gara14:20160811204140p:plain

が出力される.今までと違いm=10なので,n+m=20となっている.

まとめ

以上が,関数型の引数を使う方法と,関数型引数の一部引数を省略する方法.個人的な感想としては,単に関数を引数として使う場合(2.)には,Pythonのほうが楽に書ける.一方,引数指定の省略する場合(4.)には,matlabのが楽に書ける.といった印象.関数自体(ライブラリ含む)の作りやすさを考慮すると,Pythonが有利か?