it-swarm.com.de

Wie bekomme ich die aktuelle Funktion in eine Variable?

Wie kann ich eine Variable erhalten, die die aktuell ausgeführte Funktion in Python enthält? Ich möchte den Namen der Funktion nicht. Ich weiß, dass ich inspect.stack verwenden kann, um den aktuellen Funktionsnamen abzurufen. Ich möchte das eigentliche aufrufbare Objekt. Kann dies durchgeführt werden, ohne inspect.stack zum Abrufen des Funktionsnamens und dann eval zum Abrufen des aufrufbaren Objekts zu verwenden?

Edit: Ich habe einen Grund, dies zu tun, aber es ist nicht einmal von weitem gut. Ich verwende plac , um Befehlszeilenargumente zu analysieren. Sie verwenden es, indem Sie plac.call(main) ausführen, wodurch ein ArgumentParser-Objekt aus der Funktionssignatur von "main" generiert wird. Wenn in "main" ein Problem mit den Argumenten auftritt, möchte ich mit einer Fehlermeldung schließen, die den Hilfetext des ArgumentParser-Objekts enthält. Dies bedeutet, dass ich durch Aufrufen von plac.parser_from(main).print_help() direkt auf dieses Objekt zugreifen muss. Es wäre schön, stattdessen sagen zu können: plac.parser_from(get_current_function()).print_help(), damit ich mich nicht darauf verlasse, dass die Funktion "main" heißt. Im Moment wäre meine Implementierung von "get_current_function":

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

Diese Implementierung beruht jedoch darauf, dass die Funktion einen Namen hat, was meiner Meinung nach nicht zu lästig ist. Ich werde niemals plac.call(lambda ...) machen.

Auf lange Sicht könnte es sinnvoller sein, den Autor von plac aufzufordern, eine print_help-Methode zu implementieren, um den Hilfetext der Funktion zu drucken, die zuletzt mit plac oder ähnlichem aufgerufen wurde.

29
Ryan Thompson

Der Stack-Frame sagt uns, in welchem ​​Code-Objekt wir uns befinden. Wenn wir ein Funktionsobjekt finden, das auf dieses Code-Objekt in seinem __code__-Attribut verweist, haben wir die Funktion gefunden.

Glücklicherweise können wir den Garbage Collector fragen, welche Objekte einen Verweis auf unser Code-Objekt enthalten, und diese durchsuchen, anstatt jedes aktive Objekt in der Python-Welt durchlaufen zu müssen. Normalerweise gibt es nur eine Handvoll Verweise auf ein Codeobjekt.

Jetzt können Funktionen Codeobjekte gemeinsam verwenden und tun dies für den Fall, dass Sie eine Funktion from eine Funktion zurückgeben, d. H. Eine Schließung. Wenn mehr als eine Funktion ein bestimmtes Code-Objekt verwendet, können wir nicht feststellen, um welche Funktion es sich handelt. Daher geben wir None zurück.

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

Einige Testfälle:

def foo():
    return giveupthefunc()

zed = lambda: giveupthefunc()

bar, foo = foo, None

print bar()
print zed()

Ich bin mir nicht sicher, welche Leistungsmerkmale dies hat, aber ich denke, es sollte für Ihren Anwendungsfall in Ordnung sein.

26
kindall

Sie haben darum gebeten, so nahe ich nur kann. In Python-Versionen 2.4, 2.6, 3.0 getestet.

#!/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 __== "__main__":
    main()

Ausgabe:

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

Ich habe vor kurzem viel Zeit damit verbracht, so etwas zu tun, und bin dann weggegangen. Es gibt viele Eckfälle. 

Wenn Sie nur die unterste Ebene des Aufrufstapels wünschen, können Sie nur auf den Namen verweisen, der in der def-Anweisung verwendet wird. Dies wird durch lexikalisches Schließen an die gewünschte Funktion gebunden.

Zum Beispiel:

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

me bezieht sich jetzt auf die betreffende Funktion, unabhängig von dem Gültigkeitsbereich, aus dem die Funktion aufgerufen wird, solange sie nicht in dem Gültigkeitsbereich neu definiert wird, in dem die Definition auftritt. Gibt es einen Grund, warum dies nicht funktioniert?

Um eine Funktion zu erhalten, die höher im Aufrufstack ausgeführt wird, fällt mir nichts ein, was zuverlässig ausgeführt werden kann.

12
aaronasterling

Hier ist eine andere Möglichkeit: Ein Dekorator, der implizit einen Verweis auf die aufgerufene Funktion als erstes Argument übergibt (ähnlich wie self in gebundenen Instanzmethoden). Sie müssen jede Funktion dekorieren, die eine solche Referenz erhalten soll, aber "explizit ist besser als implizit", wie sie sagen. 

Natürlich hat es alle Nachteile von Dekorateuren: Ein weiterer Funktionsaufruf verschlechtert die Leistung geringfügig, und die Signatur der umschlossenen Funktion ist nicht mehr sichtbar.

import functools

def gottahavethatfunc(func):

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

    return wrapper

Der Testfall zeigt, dass die dekorierte Funktion auch dann den Verweis auf sich selbst erhält, wenn Sie den Namen ändern, an den die Funktion gebunden ist. Dies liegt daran, dass Sie nur die Bindung der Wrapper-Funktion ändern. Es zeigt auch seine Verwendung mit einem Lambda.

