Obtenha __name__ do módulo de função de chamada em Python

96

Suponha que myapp/foo.pycontém:

def info(msg):
    caller_name = ????
    print '[%s] %s' % (caller_name, msg)

E myapp/bar.pycontém:

import foo
foo.info('Hello') # => [myapp.bar] Hello

Eu quero caller_nameser definido para o __name__atributo do módulo de funções de chamada (que é 'myapp.foo') neste caso. Como isso pode ser feito?

Sridhar Ratnakumar
fonte
Suponha que algum outro script de ponto de entrada invoque bar.py .. e, portanto, caller_namenão pode ser__main__
Sridhar Ratnakumar

Respostas:

123

Confira o módulo de inspeção:

inspect.stack() retornará as informações da pilha.

Dentro de uma função, inspect.stack()[1]retornará a pilha do chamador. A partir daí, você pode obter mais informações sobre o nome da função do chamador, módulo, etc.

Consulte a documentação para obter detalhes:

http://docs.python.org/library/inspect.html

Além disso, Doug Hellmann tem uma boa descrição do módulo de inspeção em sua série PyMOTW:

http://pymotw.com/2/inspect/index.html#module-inspect

EDIT: Aqui está um código que faz o que você quer, eu acho:

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)
ars
fonte
1
Então, como você obtém o __name__atributo deste módulo usando o inspectmódulo? Por exemplo, como faço para voltar myapp.foo(não myapp/foo.py) no meu exemplo acima? Já tentei usar o módulo inspect antes de postar no SO.
Sridhar Ratnakumar
6
Esteja ciente de que isso irá interagir estranhamente com os ganchos de importação, não funcionará no ironpython e pode se comportar de maneiras surpreendentes no jython. É melhor se você puder evitar magia como essa.
Glyph
2
Observe também que manter uma referência a um quadro de pilha pode impedir que o GC do Python funcione corretamente. Veja o aviso aqui: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel
6
Observe que se a função do chamador estiver decorada (@ ...), você precisa acessar inspect.stack()[2]para o chamador real.
Amir Ali Akbari
Observe também que essa lógica não funciona corretamente quando você compila seu código python em um exe usando o pyinstaller.
panofish
19

Confrontado com um problema semelhante, descobri que sys._current_frames () do módulo sys contém informações interessantes que podem ajudá-lo, sem a necessidade de importar inspecionar, pelo menos em casos de uso específicos.

>>> sys._current_frames()
{4052: <frame object at 0x03200C98>}

Você pode então "subir" usando f_back:

>>> f = sys._current_frames().values()[0]
>>> # for python3: f = list(sys._current_frames().values())[0]

>>> print f.f_back.f_globals['__file__']
'/base/data/home/apps/apricot/1.6456165165151/caller.py'

>>> print f.f_back.f_globals['__name__']
'__main__'

Para o nome do arquivo, você também pode usar f.f_back.f_code.co_filename, conforme sugerido por Mark Roddy acima. Não tenho certeza dos limites e advertências desse método (vários threads provavelmente serão um problema), mas pretendo usá-lo no meu caso.

Louis LC
fonte
2
nota: o código inspect.stack FALHA após compilar para exe usando pyinstaller, mas usando sys._current_frames FUNCIONA BEM ... então esta é a técnica preferida para mim.
panofish
7
Acho que é mais fácil obter o quadro anterior sys._getframe(1), em vez de chamar sys._current_frames()(btw que retorna um mapeamento de quadro para cada thread).
hooblei
Obrigado hooblei, eu não testei ainda, mas parece muito útil para situações multi-threaded.
Louis LC
Eu prefiro usar em inspect.currentframe()vez de sys._current_frames().values()[0].
Aran-Fey
3

Não recomendo fazer isso, mas você pode alcançar seu objetivo com o seguinte método:

def caller_name():
    frame=inspect.currentframe()
    frame=frame.f_back.f_back
    code=frame.f_code
    return code.co_filename

Em seguida, atualize seu método existente da seguinte maneira:

def info(msg):
    caller = caller_name()
    print '[%s] %s' % (caller, msg)
Mark Roddy
fonte
7
O nome do arquivo não é o mesmo que__name__
Sridhar Ratnakumar