Bestimmen Sie den Funktionsnamen innerhalb dieser Funktion (ohne Traceback zu verwenden).

479

Gibt es in Python ohne Verwendung des tracebackModuls eine Möglichkeit, den Namen einer Funktion innerhalb dieser Funktion zu bestimmen?

Angenommen, ich habe ein Modul foo mit einer Funktionsleiste. Gibt es bei der Ausführung foo.bar()eine Möglichkeit für Bar, den Namen der Bar zu kennen? Oder noch besser, foo.barder Name?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?
rauben
quelle
6
Ich verstehe nicht, warum die akzeptierte Antwort nicht die von Andreas Young ist (oder eine andere, die zeigt, wie es geht). Stattdessen lautet die akzeptierte Antwort "Sie können das nicht tun", was falsch erscheint. Die einzige Anforderung des OP war die Nichtverwendung traceback. Nicht einmal die Zeiten der Antworten und Kommentare scheinen dies zu unterstützen.
sancho.s ReinstateMonicaCellio

Antworten:

198

Python hat keine Funktion, um auf die Funktion oder ihren Namen innerhalb der Funktion selbst zuzugreifen. Es wurde vorgeschlagen, aber abgelehnt. Wenn Sie nicht selbst mit dem Stack spielen möchten, sollten Sie entweder "bar"oder bar.__name__je nach Kontext verwenden.

Die angegebene Ablehnungserklärung lautet:

Dieser PEP wird abgelehnt. Es ist nicht klar, wie es implementiert werden soll oder wie die genaue Semantik in Randfällen aussehen soll, und es werden nicht genügend wichtige Anwendungsfälle angegeben. Die Reaktion war bestenfalls lauwarm.

Rosh Oxymoron
quelle
17
inspect.currentframe()ist ein solcher Weg.
Yuval
41
Durch die Kombination des Ansatzes von @ CamHart mit dem von @ Yuval werden "versteckte" und möglicherweise veraltete Methoden in der Antwort von @ RoshOxymoron sowie die numerische Indizierung in den Stapel für die Antwort von @ neuro / @ AndreasJung vermieden:print(inspect.currentframe().f_code.co_name)
Kochfelder
2
Kann man zusammenfassen, warum es abgelehnt wurde?
Charlie Parker
1
Warum ist dies die gewählte Antwort? Bei der Frage geht es nicht um den Zugriff auf die aktuelle Funktion oder das Modul selbst, sondern nur um den Namen. Die Stacktrace- / Debugging-Funktionen enthalten diese Informationen bereits.
Nurettin
410
import inspect

def foo():
   print(inspect.stack()[0][3])
   print(inspect.stack()[1][3]) #will give the caller of foos name, if something called foo
Andreas Jung
quelle
47
Dies ist großartig, da Sie auch [1][3]den Namen des Anrufers ermitteln können.
Kos
57
Sie können auch Folgendes verwenden: print(inspect.currentframe().f_code.co_name)oder, um den Namen des Anrufers abzurufen : print(inspect.currentframe().f_back.f_code.co_name). Ich denke, es sollte schneller sein, da Sie nicht wie bisher eine Liste aller Stapelrahmen abrufen inspect.stack().
Michael
7
inspect.currentframe().f_back.f_code.co_namefunktioniert nicht mit einer dekorierten Methode, wohingegen inspect.stack()[0][3]...
Dustin Wyatt
4
Bitte beachten Sie: inspect.stack () kann einen hohen Leistungsaufwand verursachen. Verwenden Sie es daher sparsam! Auf meiner Armbox dauerte es 240ms (aus irgendeinem Grund)
Gardarh
Mir scheint, dass etwas in der Python-Rekursionsmaschinerie vorhanden sein könnte, um dies effizienter zu tun
Tom Russell
160

Es gibt verschiedene Möglichkeiten, um das gleiche Ergebnis zu erzielen:

from __future__ import print_function
import sys
import inspect

def what_is_my_name():
    print(inspect.stack()[0][0].f_code.co_name)
    print(inspect.stack()[0][3])
    print(inspect.currentframe().f_code.co_name)
    print(sys._getframe().f_code.co_name)

Beachten Sie, dass die inspect.stackAnrufe tausendmal langsamer sind als die Alternativen:

