Como obter o nome do método do chamador no método chamado?

187

Python: Como obter o nome do método do chamador no método chamado?

Suponha que eu tenha 2 métodos:

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

def method2(self):
    ...

Se eu não quiser fazer nenhuma alteração no método1, como obter o nome do chamador (neste exemplo, o nome é método1) no método2?

zs2020
fonte
3
Sim. Agora eu só quero gerar algumas documentações e apenas para testes.
zs2020
A tecnologia é uma coisa, a metodologia é outra.
zs2020

Respostas:

233

O inspect.getframeinfo e outras funções relacionadas no inspectpodem ajudar:

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

essa introspecção visa ajudar a depuração e desenvolvimento; não é aconselhável confiar nele para fins de funcionalidade de produção.

Alex Martelli
fonte
18
"não é aconselhável confiar nele para fins de funcionalidade de produção". Por que não?
beltsonata
25
@beltsonata é dependente da implementação do CPython, portanto, o código que usar isso será interrompido se você tentar usar o PyPy ou Jython ou outros tempos de execução. tudo bem se você estiver apenas desenvolvendo e depurando localmente, mas não for realmente o que deseja em seu sistema de produção.
robru
@EugeneKrevenets Além da versão python, acabei de encontrar um problema com ele, onde ele cria código que é executado sob uma segunda execução em vários minutos, uma vez introduzido. -lo extremamente ineficiente
Dmitry
Por que isso não é mencionado na documentação do python3? docs.python.org/3/library/inspect.html Eles pelo menos colocariam um aviso se estiver ruim, certo?
1
É uma pena que este seja um sucesso no desempenho. O registro pode ser muito melhor com algo assim (ou CallerMemberName ).
StingyJack
92

Versão mais curta:

import inspect

def f1(): f2()

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

f1()

(com agradecimentos a @Alex e Stefaan Lippen )

Todd Owen
fonte
Olá, estou recebendo o erro abaixo quando executo este: Arquivo "/usr/lib/python2.7/inspect.py", linha 528, no findsource se não for sourcefile e file [0] + file [-1]! = ' <> ': IndexError: índice da string fora do intervalo Você pode fornecer sugestões. Thanx antecipadamente.
Pooja
Essa abordagem me deu um erro: KeyError: ' main '
Praxiteles
61

Isso parece funcionar muito bem:

import sys
print sys._getframe().f_back.f_code.co_name
Augiwan
fonte
1
Isso parece ser muito mais rápido do queinspect.stack
kentwait 23/02/19
Ainda assim, ele usa um membro protegido, o que geralmente não é recomendado, porque pode falhar após o sysmódulo receber uma refatoração pesada.
filiprem 17/04
29

Eu vim com uma versão um pouco mais longa que tenta criar um nome de método completo, incluindo módulo 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)
anatoly techtonik
fonte
Impressionante, isso funcionou bem para mim no meu código de registro, onde posso ser chamado de muitos lugares diferentes. Muito obrigado.
22415
1
Se não eliminar também stack, ainda vazamentos quadros devido a refs cirular como explicado na inspecionar-docs
ankostis
@ankostis você tem algum código de teste para provar isso?
Anatoly techtonik
1
Difícil de mostrar em um comentário ... Copie e cole em um editor este código de condução (digitando na memória) e tente as duas versões do seu código: `` `` import fraca classeref C: pass def kill (): print ('Killed' ) def leaking (): caller_name () local_var = C () weakref.finalize (local_var, kill) leaking () print ("Local_var deve ter sido morto") `` `Você deve obter:` `` Local_var morto deve ter sido matou `` `e não:` `` Local_var deve ter sido morto Killed `` `
ankostis
1
Impressionante! Isso funcionou quando outras soluções falharam! Eu estou usando métodos de classe e expressões lambda, por isso é complicado.
Osa
17

Eu usaria inspect.currentframe().f_back.f_code.co_name. Seu uso não foi abordado em nenhuma das respostas anteriores, principalmente de um dos três tipos:

  • Algumas respostas anteriores são usadas, inspect.stackmas sabe-se que são muito lentas .
  • Algumas respostas anteriores usam sys._getframe uma função privada interna, dado seu sublinhado principal e, portanto, seu uso é implicitamente desencorajado.
  • Uma resposta anterior usa, inspect.getouterframes(inspect.currentframe(), 2)[1][3]mas não está totalmente claro o que [1][3]está acessando.
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()

Observe que cast(FrameType, frame)é usado para satisfazer mypy.


Agradecimento: comentário anterior de 1313e para obter uma resposta .

Acumenus
fonte
10

Um pouco de uma amálgama das coisas acima. Mas aqui está a minha rachadura.

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

Usar:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

saída é

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo
migpok35
fonte
2
Isso aumentará um IndexError se a profundidade da pilha requerida for maior que a real. Use modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]para obter os módulos.
jake77
1

Eu encontrei uma maneira, se você estiver passando por classes e quiser a classe à qual o método pertence AND. É preciso um pouco de trabalho de extração, mas faz sentido. Isso funciona no 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()
Michael Swartz
fonte
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
fonte