Obtenez __name__ du module de la fonction appelante en Python

96

Supposons qu'il myapp/foo.pycontienne:

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

Et myapp/bar.pycontient:

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

Je veux caller_nameêtre défini sur l' __name__attribut du module des fonctions appelantes (qui est «myapp.foo») dans ce cas. Comment cela peut-il être fait?

Sridhar Ratnakumar
la source
Supposons qu'un autre script de point d'entrée appelle bar.py .. et caller_namene peut donc pas l'être__main__
Sridhar Ratnakumar

Réponses:

123

Découvrez le module d'inspection:

inspect.stack() renverra les informations de pile.

À l'intérieur d'une fonction, inspect.stack()[1]retournera la pile de votre appelant. De là, vous pouvez obtenir plus d'informations sur le nom de la fonction de l'appelant, le module, etc.

Consultez la documentation pour plus de détails:

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

De plus, Doug Hellmann a une belle description du module d'inspection de sa série PyMOTW:

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

EDIT: Voici un code qui fait ce que vous voulez, je pense:

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)
ars
la source
1
Alors, comment obtenez-vous l' __name__attribut de ce module en utilisant le inspectmodule? Par exemple, comment puis-je revenir myapp.foo(pas myapp/foo.py) dans mon exemple ci-dessus? J'ai déjà essayé d'utiliser le module inspect avant de poster chez SO.
Sridhar Ratnakumar
6
Sachez que cela interagira étrangement avec les hooks d'importation, ne fonctionnera pas sur ironpython et peut se comporter de manière surprenante sur jython. Il vaut mieux éviter la magie comme celle-ci.
Glyph le
2
Notez également que conserver une référence à un cadre de pile peut empêcher le GC de Python de fonctionner correctement. Voir l'avertissement ici: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel
6
Notez que si la fonction appelant est décorée (@ ...), vous devez y accéder inspect.stack()[2]pour l'appelant réel.
Amir Ali Akbari
Notez également que cette logique ne fonctionne pas correctement lorsque vous compilez votre code python dans un exe à l'aide de pyinstaller.
panofish
19

Confronté à un problème similaire, j'ai trouvé que sys._current_frames () du module sys contient des informations intéressantes qui peuvent vous aider, sans avoir besoin d'importer inspect, du moins dans des cas d'utilisation spécifiques.

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

Vous pouvez ensuite "monter" en utilisant 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__'

Pour le nom de fichier, vous pouvez également utiliser f.f_back.f_code.co_filename, comme suggéré par Mark Roddy ci-dessus. Je ne suis pas sûr des limites et des mises en garde de cette méthode (plusieurs threads seront probablement un problème) mais j'ai l'intention de l'utiliser dans mon cas.

Louis LC
la source
2
note: le code inspect.stack ECHEC après la compilation en exe en utilisant pyinstaller, mais en utilisant sys._current_frames FONCTIONNE BIEN ... c'est donc la technique préférée pour moi.
panofish
7
Je pense qu'il est plus facile d'obtenir la trame précédente sys._getframe(1), au lieu d'appeler sys._current_frames()(btw qui renvoie un mappage de trame pour chaque thread).
hooblei
Merci hooblei, je ne l'ai pas encore testé mais semble très utile pour les situations multi-threadées.
Louis LC
Je préfère utiliser inspect.currentframe()au lieu de sys._current_frames().values()[0].
Aran-Fey
3

Je ne recommande pas de faire cela, mais vous pouvez atteindre votre objectif avec la méthode suivante:

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

Ensuite, mettez à jour votre méthode existante comme suit:

def info(msg):
    caller = caller_name()
    print '[%s] %s' % (caller, msg)
Mark Roddy
la source
7
Le nom de fichier n'est pas le même que__name__
Sridhar Ratnakumar