Pythonでライブラリを作っている時に、「与えられた関数は本当にユーザーが実装した関数か?」をチェックしたい時に使えるTipsです。
まず関数(callable)かどうか判定
これは言うまでもないですね。
import inspect
inspect.isfunction(func)
ビルトイン関数、frozen関数かどうかを判定
Pythonでは、C言語で記述されたビルトイン関数、またPythonで記述された標準ライブラリのうち、バイトコードの形でPython処理系に組み込まれたfrozen関数が使えます。たとえばbuiltins.sorted()
はビルトイン関数、zipimport.zipimporter()
はfrozen関数です。
sys.modules
を用いる方法
これは比較的わかりやすいと思うのですが、sys.modules
からfunc
が定義されたモジュールオブジェクトを探します。これに__file__
という属性がなければビルトイン関数またはfrozen関数です。(ビルトインモジュール及びfrozenモジュール以外はPythonでは必ずファイルと1対1に対応します)
modname = func.__module__
mod = sys.modules[modname]
fname = getattr(mod, '__file__', None)
return fname != None
code
オブジェクトを用いる方法
他の方法として以下のような方法があります。こちらの方が簡潔だと思います。
co = getattr(func, '__code__', None)
return co != None
ユーザーが作成した関数かを判定
いくつか方法はあると思いますが、ここでは関数の定義されたファイルのパスを取得し、それが__main__
モジュールのパス(STDINならワーキングディレクトリ)以下に位置していれば、既存のライブラリではなくユーザーが作成したファイルであると判定します。
なお、この判定はワーキングディレクトリ以下にライブラリのキャッシュを置くパッケージマネージャ(pyflow
など)では機能しないかもしれません。
また、os.chdir
等を使ってワーキングディレクトリを変更しつつimport
を行っている場合も機能しないかもしれません。(そんなプログラムあるのか知りませんが)
filename = path.abspath(co.co_filename)
return filename.startswith(path.join(get_maindir(), ''))
def get_maindir():
mm = sys.modules['__main__']
fname = getattr(mm, '__file__', None)
if fname == None:
# STDIN
return os.getcwd()
else:
return path.dirname(path.abspath(mm.__file__))
全ソースコード
import sys
import os
import inspect
from os import path
def is_userfunc_b(func):
if not inspect.isfunction(func):
return False
modname = func.__module__
mod = sys.modules[modname]
fname = getattr(mod, '__file__', None)
if fname == None:
return false
fname = path.abspath(fname)
return fname.startswith(path.join(get_maindir(), ''))
def is_userfunc(func):
if not inspect.isfunction(func):
return False
co = getattr(func, '__code__', None)
if co == None:
return False
# Check that the file is placed under main module
filename = path.abspath(co.co_filename)
return filename.startswith(path.join(get_maindir(), ''))
def get_maindir():
mm = sys.modules['__main__']
fname = getattr(mm, '__file__', None)
if fname == None:
# STDIN
return os.getcwd()
else:
return path.dirname(path.abspath(mm.__file__))