1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Python] 受け取った関数がユーザーが定義した関数であることを確認する

Posted at

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__))
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?