$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][0].f_code.co_name'
1000 loops, best of 3: 499 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][3]'
1000 loops, best of 3: 497 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.currentframe().f_code.co_name'
10000000 loops, best of 3: 0.1 usec per loop
$ python -m timeit -s 'import inspect, sys' 'sys._getframe().f_code.co_name'
10000000 loops, best of 3: 0.135 usec per loop
Alex Granovsky
quelle
5
inspect.currentframe()scheint ein guter Kompromiss zwischen Ausführungszeit und Verwendung von privaten Mitgliedern
FabienAndre
1
@mbdevpl Meine Zahlen sind 1,25 ms, 1,24 ms, 0,5 us, 0,16 us normale (nichtpythonische :)) Sekunden entsprechend (win7x64, python3.5.1)
Antony Hatchkins
Nur damit niemand @ mbdevpl für verrückt hält :) - Ich habe eine Bearbeitung für die Ausgabe des 3. Laufs eingereicht, da dies keinen Sinn ergab. Keine Ahnung, ob das Ergebnis hätte sein sollen 0.100 usecoder so oder 0.199 usecso - hunderte Male schneller als Option 1 und 2, vergleichbar mit Option 4 (obwohl Antony Hatchkins Option 3 dreimal schneller als Option 4 fand).
Dwanderson
Ich benutze sys._getframe().f_code.co_nameover inspect.currentframe().f_code.co_nameeinfach, weil ich das sysModul bereits importiert habe . Ist das eine vernünftige Entscheidung? (wenn man bedenkt, dass die Geschwindigkeiten ziemlich ähnlich erscheinen)
PatrickT
47

Sie können den Namen, mit dem es definiert wurde, mit dem von @Andreas Jung gezeigten Ansatz erhalten , aber dies ist möglicherweise nicht der Name, mit dem die Funktion aufgerufen wurde:

import inspect

def Foo():
   print inspect.stack()[0][3]

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Ob diese Unterscheidung für Sie wichtig ist oder nicht, kann ich nicht sagen.

bgporter
quelle
3
Gleiche Situation wie bei .func_name. Denken Sie daran, dass Klassennamen und Funktionsnamen in Python eine Sache sind und Variablen, die sich auf sie beziehen, eine andere.
Kos
Manchmal möchten Sie vielleicht Foo2()drucken Foo. Zum Beispiel : Foo2 = function_dict['Foo']; Foo2(). In diesem Fall ist Foo2 ein Funktionszeiger für möglicherweise einen Befehlszeilenparser.
Harvey
Welche Auswirkungen auf die Geschwindigkeit hat dies?
Robert C. Barth
2
Geschwindigkeitsimplikation in Bezug auf was? Gibt es eine Situation, in der Sie diese Informationen in einer schwierigen Echtzeitsituation benötigen oder so?
Bgporter
44
functionNameAsString = sys._getframe().f_code.co_name

Ich wollte eine sehr ähnliche Sache, weil ich den Funktionsnamen in eine Protokollzeichenfolge einfügen wollte, die an mehreren Stellen in meinem Code stand. Wahrscheinlich nicht der beste Weg, dies zu tun, aber hier ist ein Weg, um den Namen der aktuellen Funktion zu erhalten.

Ron Davis
quelle
2
Völlig funktionierend, nur mit sys, müssen keine weiteren Module geladen werden, aber es ist nicht so einfach, sich daran zu erinnern: V
m3nda
@ erm3nda Siehe meine Antwort.
nerdfever.com
1
@ nerdfever.com Mein Problem ist nicht, eine Funktion zu erstellen, sondern sich nicht daran zu erinnern, was in diese Funktion eingefügt werden soll. Ist nicht leicht zu merken, deshalb muss ich immer eine Notiz sehen, um das wieder aufzubauen. Ich werde versuchen, den fFrame und den coCode im Auge zu behalten . Ich benutze das nicht so sehr,
umso
24

Ich habe dieses praktische Dienstprogramm in der Nähe:

import inspect
myself = lambda: inspect.stack()[1][3]

Verwendungszweck:

