Wie erhalte ich den Methodennamen des Aufrufers in der aufgerufenen Methode?

182

Python: Wie erhalte ich den Methodennamen des Aufrufers in der aufgerufenen Methode?

Angenommen, ich habe 2 Methoden:

def method1(self):
    ...
    a = A.method2()

def method2(self):
    ...

Wenn ich für Methode1 keine Änderung vornehmen möchte, wie erhalte ich den Namen des Aufrufers (in diesem Beispiel lautet der Name Methode1) in Methode2?

zs2020
quelle
3
Ja. Jetzt möchte ich nur noch Dokumentationsmaterial generieren und nur zum Testen.
zs2020
Technologie ist eine Sache, die Methodik ist eine andere.
zs2020

Antworten:

228

inspect.getframeinfo und andere verwandte Funktionen in inspectkönnen helfen:

>>> import inspect
>>> def f1(): f2()
... 
>>> def f2():
...   curframe = inspect.currentframe()
...   calframe = inspect.getouterframes(curframe, 2)
...   print('caller name:', calframe[1][3])
... 
>>> f1()
caller name: f1

Diese Selbstbeobachtung soll das Debuggen und die Entwicklung unterstützen. Es ist nicht ratsam, sich aus Gründen der Produktionsfunktionalität darauf zu verlassen.

Alex Martelli
quelle
18
"Es ist nicht ratsam, sich für produktionsfunktionale Zwecke darauf zu verlassen." Warum nicht?
Beltsonata
25
@beltsonata hängt von der CPython-Implementierung ab, sodass der Code, der dies verwendet, unterbrochen wird, wenn Sie jemals versuchen, PyPy oder Jython oder andere Laufzeiten zu verwenden. Das ist in Ordnung, wenn Sie nur lokal entwickeln und debuggen, aber nicht wirklich etwas, das Sie in Ihrem Produktionssystem wollen.
Robru
@EugeneKrevenets Abgesehen von der Python-Version bin ich auf ein Problem gestoßen, bei dem Code erstellt wird, der nach seiner Einführung in mehreren Minuten unter einem zweiten Lauf ausgeführt wird. es ist grob ineffizient
Dmitry
Warum wird dies in der Python3-Dokumentation nicht erwähnt? docs.python.org/3/library/inspect.html Sie würden zumindest eine Warnung ausgeben, wenn es schlecht ist, oder?
1
Es ist eine Schande, dass dies so ein Hit für die Leistung ist. Die Protokollierung könnte mit so etwas (oder CallerMemberName ) viel besser sein .
StingyJack
92

Kürzere Version:

import inspect

def f1(): f2()

def f2():
    print 'caller name:', inspect.stack()[1][3]

f1()

(danke an @Alex und Stefaan Lippen )

Todd Owen
quelle
Hallo, ich erhalte folgende Fehlermeldung, wenn ich Folgendes ausführe: Datei "/usr/lib/python2.7/inspect.py", Zeile 528, in findource, wenn nicht Quelldatei und Datei [0] + Datei [-1]! = ' <> ': IndexError: Zeichenfolgenindex außerhalb des Bereichs Können Sie bitte einen Vorschlag machen? Vielen Dank im Voraus.
Pooja
Dieser Ansatz gab mir einen Fehler: KeyError: ' main '
Praxiteles
60

Dies scheint gut zu funktionieren:

import sys
print sys._getframe().f_back.f_code.co_name
Augiwan
quelle
1
Dies scheint viel schneller zu sein alsinspect.stack
Kentwait
Es wird jedoch ein geschütztes Element verwendet, was im Allgemeinen nicht empfohlen wird, da es nach syseinem umfangreichen Refactoring des Moduls fehlschlagen kann .
Filiprem
28

Ich habe mir eine etwas längere Version ausgedacht, die versucht, einen vollständigen Methodennamen einschließlich Modul und Klasse zu erstellen.

https://gist.github.com/2151727 (rev 9cccbf)

# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2

import inspect

def caller_name(skip=2):
    """Get a name of a caller in the format module.class.method

       `skip` specifies how many levels of stack to skip while getting caller
       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.

       An empty string is returned if skipped levels exceed stack height
    """
    stack = inspect.stack()
    start = 0 + skip
    if len(stack) < start + 1:
      return ''
    parentframe = stack[start][0]    

    name = []
    module = inspect.getmodule(parentframe)
    # `modname` can be None when frame is executed directly in console
    # TODO(techtonik): consider using __main__
    if module:
        name.append(module.__name__)
    # detect classname
    if 'self' in parentframe.f_locals:
        # I don't know any way to detect call from the object method
        # XXX: there seems to be no way to detect static method call - it will
        #      be just a function call
        name.append(parentframe.f_locals['self'].__class__.__name__)
    codename = parentframe.f_code.co_name
    if codename != '<module>':  # top level usually
        name.append( codename ) # function or a method

    ## Avoid circular refs and frame leaks
    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack
    del parentframe, stack

    return ".".join(name)
