Comment obtenir le nom de la méthode de l'appelant dans la méthode appelée?


199

Python: Comment obtenir le nom de la méthode de l'appelant dans la méthode appelée?

Supposons que j'ai 2 méthodes:

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

def method2(self):
    ...

Si je ne veux pas faire de changement pour method1, comment obtenir le nom de l'appelant (dans cet exemple, le nom est method1) dans method2?


Réponses:


245

inspect.getframeinfo et d'autres fonctions associées dans inspectpeuvent aider:

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

cette introspection est destinée à aider au débogage et au développement; il n'est pas conseillé de s'y fier à des fins de fonctionnalité de production.


18
"il n'est pas conseillé de s'y fier à des fins de fonctionnalité de production." Pourquoi pas?
beltsonata

27
@beltsonata dépend de l'implémentation CPython, donc le code qui l'utilise sera cassé si vous essayez d'utiliser PyPy ou Jython ou d'autres environnements d'exécution. c'est bien si vous ne faites que développer et déboguer localement mais pas vraiment quelque chose que vous voulez dans votre système de production.
robru

@EugeneKrevenets Au-delà de la version python, je viens de rencontrer un problème avec celui-ci où il crée du code qui s'exécute sous une deuxième exécution en plusieurs minutes une fois introduit. it grossly inefficace
Dmitry

Pourquoi cela n'est pas mentionné dans la documentation python3? docs.python.org/3/library/inspect.html Ils mettraient au moins un avertissement si c'est mauvais non?

1
C'est dommage que ce soit un tel succès sur les performances. La journalisation pourrait être tellement plus agréable avec quelque chose comme ça (ou CallerMemberName ).
StingyJack

97

Version plus courte:

import inspect

def f1(): f2()

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

f1()

(avec merci à @Alex et Stefaan Lippen )


Bonjour, j'obtiens l'erreur ci-dessous lorsque j'exécute ceci: Fichier "/usr/lib/python2.7/inspect.py", ligne 528, dans findource sinon sourcefile et fichier [0] + fichier [-1]! = ' <> ': IndexError: string index out of range Pouvez-vous fournir une suggestion. Merci d'avance.
Pooja

Cette approche m'a donné une erreur: KeyError: ' main '
Praxiteles

66

Cela semble fonctionner très bien:

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

3
Cela semble être beaucoup plus rapide queinspect.stack
kentwait

Pourtant, il utilise un membre protégé, ce qui n'est généralement pas conseillé, car il pourrait échouer après que le sysmodule subisse une refactorisation lourde.
filiprem le

1
Point valide. Merci de l'avoir présenté ici comme un écueil potentiel à long terme de cette solution.
Augiwan

1
Fonctionne également avec pyinstaller. Court et doux. Je l'utilise avec co_filename (au lieu de co_name) pour savoir à partir de quel programme principal il est appelé ou pour savoir s'il est appelé directement à des fins de test.
Le Droid

30

J'ai proposé une version légèrement plus longue qui tente de créer un nom de méthode complet, y compris le module et la classe.

https://gist.github.com/2151727 (rév 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)

Génial, cela a bien fonctionné pour moi dans mon code de journalisation, où je peux être appelé de nombreux endroits différents. Merci beaucoup.
little_birdie

1
À moins que vous ne supprimiez également stack, il y a toujours des fuites de cadres en raison de références circulaires, comme expliqué dans
inspect

@ankostis avez-vous du code de test pour le prouver?
anatoly techtonik

1
Difficile à afficher dans un commentaire ... Copiez-collez dans un éditeur ce code de pilotage (en tapant de mémoire) et essayez les deux versions de votre code: `` import lowref class C: pass def kill (): print ('Killed' ) def leaking (): caller_name () local_var = C () lowref.finalize (local_var, kill) leaking () print ("Local_var must have been kill") `` `Vous devriez obtenir:` `` `Killed Local_var must have been tué `` '' et non: `` Local_var doit avoir été tué Tué ''
ankostis

1
Impressionnant! Cela a fonctionné lorsque d'autres solutions ont échoué! J'utilise des méthodes de classe et des expressions lambda, donc c'est délicat.
Sergey Orshanskiy

21

J'utiliserais inspect.currentframe().f_back.f_code.co_name. Son utilisation n'a été couverte dans aucune des réponses précédentes qui sont principalement de l'un des trois types suivants:

  • Certaines réponses antérieures utilisent inspect.stackmais on sait que c'est trop lent .
  • Certaines réponses antérieures utilisent sys._getframequi est une fonction privée interne étant donné son trait de soulignement principal, et donc son utilisation est implicitement déconseillée.
  • Une réponse précédente utilise, inspect.getouterframes(inspect.currentframe(), 2)[1][3]mais on ne sait pas du tout à quoi [1][3]accéder.
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()

Notez qu'il cast(FrameType, frame)est utilisé pour satisfaire mypy.


Remerciements: commentaire préalable de 1313e pour une réponse .


10

Un peu de fusion des choses ci-dessus. Mais voici ma fissure.

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

Utilisation:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

la sortie est

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

2
Cela lèvera une IndexError si la profondeur de pile requeted est supérieure à la réelle. Utilisez modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]pour obtenir les modules.
jake77

1

J'ai trouvé un moyen si vous traversez des classes et que vous voulez la classe à laquelle appartient la méthode ET la méthode. Cela demande un peu de travail d'extraction mais cela fait valoir son point de vue. Cela fonctionne dans 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$
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.