Developer

【Python連載】関数オブジェクトの概要
2021.12.27
Lv1

【Python連載】関数オブジェクトの概要

Pythonは、オブジェクト指向と呼ばれるプログラミング言語の一つです。
今回は、関数について、関数オブジェクトというものを紹介します。


関数オブジェクトとは

Pythonでは、関数とは使用する際にはdef~から始まる形式で定義し、行いたい処理をまとめておくことが出来るものでした。
そんな関数についてですが、関数も、『オブジェクト』として扱うことが出来ます。

オブジェクトとは、一般的に「物(モノ)」を意味するものでしたね。
Pythonのデータ(数値や文字列、リスト、タプル、辞書など)はすべてオブジェクトであり、関数もそれらと同じであるということになります。

オブジェクトだから何が出来るかというと・・・
例えば、数値や文字列は、変数に代入されたり、関数の引数として使用されたりしますよね。
それと同じく、関数も変数に代入されたり、関数の引数として使用することが可能です。

そのように扱えることでどんなメリットがあるかについては、最後に少しご紹介します。
まずは、基本的な使用例を見てみましょう。

関数を変数に代入する

次の例では、単に『こんにちは』と出力するmorningという関数を定義しました。
通常のユーザ定義関数のようにmorning() としても実行可能ですが、今回は変数に代入する形で関数を実行しています。
本来はただの変数に()を付けることは出来ませんが、関数を代入した場合はあたかもメソッドであるかのように()を付けて実行することが可能です。

#関数の定義
def morning():
    print("おはよう")

message = morning #変数に関数を代入
message()         #変数に入っている関数を実行
C:\Python> python 12-1-1.py
おはよう

関数の引数として関数を使用する

関数の引数として、関数を渡すことも可能です。
次の例では、引数で渡された関数を実行するような関数を定義しました。

#関数の定義
def greet(func):
    func()

def morning():      #関数greetに渡されて実行される関数(その1)
    print("おはよう")

def evening():      #関数greetに渡されて実行される関数(その2)
    print("こんばんは")

greet(morning)      #引数として関数morningを渡して実行
greet(evening)      #引数として関数eveningを渡して実行
C:\Python> python 12-1-2.py
おはよう
こんばんは

関数greetは、引数で渡された関数funcをそのまま実行する形で定義しています(2,3行目)。
今回は11行目と12行目で2種類の関数morningとeveningを引数として渡しているので、それが実行されたという形になります。

いかがでしょうか。
なにやら回りくどい感じで、少し難しく感じるかもしれませんね。
正直な話、上記のような例であれば、関数オブジェクトを使用しなくても通常の関数を実行する形で書いてしまっても、何も問題はありません。
(むしろ、そちらの方がわかりやすいと思います)

では、どんな時に関数オブジェクトのメリットがあるのでしょうか。
実は、初心者向けの参考書等では、関数オブジェクトというもののメリットが書かれていることは少ないです。
それはなぜかというと、少し難解だからです・・・。

ですので、『そこまで深追いはしたくないよ』という方は、上記の関数オブジェクトの基本的な書き方と読み方を覚えておいていただければ、問題ありません。

余裕のある方は、最後までご覧いただければと思います。

関数をオブジェクトとして扱えるメリット

関数オブジェクトのメリットを一言で表すなら、『コールバック関数と呼ばれるものの記述に役立つ』という感じです。

コールバック関数とは、関数に引数として渡されて、関数内から呼び出される関数のことです。
要するに、上記の例でいうところのmorning()やevening()が該当します。

さて、順を追って説明していきたいと思います。
まず、次のようなプログラムがあるとします。

定義されている関数は、渡されたリストの各要素をチェックして、値が条件に一致するものだけを取り出すような関数です。
今回は、値が正の時に取り出すような形にしました。

#関数の定義
def list_check(num_list) :
    after_num_list = []                #新たなリストを準備し、これに条件に合致した要素の値を格納
    for num in num_list :              #リストから1要素ずつ順番にチェック
        if num >= 0 :                  #要素の値が条件を満たしているかどうかチェック
            after_num_list.append(num) #条件を満たしていれば、その値をリストへ格納
    return after_num_list

num_list = [-2,2,-1,1,0,3,-3]          #チェック対象となるリスト
result_list = list_check(num_list)     #関数の実行
print(result_list)
C:\Python> python 12-1-3.py
[2, 1, 0, 3]

関数list_checkは、引数で受け取ったnum_listの各要素の値をチェックして、値が0以上のものだけを抽出して新たなリストafter_num_listを作成します。
実行した結果から、その通りのリストが作成されていることがわかります。