@gottahavethatfunc
def quux(me):
    return me

zoom = gottahavethatfunc(lambda me: me)

baz, quux = quux, None

print baz()
print zoom()

Bei Verwendung dieses Dekorators mit einer Instanz- oder Klassenmethode sollte die Methode die Funktionsreferenz als erstes Argument und die traditionelle Variable self als zweites akzeptieren.

class Demo(object):

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

print Demo().method()

Der Dekorateur ist auf einen Verschluss angewiesen, um den Verweis auf die umwickelte Funktion im Wrapper zu halten. Das direkte Erstellen des Abschlusses ist möglicherweise sauberer und hat keinen zusätzlichen Aufwand für den Funktionsaufruf:

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

Innerhalb der inneren Funktion bezieht sich der Name my_func immer auf diese Funktion. Ihr Wert hängt nicht von einem globalen Namen ab, der geändert werden kann. Dann "heben" wir diese Funktion einfach in den globalen Namespace und ersetzen die Referenz auf die äußere Funktion. Funktioniert auch in einer Klasse:

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

Ich definiere einfach zu Beginn jeder Funktion ein "Schlüsselwort", das nur einen Verweis auf den tatsächlichen Namen der Funktion darstellt. Ich mache das einfach für jede Funktion, ob sie es braucht oder nicht:

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

Der Aufrufstapel behält keinen Verweis auf die Funktion selbst - , Obwohl der laufende Frame als Verweis auf das Codeobjekt dient, bei dem es sich um den Code handelt, der einer bestimmten Funktion zugeordnet ist.

(Funktionen sind Objekte mit Code und einige Informationen zu ihrer Umgebung, z. B. Schließungen, Name, globales Wörterbuch, Dokumentstring, Standardparameter usw.).

Wenn Sie also eine reguläre Funktion ausführen, sollten Sie besser einen eigenen Namen im Globals-Wörterbuch verwenden, um sich selbst aufzurufen, wie bereits erwähnt wurde.

Wenn Sie dynamischen oder Lambda-Code ausführen, in dem Sie den Funktionsnamen nicht verwenden können, besteht die einzige Lösung darin, ein anderes Funktionsobjekt neu zu erstellen, das das gerade ausgeführte Codeobjekt erneut verwendet, und stattdessen diese neue Funktion aufzurufen.

Sie verlieren ein paar Dinge, z. B. Standardargumente, und es kann schwierig sein, sie mit Schließungen zum Laufen zu bringen (obwohl dies möglich ist).

Ich habe einen Blogpost geschrieben, in dem es darum geht, anonyme Funktionen aus sich selbst heraus aufzurufen. Ich hoffe, der Code dort kann Ihnen helfen:

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

Nebenbei bemerkt: Vermeiden Sie die Verwendung von "inspect.stack" - es ist zu langsam, da es bei jedem Aufruf eine Vielzahl von Informationen erstellt. Verwenden Sie inspect.currentframe lieber, um stattdessen mit Codeframes zu arbeiten.

Das hört sich vielleicht kompliziert an, aber der Code selbst ist sehr kurz - ich klebe ihn unten. Der Beitrag oben enthält weitere Informationen zur Funktionsweise.

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 __== "__main__":
    print "Factorial of 5", (lambda n: n * myself(n - 1) if n > 1 else 1)(5)

Wenn Sie die ursprüngliche Funktion wirklich benötigen, könnte die Funktion "Ich" oben dazu dienen, in einigen Bereichen (wie dem aufrufenden Funktionswörterbuch) nach einem Funktionsobjekt zu suchen, dessen Codeobjekt mit dem aus dem Rahmen abgerufenen übereinstimmen würde eine neue Funktion erstellen.

4
jsbueno

sys._getframe (0) .f_code gibt genau das zurück, was Sie benötigen: das Codeobjekt, das ausgeführt wird. Mit einem Codeobjekt können Sie einen Namen mit codeobject . Co_name abrufen

2
muodov

OK, nachdem ich die Frage und die Kommentare noch einmal gelesen habe, denke ich, dass dies ein anständiger Testfall ist:

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

Ich habe versucht, das Problem zu lösen, indem ein Dekorator verwendet wurde, um den globalen Namespace vorübergehend zu blockieren und das Funktionsobjekt dem ursprünglichen Namen der Funktion neu zuzuweisen:

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)

Ich bin mir sicher, dass ich nicht alle Anwendungsfälle durchdacht habe. Das größte Problem, das ich sehe, ist das Funktionsobjekt, das absichtlich ändert, worauf sein eigener Name verweist (eine Operation, die vom Dekorateur überschrieben werden würde), aber es sollte in Ordnung sein, solange die rekursive Funktion nicht ihren eigenen Namen in der Mitte neu definiert der Rekursion.

Immer noch nicht sicher, ob ich das jemals tun müsste, aber darüber nachzudenken war interessant.

1
Triptych

Hier eine Variation (Python 3.5.1) der Antwort von get_referrers (), die versucht, zwischen Schließungen zu unterscheiden, die dasselbe Codeobjekt verwenden:

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

Korrektur meiner vorherigen Antwort, da die Subdict-Prüfung bereits mit "<=" funktioniert, das für dict_items aufgerufen wird, und die zusätzlichen Aufrufe von set () zu Problemen führen, wenn Dict-Werte selbst Dict-Werte sind:

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