Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

Pythonで関数かメソッドかラムダか__call__()かを判別する

はじめに

Pythonでは、呼び出し可能な関数ライクなオブジェクトをいくつかの方法で作成することができる。

  • 関数として定義
  • ラムダとして定義
  • クラスにメソッドとして定義
  • __call__()を実装することで定義
  • (他にもあるかもしれない)

ここで、デバッグソースコードの解析のために、実行時にできるだけ正確に、どの方法で作成されたかを知りたいという状況になった。 単純にisinstance(x, Callable)するだけだと、xが上記のどれの場合も真になってしまうので、別の方法を考える必要が出てきた。

環境

$ python3 --version
Python 3.10.12

検査の方法

それぞれ個別に判別するため、以下の検査用の関数を作成してみた。 isinstance(x, FunctionType)だけでは関数かラムダかを区別できないので、__name__も検査する必要がある。 ちなみにtypes.FunctionTypeと同様にtypes.LambdaTypeがあるものの、これは実質的にtypes.FunctionTypeの別名であり、ラムダかどうかの判別には利用できない。

from typing import Callable
from types import FunctionType, MethodType

def is_function(x) -> bool:
    return isinstance(x, FunctionType) and (x.__name__ != '<lambda>')

def is_method(x) -> bool:
    return isinstance(x, MethodType) and (x.__name__ != '<lambda>')

def is_lambda(x) -> bool:
    return (isinstance(x, FunctionType) or isinstance(x, MethodType)) and (x.__name__ == '<lambda>')

def is_call(x) -> bool:
    return isinstance(x, Callable) and (not isinstance(x, FunctionType)) and (not isinstance(x, MethodType))

実行結果

以下のテストコードを実行してみる。

# 関数として定義
def no1_func(x):
    return x

# ラムダとして定義
no2_lambda = lambda x: x

class Functor:
    def __init__(self) -> None:
        # ラムダをメンバーとして定義
        self.no3_lambda = lambda x: x

    # メソッドとして定義    
    def no4_method(self, x):
        return x

    # 関数オブジェクトとして定義    
    def __call__(self, x):
        return x

obj = Functor()

funcs = {
    'no1_func': no1_func,
    'no2_lambda': no2_lambda,
    'no3_lambda': obj.no3_lambda,
    'no4_method': obj.no4_method,
    'no5_functor': obj,
}

for name, func in funcs.items():
    print(f'{name:11s} ... ', end='')
    print(f'function: {is_function(func):1d},  ', end='')
    print(f'lambda: {is_lambda(func):1d},  ', end='')
    print(f'method: {is_method(func):1d},  ', end='')
    print(f'__call__: {is_call(func):1d}', end='')
    print()

実行結果は以下の通り。

no1_func    ... function: 1,  lambda: 0,  method: 0,  __call__: 0
no2_lambda  ... function: 0,  lambda: 1,  method: 0,  __call__: 0
no3_lambda  ... function: 0,  lambda: 1,  method: 0,  __call__: 0
no4_method  ... function: 0,  lambda: 0,  method: 1,  __call__: 0
no5_functor ... function: 0,  lambda: 0,  method: 0,  __call__: 1

参考

stackoverflow.com

docs.python.org