myself()
xxyzzy
quelle
1
Wie würde dies mit der hier vorgeschlagenen Alternative geschehen? "yourself = lambda: sys._getframe (). f_code.co_name" funktioniert nicht (die Ausgabe ist "<lambda>"; ich denke, weil das Ergebnis zur Definitionszeit und nicht später zur
Anrufzeit bestimmt wird
2
@ prismalytics.io: Wenn du mich (mich ()) nennst und nicht nur seinen Wert (mich) verwendest, bekommst du, wonach du suchst.
ijw
NYCeyes hatte recht, der Name wird im Lambda aufgelöst und somit ist das Ergebnis <lambda>. Die Methoden sys._getframe () und inspect.currentframe () MÜSSEN direkt in der Funktion ausgeführt werden, deren Namen Sie erhalten möchten. Die Methode inspect.stack () funktioniert, weil Sie den Index 1 angeben können. Wenn Sie inspect.stack () [0] [3] ausführen, erhalten Sie auch <lambda>.
Kikones34
20

Ich denke, das inspectist der beste Weg, dies zu tun. Zum Beispiel:

import inspect
def bar():
    print("My name is", inspect.stack()[0][3])
Björn
quelle
17

Ich habe einen Wrapper gefunden, der den Funktionsnamen schreibt

from functools import wraps

def tmp_wrap(func):
    @wraps(func)
    def tmp(*args, **kwargs):
        print func.__name__
        return func(*args, **kwargs)
    return tmp

@tmp_wrap
def my_funky_name():
    print "STUB"

my_funky_name()

Dies wird gedruckt

my_funky_name

STUB

cad106uk
quelle
1
Als Dekorateur Noob frage ich mich, ob es eine Möglichkeit gibt, im Kontext von my_funky_name auf func .__ name__ zuzugreifen (damit ich seinen Wert abrufen und in my_funky_name verwenden kann)
cowbert
Die Möglichkeit, dies innerhalb der Funktion my_funky_name zu tun, ist my_funky_name.__name__. Sie können den func .__ name__ als neuen Parameter an die Funktion übergeben. func (* args, ** kwargs, my_name = func .__ name__). Um den Namen Ihres Dekorateurs aus Ihrer Funktion herauszuholen, würde dies meiner Meinung nach die Verwendung von inspect erfordern. Aber den Namen der Funktion zu bekommen, die meine Funktion innerhalb meiner laufenden Funktion steuert ... nun, das klingt wie der Beginn eines schönen Mems :)
cad106uk
15

Dies ergibt sich tatsächlich aus den anderen Antworten auf die Frage.

Hier ist meine Einstellung:

import sys

# for current func name, specify 0 or no argument.
# for name of caller of current func, specify 1.
# for name of caller of caller of current func, specify 2. etc.
currentFuncName = lambda n=0: sys._getframe(n + 1).f_code.co_name


def testFunction():
    print "You are in function:", currentFuncName()
    print "This function's caller was:", currentFuncName(1)    


def invokeTest():
    testFunction()


invokeTest()

# end of file

Der wahrscheinliche Vorteil dieser Version gegenüber der Verwendung von inspect.stack () besteht darin, dass sie tausendfach schneller sein sollte [siehe Alex Melihoffs Beitrag und Zeitangaben zur Verwendung von sys._getframe () im Vergleich zur Verwendung von inspect.stack ()].

Gino
quelle
12

print(inspect.stack()[0].function) scheint auch zu funktionieren (Python 3.5).

Pierre Voisin
quelle
11

Hier ist ein zukunftssicherer Ansatz.

Die Kombination der Vorschläge von @ CamHart und @ Yuval mit der akzeptierten Antwort von @ RoshOxymoron hat den Vorteil, dass Folgendes vermieden wird:

  • _hidden und möglicherweise veraltete Methoden
  • Indizierung in den Stack (der in zukünftigen Pythons neu angeordnet werden könnte)

Ich denke, das spielt sich gut mit zukünftigen Python-Versionen (getestet auf 2.7.3 und 3.3.2):

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))
Kochfelder
quelle
11
import sys

def func_name():
    """
    :return: name of caller
    """
    return sys._getframe(1).f_code.co_name

class A(object):
    def __init__(self):
        pass
    def test_class_func_name(self):
        print(func_name())

def test_func_name():
    print(func_name())

Prüfung:

a = A()
a.test_class_func_name()
test_func_name()

Ausgabe:

test_class_func_name
test_func_name
nordborn
quelle
11

Ich bin mir nicht sicher, warum die Leute es kompliziert machen:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))
karthik r
quelle
Vielleicht, weil Sie die private Funktion des sys-Moduls außerhalb davon verwenden. Im Allgemeinen wird es als schlechte Praxis angesehen, nicht wahr?
Tikej
@tikej, die Verwendung stimmt mit den hier angegebenen Best Practices überein: docs.quantifiedcode.com/python-anti-patterns/correctness/…
karthik r
10
import inspect

def whoami():
    return inspect.stack()[1][3]

def whosdaddy():
    return inspect.stack()[2][3]

def foo():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())
    bar()

def bar():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())

foo()
bar()

In der IDE wird der Code ausgegeben

Hallo, ich bin foo, Papa ist

Hallo, ich bin Bar, Papa ist foo

Hallo, ich bin Bar, Papa ist

Lee
quelle
8

Sie können einen Dekorateur verwenden:

def my_function(name=None):
    return name

def get_function_name(function):
    return function(name=function.__name__)

