web-dev-qa-db-ja.com

現在の関数を変数に入れる方法は?

Pythonで現在実行中の関数を含む変数を取得するにはどうすればよいですか?関数の名前は必要ありません。 inspect.stackを使用して現在の関数名を取得できることはわかっています。実際の呼び出し可能オブジェクトが必要です。これは、inspect.stackを使用して関数名を取得し、名前をevalingして呼び出し可能なオブジェクトを取得せずに実行できますか?

編集:私にはこれを行う理由がありますが、それはリモートで良いものでさえありません。 plac を使用してコマンドライン引数を解析しています。 plac.call(main)を実行して使用します。これにより、「main」の関数シグネチャからArgumentParserオブジェクトが生成されます。 "main"内で、引数に問題がある場合、ArgumentParserオブジェクトのヘルプテキストを含むエラーメッセージで終了します。つまり、plac.parser_from(main).print_help()を呼び出してこのオブジェクトに直接アクセスする必要があります。代わりに、plac.parser_from(get_current_function()).print_help()と言ってもいいので、「main」という名前の関数に依存していません。現在、「get_current_function」の実装は次のようになります。

import inspect    
def get_current_function():
    return eval(inspect.stack()[1][3])

しかし、この実装は名前を持つ関数に依存しています。名前はそれほど面倒ではありません。 plac.call(lambda ...)をするつもりはありません。

長い目で見ると、placの作成者にprint_helpメソッドを実装して、最近、placなどを使用して呼び出された関数のヘルプテキストを印刷するよう依頼する方が便利な場合があります。

32

スタックフレームは、現在のコードオブジェクトを示します。その[__code__]属性でそのコードオブジェクトを参照する関数オブジェクトが見つかった場合、その関数は見つかりました。

幸い、Pythonワールド内のすべてのアクティブなオブジェクトをトラバースする必要がなく、コードオブジェクトへの参照を保持しているオブジェクトをガベージコレクターに問い合わせて、それらをふるいにかけることができます。通常、コードオブジェクトへの参照の数。

これで、関数はコードオブジェクトを共有できるようになり、関数from関数を返す場合(つまり、クロージャー)にも同じことができます。特定のコードオブジェクトを使用する関数が複数ある場合、どの関数であるかがわからないため、Noneを返します。

import inspect, gc

def giveupthefunc():
    frame = inspect.currentframe(1)
    code  = frame.f_code
    globs = frame.f_globals
    functype = type(lambda: 0)
    funcs = []
    for func in gc.get_referrers(code):
        if type(func) is functype:
            if getattr(func, "__code__", None) is code:
                if getattr(func, "__globals__", None) is globs:
                    funcs.append(func)
                    if len(funcs) > 1:
                        return None
    return funcs[0] if funcs else None

いくつかのテストケース:

def foo():
    return giveupthefunc()

zed = lambda: giveupthefunc()

bar, foo = foo, None

print bar()
print zed()

これのパフォーマンス特性についてはわかりませんが、ユースケースとしては問題ないと思います。

26
kindall

これはあなたが求めたものであり、私が来ることができる限り近くです。 pythonバージョン2.4、2.6、3.0でテスト済み。

#!/usr/bin/python
def getfunc():
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    caller = caller.f_back
    from pprint import pprint
    func = caller.f_locals.get(
            func_name, caller.f_globals.get(
                func_name
        )
    )

    return func

def main():
    def inner1():
        def inner2():
            print("Current function is %s" % getfunc())
        print("Current function is %s" % getfunc())
        inner2()
    print("Current function is %s" % getfunc())
    inner1()


#entry point: parse arguments and call main()
if __name__ == "__main__":
    main()

出力:

Current function is <function main at 0x2aec09fe2ed8>
Current function is <function inner1 at 0x2aec09fe2f50>
Current function is <function inner2 at 0x2aec0a0635f0>
15
bukzor

私は最近、このようなことをするために多くの時間を費やし、結局それから離れてしまいました。多くのコーナーケースがあります。

コールスタックの最下位レベルだけが必要な場合は、defステートメントで使用されている名前を参照するだけで済みます。これは、字句閉包を介して必要な関数にバインドされます。

