Ruft __name__ des aufrufenden Funktionsmoduls in Python ab

96

Angenommen, myapp/foo.pyenthält:

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

Und myapp/bar.pyenthält:

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

Ich möchte in diesem Fall caller_nameauf das __name__Attribut des aufrufenden Funktionsmoduls (myapp.foo) gesetzt werden. Wie kann das gemacht werden?

Sridhar Ratnakumar
quelle
Angenommen, ein anderes Einstiegspunktskript ruft bar.py .. auf und caller_namekann es daher nicht sein__main__
Sridhar Ratnakumar

Antworten:

122

Überprüfen Sie das Inspektionsmodul:

inspect.stack() gibt die Stapelinformationen zurück.

Gibt innerhalb einer Funktion inspect.stack()[1]den Stapel Ihres Anrufers zurück. Von dort erhalten Sie weitere Informationen über den Funktionsnamen, das Modul usw. des Anrufers.

Einzelheiten finden Sie in den Dokumenten:

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

Außerdem hat Doug Hellmann eine schöne Beschreibung des Inspektionsmoduls in seiner PyMOTW-Serie:

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

EDIT: Hier ist ein Code, der macht, was Sie wollen, denke ich:

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)
ars
quelle
1
Wie erhalten Sie das __name__Attribut dieses Moduls mithilfe des inspectModuls? Wie komme ich beispielsweise in meinem obigen Beispiel zurück myapp.foo(nicht myapp/foo.py)? Ich habe bereits vor dem Posten bei SO versucht, das Inspect-Modul zu verwenden.
Sridhar Ratnakumar
6
Beachten Sie, dass dies seltsamerweise mit Import-Hooks interagiert, unter Ironpython nicht funktioniert und sich unter Jython möglicherweise überraschend verhält. Es ist am besten, wenn Sie solche Magie vermeiden können.
Glyphe
2
Beachten Sie auch, dass das Beibehalten eines Verweises auf einen Stapelrahmen die ordnungsgemäße Funktion von Pythons GC beeinträchtigen kann. Siehe Warnung hier: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel
6
Beachten Sie, dass Sie inspect.stack()[2]für den echten Anrufer zugreifen müssen, wenn die Anruferfunktion dekoriert ist (@ ...) .
Amir Ali Akbari
Beachten Sie auch, dass diese Logik nicht ordnungsgemäß funktioniert, wenn Sie Ihren Python-Code mithilfe von Pyinstaller in eine Exe kompilieren.
Panofish
19

Angesichts eines ähnlichen Problems habe ich festgestellt, dass sys._current_frames () aus dem sys-Modul interessante Informationen enthält, die Ihnen helfen können, Inspektion zumindest in bestimmten Anwendungsfällen zu importieren.

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

Sie können dann mit f_back "aufsteigen":

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

Für den Dateinamen können Sie auch f.f_back.f_code.co_filename verwenden, wie oben von Mark Roddy vorgeschlagen. Ich bin mir der Grenzen und Einschränkungen dieser Methode nicht sicher (mehrere Threads werden höchstwahrscheinlich ein Problem sein), aber ich beabsichtige, sie in meinem Fall zu verwenden.

Louis LC
quelle
2
Hinweis: Der inspect.stack-Code schlägt fehl, nachdem er mit pyinstaller kompiliert wurde, aber mit sys._current_frames. WORKS FINE ... das ist die bevorzugte Technik für mich.
Panofish
7
Ich denke, es ist einfacher, den vorherigen Frame zu erhalten sys._getframe(1), als ihn aufzurufen sys._current_frames()(übrigens, der für jeden Thread eine Frame-Zuordnung zurückgibt).
Hooblei
Vielen Dank, hooblei, ich habe es noch nicht getestet, aber es scheint sehr nützlich für Multithread-Situationen zu sein.
Louis LC
Ich benutze lieber inspect.currentframe()statt sys._current_frames().values()[0].
Aran-Fey
3

Ich empfehle dies nicht, aber Sie können Ihr Ziel mit der folgenden Methode erreichen:

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

Aktualisieren Sie dann Ihre vorhandene Methode wie folgt:

def info(msg):
    caller = caller_name()
    print '[%s] %s' % (caller, msg)
Mark Roddy
quelle
7
Dateiname ist nicht dasselbe wie__name__
Sridhar Ratnakumar