>>> get_function_name(my_function)
'my_function'
Douglas Denhartog
quelle
Wie beantwortet das die Frage des Posters? Können Sie dies um ein Beispiel erweitern, wie der Funktionsname innerhalb der Funktion bekannt ist?
Parvus
@parvus: meine Antwort wie ein Beispiel , das eine Antwort auf OP Frage demonstriert
Douglas Denhartog
Ok, my_function ist die zufällige Benutzerfunktion des OP. Schuld daran ist mein mangelndes Verständnis der Dekorateure. Bei dem die @? Wie funktioniert dies für Funktionen, deren Argumente Sie nicht anpassen möchten? So verstehe ich Ihre Lösung: Wenn ich den Funktionsnamen wissen möchte, muss ich ihn an @get_function_name anhängen und das Argument name hinzufügen, in der Hoffnung, dass er nicht bereits für einen anderen Zweck vorhanden ist. Ich vermisse wahrscheinlich etwas, tut mir leid.
Parvus
Ohne meinen eigenen Python-Kurs in einem Kommentar zu beginnen: 1. Funktionen sind Objekte; 2. Sie können der Funktion ein Namensattribut hinzufügen, den Namen drucken / protokollieren oder eine beliebige Anzahl von Dingen mit dem "Namen" im Dekorateur ausführen. 3. Dekorateure können auf verschiedene Arten angebracht werden (zB @ oder in meinem Beispiel); 4. Dekorateure können @wraps verwenden und / oder selbst Klassen sein; 5. Ich könnte weitermachen, aber viel Spaß beim Programmieren!
Douglas Denhartog
Dies scheint nur ein komplizierter Weg zu sein, um zum __name__Attribut einer Funktion zu gelangen. Die Verwendung erfordert das Wissen über das, was Sie erhalten möchten, was mir in einfachen Fällen, in denen Funktionen nicht im laufenden Betrieb definiert sind, nicht sehr nützlich erscheint.
Avi
3

Ich mache meinen eigenen Ansatz, um Super mit Sicherheit innerhalb eines Mehrfachvererbungsszenarios aufzurufen (ich habe den gesamten Code eingefügt).

def safe_super(_class, _inst):
    """safe super call"""
    try:
        return getattr(super(_class, _inst), _inst.__fname__)
    except:
        return (lambda *x,**kx: None)


def with_name(function):
    def wrap(self, *args, **kwargs):
        self.__fname__ = function.__name__
        return function(self, *args, **kwargs)
return wrap

Beispielnutzung:

class A(object):

    def __init__():
        super(A, self).__init__()

    @with_name
    def test(self):
        print 'called from A\n'
        safe_super(A, self)()

class B(object):

    def __init__():
        super(B, self).__init__()

    @with_name
    def test(self):
        print 'called from B\n'
        safe_super(B, self)()

class C(A, B):

    def __init__():
        super(C, self).__init__()

    @with_name
    def test(self):
        print 'called from C\n'
        safe_super(C, self)()

Testen:

a = C()
a.test()

Ausgabe:

called from C
called from A
called from B

Innerhalb jeder mit @with_name dekorierten Methode haben Sie Zugriff auf self .__ fname__ als aktuellen Funktionsnamen.

Mel Viso Martinez
quelle
3

Ich habe kürzlich versucht, die obigen Antworten zu verwenden, um aus dem Kontext dieser Funktion auf die Dokumentzeichenfolge einer Funktion zuzugreifen, aber da die obigen Fragen nur die Namenszeichenfolge zurückgaben, funktionierte dies nicht.

Zum Glück habe ich eine einfache Lösung gefunden. Wenn Sie wie ich auf die Funktion verweisen möchten, anstatt einfach die Zeichenfolge abzurufen, die den Namen darstellt, können Sie eval () auf die Zeichenfolge des Funktionsnamens anwenden.

import sys
def foo():
    """foo docstring"""
    print(eval(sys._getframe().f_code.co_name).__doc__)
John Forbes
quelle
3

Ich schlage vor, sich nicht auf Stapelelemente zu verlassen. Wenn jemand Ihren Code in verschiedenen Kontexten verwendet (z. B. Python-Interpreter), ändert sich Ihr Stapel und bricht Ihren Index ([0] [3]).

Ich schlage Ihnen so etwas vor:

class MyClass:

    def __init__(self):
        self.function_name = None

    def _Handler(self, **kwargs):
        print('Calling function {} with parameters {}'.format(self.function_name, kwargs))
        self.function_name = None

    def __getattr__(self, attr):
        self.function_name = attr
        return self._Handler


mc = MyClass()
mc.test(FirstParam='my', SecondParam='test')
mc.foobar(OtherParam='foobar')
Genschi
quelle
0

Dies ist mit einem Dekorateur ziemlich einfach zu erreichen.

>>> from functools import wraps

>>> def named(func):
...     @wraps(func)
...     def _(*args, **kwargs):
...         return func(func.__name__, *args, **kwargs)
...     return _
... 

>>> @named
... def my_func(name, something_else):
...     return name, something_else
... 

>>> my_func('hello, world')
('my_func', 'hello, world')
Jeff Laughlin
quelle