例えば:

def recursive(*args, **kwargs):
    me = recursive

meは、定義が発生するスコープで再定義されない限り、関数の呼び出し元のスコープに関係なく、問題の関数を参照するようになります。これが機能しない理由はありますか?

呼び出しスタックの上位で実行されている関数を取得するために、確実に実行できることは何も考えられませんでした。

13
aaronasterling

これが別の可能性です:呼び出された関数への参照を最初の引数として暗黙的に渡すデコレーター(バインドされたインスタンスメソッドのselfと同様)。そのような参照を受け取りたい各関数を装飾する必要がありますが、彼らが言うように「明示的は暗黙的よりも優れています」。

もちろん、これにはデコレータのすべての欠点があります。別の関数呼び出しはパフォーマンスをわずかに低下させ、ラップされた関数のシグネチャは見えなくなります。

import functools

def gottahavethatfunc(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(func, *args, **kwargs)

    return wrapper

テストケースは、関数がバインドされている名前を変更しても、修飾された関数がそれ自体への参照を取得することを示しています。これは、ラッパー関数のバインディングのみを変更するためです。また、ラムダでの使用法も示しています。

@gottahavethatfunc
def quux(me):
    return me

zoom = gottahavethatfunc(lambda me: me)

baz, quux = quux, None

print baz()
print zoom()

このデコレータをインスタンスまたはクラスメソッドで使用する場合、メソッドは最初の引数として関数参照を受け入れ、2番目の引数として従来のselfを受け入れる必要があります。

class Demo(object):

    @gottahavethatfunc
    def method(me, self):
        return me

print Demo().method()

デコレーターは、ラッパー内のラップされた関数への参照を保持するクロージャーに依存しています。クロージャを直接作成すると、実際にはよりクリーンになり、追加の関数呼び出しのオーバーヘッドがなくなります。

def my_func():
    def my_func():
        return my_func
    return my_func
my_func = my_func()

内部関数内では、名前my_funcは常にその関数を指します。その値は、変更される可能性のあるグローバル名に依存しません。次に、その関数をグローバルネームスペースに「リフト」し、外部関数への参照を置き換えます。クラスでも動作します:

class K(object):
    def my_method():
        def my_method(self):
             return my_method
        return my_method
    my_method = my_method()
10
kindall

各関数の最初に、「キーワード」を定義します。これは、関数の実際の名前への参照にすぎません。それが必要かどうかにかかわらず、私はこれをすべての関数に対して実行します:

def test():
    this=test
    if not hasattr(this,'cnt'):
        this.cnt=0
    else:
        this.cnt+=1
    print this.cnt
6
Florian

呼び出しスタックは、関数自体への参照を保持しません。ただし、実行中のフレームは、特定の関数に関連付けられたコードであるコードオブジェクトへの参照です。

(関数はコードを持つオブジェクトであり、クロージャ、名前、グローバルディクショナリ、ドキュメント文字列、デフォルトパラメータなど、環境に関するいくつかの情報です)。

したがって、通常の関数を実行している場合は、指摘されているように、グローバルディクショナリで独自の名前を使用してそれ自体を呼び出す方が適切です。

関数名を使用できない動的コードまたはラムダコードを実行している場合、唯一の解決策は、現在実行中のコードオブジェクトを再利用する別の関数オブジェクトを再構築し、代わりにその新しい関数を呼び出すことです。

デフォルトの引数など、いくつかの要素が失われ、クロージャーで機能させるのが難しい場合があります(それは可能です)。

私はそれを正確に行うことについてブログ投稿を書いています-内部から匿名関数を呼び出す-私はそこのコードがあなたを助けることを願っています:

http://metapython.blogspot.com/2010/11/recursive-lambda-functions.html

余談ですが、inspect.stackの使用は避けてください。呼び出されるたびに多くの情報が再構築されるため、処理が遅すぎます。代わりにinspect.currentframeを使用してコードフレームを処理することをお勧めします。

これは複雑に聞こえるかもしれませんが、コード自体は非常に短いので、以下に貼り付けます。上記の投稿には、これがどのように機能するかについての詳細が含まれています。

from inspect import currentframe
from types import FunctionType

lambda_cache = {}

def myself (*args, **kw):
    caller_frame = currentframe(1)
    code = caller_frame.f_code
    if not code in lambda_cache:
        lambda_cache[code] = FunctionType(code, caller_frame.f_globals)
    return lambda_cache[code](*args, **kw)

if __name__ == "__main__":
    print "Factorial of 5", (lambda n: n * myself(n - 1) if n > 1 else 1)(5)

元の関数自体が本当に必要な場合は、上記の「myself」関数を使用して、一部のスコープ(関数のグローバルディクショナリの呼び出しなど)を検索し、代わりに、コードオブジェクトがフレームから取得したものと一致する関数オブジェクトを探すことができます。新しい関数を作成します。

4
jsbueno

sys._getframe(0).f_code は、必要なもの、つまり実行されているcodeobjectを正確に返します。コードオブジェクトがあると、codeobject。co_nameで名前を取得できます

4
muodov

質問とコメントをもう一度読んだ後、これはまともなテストケースだと思います。

def foo(n):
  """ print numbers from 0 to n """
  if n: foo(n-1)
  print n

g = foo    # assign name 'g' to function object
foo = None # clobber name 'foo' which refers to function object
g(10)      # dies with TypeError because function object tries to call NoneType

私はデコレータを使用して一時的にグローバル名前空間を上書きし、関数オブジェクトを関数の元の名前に再割り当てすることで解決しようとしました:

def selfbind(f):
   """ Ensures that f's original function name is always defined as f when f is executed """
   oname = f.__name__
   def g(*args, **kwargs):

      # Clobber global namespace
      had_key = None
      if globals().has_key(oname):
         had_key = True
         key = globals()[oname]
      globals()[oname] = g

      # Run function in modified environment
      result = f(*args, **kwargs)

      # Restore global namespace
      if had_key: 
         globals()[oname] = key
      else:
         del globals()[oname]

      return result

   return g

@selfbind
def foo(n):
   if n: foo(n-1)
   print n

g = foo   # assign name 'g' to function object
foo = 2   # calling 'foo' now fails since foo is an int
g(10)     # print from 0..10, even though foo is now an int
print foo # prints 2 (the new value of Foo)

すべてのユースケースを検討したわけではないと思います。私が目にする最大の問題は、関数オブジェクトが自身の名前が指すものを意図的に変更することです(デコレータによって上書きされる操作)が、再帰関数が途中で自分の名前を再定義しない限り、問題ありません再帰の。

それでもこれを行う必要があるかどうかはまだわかりませんが、考えることは面白かったです。

1
Triptych

ここで、同じコードオブジェクトを使用しているクロージャを区別しようとするget_referrers()回答のバリエーション(Python 3.5.1)を示します。

import functools
import gc
import inspect

def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        set(inspect.getclosurevars(referer).nonlocals.items()) <=
        set(frame.f_locals.items())][0]

def f1(x):
    def f2(y):
        print(get_func())
        return x + y
    return f2

f_var1 = f1(1)
f_var1(3)
# <function f1.<locals>.f2 at 0x0000017235CB2C80>
# 4

f_var2 = f1(2)
f_var2(3)
# <function f1.<locals>.f2 at 0x0000017235CB2BF8>
# 5

def f3():
    print(get_func())    

f3()
# <function f3 at 0x0000017235CB2B70>

def wrapper(func):
    functools.wraps(func)
    def wrapped(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped

@wrapper
def f4():
    print(get_func())    

f4()
# <function f4 at 0x0000017235CB2A60>

f5 = lambda: get_func()    

print(f5())
# <function <lambda> at 0x0000017235CB2950>
1
someone

Subdictチェックは既にdict_itemsで呼び出された「<=」で機能しており、追加のset()呼び出しは、dicts自体がdicts値である場合に問題が発生するため、私の以前の回答の修正:

import gc
import inspect


def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        inspect.getclosurevars(referer).nonlocals.items() <=
        frame.f_locals.items()][0]
0
someone