¿Cómo obtener el nombre del método de la persona que llama en el método llamado?


188

Python: ¿Cómo obtener el nombre del método de la persona que llama en el método llamado?

Supongamos que tengo 2 métodos:

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

def method2(self):
    ...

Si no quiero hacer ningún cambio para el método1, ¿cómo obtener el nombre de la persona que llama (en este ejemplo, el nombre es método1) en el método2?


3
Si. Ahora solo quiero generar algunas cosas de documentación y solo para pruebas.
zs2020

La tecnología es una cosa, la metodología es otra.
zs2020

Respuestas:


233

inspect.getframeinfo y otras funciones relacionadas en inspectpueden ayudar:

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

esta introspección está destinada a ayudar a la depuración y el desarrollo; No es recomendable confiar en él para fines de funcionalidad de producción.


18
"No es aconsejable confiar en él para fines de funcionalidad de producción". Por qué no?
beltsonata

25
@beltsonata depende de la implementación de CPython, por lo que el código que usa esto se romperá si alguna vez intentas usar PyPy o Jython u otros tiempos de ejecución. eso está bien si solo está desarrollando y depurando localmente pero realmente no es algo que desea en su sistema de producción.
robru

@EugeneKrevenets Más allá de la versión de Python, me encontré con un problema con el que hace que el código se ejecute en una segunda ejecución en varios minutos una vez introducido. es extremadamente ineficiente
Dmitry

¿Por qué esto no se menciona en la documentación de python3? docs.python.org/3/library/inspect.html Al menos pondrían una advertencia si es malo, ¿verdad?

1
Es una pena que esto sea un éxito en el rendimiento. El registro podría ser mucho mejor con algo como esto (o CallerMemberName ).
StingyJack

92

Versión más corta:

import inspect

def f1(): f2()

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

f1()

(con agradecimiento a @Alex y Stefaan Lippen )


Hola, recibo el siguiente error cuando ejecuto esto: Archivo "/usr/lib/python2.7/inspect.py", línea 528, en findource si no en el archivo fuente y archivo [0] + archivo [-1]! = ' <> ': IndexError: índice de cadena fuera de rango ¿Puede proporcionar una sugerencia? Gracias de antemano.
Pooja

Este enfoque me dio un error: KeyError: ' main '
Praxiteles

61

Esto parece funcionar bien:

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

1
Esto parece ser mucho más rápido queinspect.stack
kentwait

Aún así, utiliza un miembro protegido, que generalmente no se recomienda, ya que podría fallar después de que el sysmódulo obtenga una refactorización considerable.
filiprem

29

Se me ocurrió una versión un poco más larga que intenta construir un nombre de método completo que incluya módulo y clase.

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)

Impresionante, esto funcionó bien para mí en mi código de registro, donde se me puede llamar desde muchos lugares diferentes. Muchas gracias.
little_birdie

1
A menos que elimine también stack, todavía pierde marcos debido a referencias circulares como se explica en los documentos de inspección
ankostis

@ankostis, ¿tienes algún código de prueba para probar eso?
anatoly techtonik

1
Difícil de mostrar en un comentario ... Copie y pegue en un editor este código de conducción (escribiendo desde la memoria) e intente ambas versiones de su código: `` `Importar debilref clase C: pasar def matar (): imprimir ('Asesinado' ) def leaking (): caller_name () local_var = C () weakref.finalize (local_var, kill) leaking () print ("Local_var debe haber sido asesinado") `` `Deberías obtener:` `` Killed Local_var debe haber sido asesinado `` `y no:` `` Local_var debe haber sido asesinado `` asesinado ''
ankostis

1
¡Increíble! ¡Eso funcionó cuando otras soluciones fallaron! Estoy usando métodos de clase y expresiones lambda, así que es complicado.
osa

17

Me gustaría utilizar inspect.currentframe().f_back.f_code.co_name. Su uso no ha sido cubierto en ninguna de las respuestas anteriores que son principalmente de uno de los tres tipos:

  • Se utilizan algunas respuestas anteriores, inspect.stackpero se sabe que es demasiado lento .
  • Algunas respuestas anteriores usan sys._getframeuna función privada interna dado su subrayado principal, por lo que se desaconseja implícitamente su uso.
  • Se utiliza una respuesta anterior, inspect.getouterframes(inspect.currentframe(), 2)[1][3]pero no está del todo claro qué [1][3]está accediendo.
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()

Tenga en cuenta que cast(FrameType, frame)se utiliza para satisfacer mypy.


Agradecimientos: comentario previo de 1313e para una respuesta .


10

Un poco de amalgama de las cosas de arriba. Pero aquí está mi oportunidad.

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

Utilizar:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

la salida es

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

2
Esto generará un IndexError si la profundidad de la pila solicitada es mayor que la real. Use modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]para obtener los módulos.
jake77

1

Encontré una manera si vas a cruzar las clases y quieres la clase a la que pertenece el método Y el método. Toma un poco de trabajo de extracción pero hace su punto. Esto funciona en 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$
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.