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.