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?


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.


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 )


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

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)

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 .


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

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()

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$
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.