Jak uzyskać nazwę metody wywołującej w wywoływanej metodzie?

186

Python: Jak uzyskać nazwę metody wywołującej w wywoływanej metodzie?

Załóżmy, że mam 2 metody:

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

def method2(self):
    ...

Jeśli nie chcę wprowadzać żadnych zmian dla metody 1, jak uzyskać nazwę osoby dzwoniącej (w tym przykładzie nazwa to metoda 1) w metodzie 2?

zs2020
źródło
3
Tak. Teraz chcę tylko wygenerować trochę dokumentacji i tylko do testów.
zs2020
Technologia to jedno, metodologia to drugie.
zs2020

Odpowiedzi:

233

inspect.getframeinfo i inne powiązane funkcje inspectmogą pomóc:

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

ta introspekcja ma na celu pomóc w debugowaniu i rozwoju; nie jest wskazane poleganie na nim do celów związanych z produkcją.

Alex Martelli
źródło
18
„nie zaleca się polegania na nim w celach związanych z produkcją”. Dlaczego nie?
beltsonata,
25
@beltsonata zależy od implementacji CPython, więc kod korzystający z tego kodu ulegnie awarii, jeśli kiedykolwiek spróbujesz użyć PyPy, Jython lub innych środowisk wykonawczych. to dobrze, jeśli tylko tworzysz i debugujesz lokalnie, ale tak naprawdę nie jest to coś, czego chcesz w systemie produkcyjnym.
robru
@EugeneKrevenets Poza wersją python, po prostu natknąłem się na problem z tym, że po wprowadzeniu kod uruchamia się pod drugim uruchomieniem w ciągu kilku minut. to rażąco nieefektywne
Dmitry
Dlaczego nie wspomniano o tym w dokumentacji Python3? docs.python.org/3/library/inspect.html Przynajmniej ostrzegaliby, jeśli jest źle, prawda?
1
Szkoda, że ​​to taki hit w wydajności. Rejestrowanie może być o wiele przyjemniejsze z czymś takim (lub CallerMemberName ).
StingyJack
92

Krótsza wersja:

import inspect

def f1(): f2()

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

f1()

(dzięki dzięki @Alex i Stefaan Lippen )

Todd Owen
źródło
Cześć, dostaję poniżej błędu, gdy uruchamiam to: Plik "/usr/lib/python2.7/inspect.py", wiersz 528, w findource, jeśli nie plik źródłowy i plik [0] + plik [-1]! = ' <> ': IndexError: indeks ciągu poza zakresem Czy możesz podać sugestię. Z góry dziękuję.
Pooja,
To podejście dało mi błąd: KeyError: ' main '
Praxiteles
61

To wydaje się działać dobrze:

import sys
print sys._getframe().f_back.f_code.co_name
Augiwan
źródło
1
Wydaje się, że jest to znacznie szybsze niżinspect.stack
kentwait
Mimo to używa chronionego elementu, co na ogół nie jest zalecane, ponieważ może się nie powieść po tym, jak sysmoduł ulegnie ciężkiej refaktoryzacji.
filiprem
28

Wymyśliłem nieco dłuższą wersję, która próbuje zbudować pełną nazwę metody, w tym moduł i klasę.

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
źródło
Świetnie, działało to dla mnie dobrze w moim kodzie logowania, do którego można dzwonić z wielu różnych miejsc. Dziękuję bardzo.
little_birdie
1
O ile nie usuniesz również stack, nadal przecieka ramki z powodu cyrkularnych odniesień, jak wyjaśniono w inspect-docs
ankostis
@ankostis czy masz jakiś kod testowy, aby to udowodnić?
anatoly techtonik
1
Trudno pokazać w komentarzu ... Skopiuj i wklej w edytorze ten kod sterujący (pisząc z pamięci) i wypróbuj obie wersje kodu: `` importuj słabą klasę C: pass def kill (): print ('Killed' ) def leaking (): caller_name () local_var = C () slaveref.finalize (local_var, kill) leaking () print („Local_var musiał zostać zabity”) `` Powinieneś dostać: `` Killed Local_var musiał być zabity `` i nie: `` Local_var musiał zostać zabity Zabity ''
ankostis
1
Niesamowite! Działało, gdy zawiodły inne rozwiązania! Używam metod klasowych i wyrażeń lambda, więc jest to trudne.
osa
17

Chciałbym użyć inspect.currentframe().f_back.f_code.co_name. Jego zastosowanie nie zostało uwzględnione w żadnej z wcześniejszych odpowiedzi, które są głównie jednego z trzech rodzajów:

  • Niektóre wcześniejsze odpowiedzi są używane, inspect.stackale wiadomo, że są zbyt wolne .
  • Niektóre wcześniejsze odpowiedzi wykorzystują, sys._getframektóra jest wewnętrzną funkcją prywatną, biorąc pod uwagę jej wiodący znak podkreślenia, więc jej użycie jest pośrednio odradzane.
  • Używa się jednej wcześniejszej odpowiedzi, inspect.getouterframes(inspect.currentframe(), 2)[1][3]ale nie jest całkowicie jasne, do czego [1][3]można uzyskać dostęp.
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()

Uwaga: cast(FrameType, frame)służy do spełnienia mypy.


Podziękowania: wcześniejszy komentarz 1313e w celu uzyskania odpowiedzi .

Acumenus
źródło
10

Trochę połączenia powyższych rzeczy. Ale oto mój problem.

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

Posługiwać się:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

wyjście jest

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo
migpok35
źródło
2
Spowoduje to podniesienie błędu indeksu, jeśli wymagana głębokość stosu jest większa niż rzeczywista. Użyj, modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]aby uzyskać moduły.
jake77
1

Znalazłem sposób, jeśli przechodzisz przez klasy i chcesz klasy, do której należy metoda ORAZ metoda. Wymaga trochę pracy przy wydobywaniu, ale ma sens. Działa to w Pythonie 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
źródło
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
źródło