この関数ですが・・・
『値が0以上かどうか』が固定されているため、プログラムとして汎用性がありません。
仮に『1以上』や『-3以上』などに変更したくなった際には、関数定義部分を直接修正してあげなければいけません。

そこで次は、閾値も関数の引数として渡してあげる形にします。

#関数の定義
def list_check(num_list,threshold_val) :        #引数に閾値となるものを追加
    after_num_list = []
    for num in num_list :
        if num >= threshold_val :               #0以上と固定していたものを書き換え
            after_num_list.append(num)
    return after_num_list

num_list = [-2,2,-1,1,0,3,-3]
result_list = list_check(num_list,2)            #引数に閾値として2を指定
print(result_list)
C:\Python> python 12-1-4.py
[2, 3]

今回は2以上のデータだけを取り出したような結果になっています。

では、ここからが本題になります。

上記のプログラムは、あくまで『n以上』という条件に対応しただけのものになります。

他に例えば『n以上、かつ、m未満』や『kで割り切れるかどうか』や『iの倍数かどうか』等の条件式のものを準備したくなった場合には、引数を増やすだけでの解決は難しくなります。
上記の関数を丸ごと複製して、条件式部分を書き換えることが必要になるでしょう。

そこで、条件式部分だけを別の関数として準備して、その関数を引数として渡してあげます。
上述した通り、関数もオブジェクトなので、引数として渡すことが可能です。

#関数の定義
def list_check(num_list,callback_func) :   #引数にコールバック関数を追加
    after_num_list = []
    for num in num_list :
        if callback_func(num) :            #コールバック関数が条件を判定
            after_num_list.append(num)
    return after_num_list

#コールバック関数の定義(その1)
def callback_func1(num):
    if -1 <= num <= 1:
        return True
    else:
        return False

#コールバック関数の定義(その2)
def callback_func2(num):
    if num % 2 == 0:
        return True
    else:
        return False

#実行
num_list = [-2,2,-1,1,0,3,-3]
result_list1 = list_check(num_list,callback_func1)   #callback_func1で条件判定されたリストを作成
result_list2 = list_check(num_list,callback_func2)   #callback_func2で条件判定されたリストを作成

print(result_list1)
print(result_list2)
C:\Python> python 12-1-5.py
[-1, 1, 0] [-2, 2, 0]

条件の判定を行う部分を、別の関数を呼び出す形にしました。
このような関数のことを、コールバック関数といいます。
※ちなみに、関数を引数として使っている上記list_checkのような関数は、一般的に高階関数と呼ばれています。

上記例ではコールバック関数を二つ準備しました。引数に指定するコールバック関数を変えるだけで、簡単に条件を変えることが出来ています(24,25行目)。

これで、どのような条件判定を行うかは引数(として与えられる関数)で指定できるようになりました。
条件式ごと含んだチェック用の関数を丸ごと複製(または書き換え)する必要が無くなるため、汎用的になったと言えます。
このようなことが出来るということが、関数オブジェクトの大きなメリットと言われています。

ちなみに、コールバック関数に書かれている条件式がそこまで複雑ではない場合には『ラムダ式』というものを用いることで、簡潔に記述することが出来ます。

ラムダ式の詳細は次回以降の記事で触れますが、そのラムダ式を用いると、次のように書き換えることが出来ます。

#関数の定義
def list_check(num_list,callback_func) : 
    after_num_list = []
    for num in num_list :
        if callback_func(num) :
            after_num_list.append(num)
    return after_num_list

num_list = [-2,2,-1,1,0,3,-3]
result_list = list_check(num_list,lambda x : -1 <= x <= 1)
                        #ラムダ式と呼ばれる記述で、引数となる関数を記載
print(result_list)
C:\Python> python 12-1-6.py
[-1, 1, 0]

今はこの形を見ても難しいかもしれませんので、次回記事をご覧いただければと思います。
以上が、関数オブジェクトのお話でした。


まとめ

Pythonでは、関数をオブジェクトとして扱うことが出来ます。
関数を変数に代入したり、関数の引数として関数を使用することが可能です。


確認問題

次のプログラムを実行した時の出力について、正しいものを選択してください。

def greet(func):
    str = func()
    print(str)

def morning():
    return "おはよう"

def evening():
    return "こんばんは"

message = greet
message(morning)

(1)11行目で変数に関数を代入しているため、エラーが発生する
(2)おはよう と表示される
(3)こんばんは と表示される
(4)1行目で関数の引数として関数を指定しているため、エラーが発生する

答えは次回の記事に!

連載目次

独学で学ぶ Pythonプログラミング 連載目次