Come ottenere il nome del metodo del chiamante nel metodo chiamato?


188

Python: come ottenere il nome del metodo del chiamante nel metodo chiamato?

Supponiamo di avere 2 metodi:

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

def method2(self):
    ...

Se non voglio fare alcuna modifica per method1, come ottenere il nome del chiamante (in questo esempio, il nome è method1) in method2?


3
Sì. Ora voglio solo generare alcuni documenti e solo per i test.
zs2020,

La tecnologia è una cosa, la metodologia è un'altra.
zs2020

Risposte:


233

inspect.getframeinfo e altre funzioni correlate ininspect possono aiutare:

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

questa introspezione ha lo scopo di aiutare il debug e lo sviluppo; non è consigliabile fare affidamento su di esso per scopi di funzionalità di produzione.


18
"non è consigliabile fare affidamento su di esso per scopi di funzionalità di produzione". Perchè no?
beltsonata,

25
@beltsonata dipende dall'implementazione di CPython, quindi l'utilizzo del codice si interromperà se si tenta di utilizzare PyPy o Jython o altri runtime. va bene se stai solo sviluppando e eseguendo il debug a livello locale, ma non davvero qualcosa che desideri nel tuo sistema di produzione.
Robru,

@EugeneKrevenets Oltre alla sola versione di Python, ho appena riscontrato un problema in cui si creava codice che gira sotto una seconda esecuzione in più minuti una volta introdotto. è gravemente inefficiente
Dmitry

Perché questo non è menzionato nella documentazione di python3? docs.python.org/3/library/inspect.html Almeno metterebbero un avvertimento se è male vero?

1
È un peccato che questo sia un tale successo in termini di prestazioni. La registrazione potrebbe essere molto più piacevole con qualcosa di simile (o CallerMemberName ).
StingyJack,

92

Versione più corta:

import inspect

def f1(): f2()

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

f1()

(grazie a @Alex e Stefaan Lippen )


Ciao, visualizzo l'errore sotto quando eseguo questo: File "/usr/lib/python2.7/inspect.py", riga 528, in findource se non sourcefile e file [0] + file [-1]! = ' <> ': IndexError: indice di stringa non compreso nell'intervallo È possibile fornire suggerimenti. Grazie in anticipo.
Pooja,

Questo approccio mi ha dato un errore: KeyError: ' main '
Praxiteles

61

Questo sembra funzionare bene:

import sys
print sys._getframe().f_back.f_code.co_name

1
Questo sembra essere molto più veloce diinspect.stack
kentwait

Tuttavia utilizza un membro protetto, che in genere non è consigliato, poiché potrebbe non funzionare dopo che il sysmodulo ha ottenuto un pesante refactoring.
filiprem,

29

Ho escogitato una versione leggermente più lunga che tenta di creare un nome di metodo completo che includa modulo e classe.

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)

Fantastico, questo ha funzionato bene per me nel mio codice di registrazione, dove posso essere chiamato da molti posti diversi. Grazie mille.
little_birdie,

1
A meno che non si elimini anche stack, perde comunque i frame a causa dei riferimenti circolari come spiegato in
inspect

@ankostis hai qualche codice di prova per dimostrarlo?
Anatoly Techtonik,

1
Difficile mostrare in un commento ... Copia e incolla in un editor questo codice guida (digitando dalla memoria) e prova entrambe le versioni del tuo codice: `` importa classe debref C: passa def kill (): print ('Killed' ) def leaking (): caller_name () local_var = C () weakref.finalize (local_var, kill) leaking () print ("Local_var deve essere stato ucciso") `` `Dovresti ottenere:` `` Killed Local_var deve essere stato ucciso `` `e non:` `` Local_var deve essere stato ucciso ucciso `` `
ankostis

1
Eccezionale! Funzionava quando altre soluzioni fallivano! Sto usando metodi di classe ed espressioni lambda, quindi è complicato.
osa,

17

Vorrei usare inspect.currentframe().f_back.f_code.co_name. Il suo uso non è stato trattato in nessuna delle risposte precedenti che sono principalmente di uno dei tre tipi:

  • Alcune risposte precedenti usano inspect.stackma è noto per essere troppo lento .
  • Alcune risposte precedenti usano sys._getframeche è una funzione privata interna dato il suo carattere di sottolineatura principale, e quindi il suo uso è implicitamente scoraggiato.
  • Viene utilizzata una risposta precedente, inspect.getouterframes(inspect.currentframe(), 2)[1][3]ma non è del tutto chiaro a cosa [1][3]acceda.
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()

Si noti che cast(FrameType, frame)viene utilizzato per soddisfare mypy.


Ringraziamento: precedente commento di 1313e per una risposta .


10

Un po 'di una fusione delle cose sopra. Ma ecco il mio crack.

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

Uso:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

l'uscita è

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo

2
Ciò genererà un IndexError se la profondità dello stack richiesta è maggiore di quella effettiva. Usomodules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))] per ottenere i moduli.
jake77,

1

Ho trovato un modo se stai andando attraverso le classi e vuoi la classe a cui appartiene il metodo E il metodo. Ci vuole un po 'di lavoro di estrazione ma fa il punto. Funziona 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$
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.