anatoly techtonik
quelle
Genial, das hat in meinem Protokollcode gut funktioniert, wo ich von vielen verschiedenen Orten aus angerufen werden kann. Vielen Dank.
little_birdie
1
Wenn Sie nicht auch löschen stack, leckt es immer noch Frames aufgrund von kreisförmigen Verweisen, wie in den
inspect
@ankostis hast du einen Testcode, um das zu beweisen?
Anatoly Techtonik
1
In einem Kommentar schwer anzuzeigen ... Kopieren Sie diesen Fahrcode in einen Editor (geben Sie ihn aus dem Speicher ein) und probieren Sie beide Versionen Ihres Codes aus: `` `Schwachstellenklasse C importieren: pass def kill (): print ('Killed') ) def leaking (): caller_name () local_var = C () schwachref.finalize (local_var, kill) undicht () print ("Local_var muss getötet worden sein") `` `Sie sollten erhalten:` `` Getötet Local_var muss gewesen sein getötet `` `und nicht:` `` Local_var muss getötet worden sein Getötet `` `
ankostis
1
Genial! Das hat funktioniert, als andere Lösungen fehlgeschlagen sind! Ich benutze Klassenmethoden und Lambda-Ausdrücke, also ist es schwierig.
osa
16

Ich würde verwenden inspect.currentframe().f_back.f_code.co_name. Seine Verwendung wurde in keiner der vorherigen Antworten behandelt, die hauptsächlich von einer von drei Arten sind:

  • Einige frühere Antworten verwenden, inspect.stackaber es ist bekannt, dass es zu langsam ist .
  • Bei einigen früheren Antworten sys._getframehandelt es sich um eine interne private Funktion, die aufgrund ihres führenden Unterstrichs eine interne Funktion darstellt. Daher wird von ihrer Verwendung implizit abgeraten.
  • Eine vorherige Antwort verwendet, inspect.getouterframes(inspect.currentframe(), 2)[1][3]aber es ist völlig unklar, auf was [1][3]zugegriffen wird.
import inspect
from types import FrameType
from typing import cast


def caller_name() -> str:
    """Return the calling function's name."""
    # Ref: https://stackoverflow.com/a/57712700/
    return cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name


if __name__ == '__main__':
    def _test_caller_name() -> None:
        assert caller_name() == '_test_caller_name'
    _test_caller_name()

Beachten Sie, dass cast(FrameType, frame)verwendet wird, um zu befriedigen mypy.


Danksagung: vorheriger Kommentar von 1313e für eine Antwort .

Scharfsinn
quelle
10

Eine Art Zusammenschluss der oben genannten Dinge. Aber hier ist mein Knaller.

def print_caller_name(stack_size=3):
    def wrapper(fn):
        def inner(*args, **kwargs):
            import inspect
            stack = inspect.stack()

            modules = [(index, inspect.getmodule(stack[index][0]))
                       for index in reversed(range(1, stack_size))]
            module_name_lengths = [len(module.__name__)
                                   for _, module in modules]

            s = '{index:>5} : {module:^%i} : {name}' % (max(module_name_lengths) + 4)
            callers = ['',
                       s.format(index='level', module='module', name='name'),
                       '-' * 50]

            for index, module in modules:
                callers.append(s.format(index=index,
                                        module=module.__name__,
                                        name=stack[index][3]))

            callers.append(s.format(index=0,
                                    module=fn.__module__,
                                    name=fn.__name__))
            callers.append('')
            print('\n'.join(callers))

            fn(*args, **kwargs)
        return inner
    return wrapper

Verwenden:

@print_caller_name(4)
def foo():
    return 'foobar'

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

Ausgabe ist

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo
migpok35
quelle
2
Dies löst einen IndexError aus, wenn die erforderliche Stapeltiefe größer als die tatsächliche ist. Verwenden Sie modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))], um die Module zu erhalten.
Jake77
1

Ich habe einen Weg gefunden, wenn Sie über Klassen gehen und die Klasse wollen, zu der die Methode gehört, UND die Methode. Es erfordert ein wenig Extraktionsarbeit, aber es macht seinen Sinn. Dies funktioniert in Python 2.7.13.

import inspect, os

class ClassOne:
    def method1(self):
        classtwoObj.method2()

class ClassTwo:
    def method2(self):
        curframe = inspect.currentframe()
        calframe = inspect.getouterframes(curframe, 4)
        print '\nI was called from', calframe[1][3], \
        'in', calframe[1][4][0][6: -2]

# create objects to access class methods
classoneObj = ClassOne()
classtwoObj = ClassTwo()

# start the program
os.system('cls')
classoneObj.method1()
Michael Swartz
quelle
0
#!/usr/bin/env python
import inspect

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

def caller1():
    print "inside: ",called()

def caller2():
    print "inside: ",called()

if __name__=='__main__':
    caller1()
    caller2()
shahid@shahid-VirtualBox:~/Documents$ python test_func.py 
inside:  caller1
inside:  caller2
shahid@shahid-VirtualBox:~/Documents$
Mohammad Shahid Siddiqui
quelle