Pobierz __name__ wywołania modułu funkcji w Pythonie

98

Załóżmy, że myapp/foo.pyzawiera:

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

I myapp/bar.pyzawiera:

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

W tym przypadku chcę caller_nameustawić __name__atrybut modułu funkcji wywołujących (czyli „myapp.foo”). Jak można to zrobić?

Sridhar Ratnakumar
źródło
Załóżmy, że jakiś inny skrypt punktu wejścia wywołuje bar.py .. i dlatego caller_namenie może być__main__
Sridhar Ratnakumar

Odpowiedzi:

125

Sprawdź moduł inspekcji:

inspect.stack() zwróci informacje o stosie.

Wewnątrz funkcji inspect.stack()[1]zwraca stos dzwoniącego. Stamtąd możesz uzyskać więcej informacji o nazwie funkcji dzwoniącego, module itp.

Zobacz dokumentację, aby uzyskać szczegółowe informacje:

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

Ponadto Doug Hellmann ma fajny opis modułu inspekcji w swojej serii PyMOTW:

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

EDYCJA: Oto kod, który robi to, co chcesz, myślę:

import inspect 

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)
ar
źródło
1
Jak więc uzyskać __name__atrybut tego modułu za pomocą inspectmodułu? Na przykład, jak mogę wrócić myapp.foo(nie myapp/foo.py) w powyższym przykładzie? Próbowałem już skorzystać z modułu inspekcji przed wysłaniem wiadomości do SO.
Sridhar Ratnakumar
6
Należy pamiętać, że będzie to dziwnie współdziałać z hookami importu, nie będzie działać na ironpythonie i może zachowywać się w zaskakujący sposób w jythonie. Najlepiej, jeśli możesz uniknąć takiej magii.
Glyph
2
Zauważ również, że zachowanie odniesienia do ramki stosu może uniemożliwić poprawne działanie GC Pythona. Zobacz ostrzeżenie tutaj: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel
6
Zauważ, że jeśli funkcja dzwoniącego jest udekorowana (@ ...), musisz uzyskać dostęp inspect.stack()[2]dla prawdziwego dzwoniącego.
Amir Ali Akbari
Zauważ również, że ta logika nie działa poprawnie, gdy kompilujesz kod Pythona do pliku exe za pomocą pyinstaller.
panofish
19

W obliczu podobnego problemu stwierdziłem, że sys._current_frames () z modułu sys zawiera interesujące informacje, które mogą Ci pomóc, bez konieczności importowania inspekcji, przynajmniej w określonych przypadkach użycia.

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

Następnie możesz „przesunąć się w górę” za pomocą 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__'

Jako nazwę pliku możesz również użyć f.f_back.f_code.co_filename, zgodnie z sugestią Marka Roddy'ego powyżej. Nie jestem pewien ograniczeń i zastrzeżeń tej metody (najprawdopodobniej wiele wątków będzie problemem), ale zamierzam ją zastosować w moim przypadku.

Louis LC
źródło
2
uwaga: kod inspect.stack BŁĘDY się po kompilacji do exe przy użyciu pyinstaller, ale przy użyciu sys._current_frames DZIAŁA DOKŁADNIE ... więc jest to preferowana technika dla mnie.
panofish
7
Myślę, że łatwiej jest uzyskać poprzednią klatkę sys._getframe(1), zamiast dzwonić sys._current_frames()(przy okazji zwraca mapowanie ramki dla każdego wątku).
hooblei
Dziękuję hooblei, jeszcze tego nie testowałem, ale wydaje się bardzo przydatny w sytuacjach wielowątkowych.
Louis LC
Wolę używać inspect.currentframe()zamiast sys._current_frames().values()[0].
Aran-Fey
3

Nie polecam tego robić, ale możesz osiągnąć swój cel następującą metodą:

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

Następnie zaktualizuj istniejącą metodę w następujący sposób:

def info(msg):
    caller = caller_name()
    print '[%s] %s' % (caller, msg)
Mark Roddy
źródło
7
Nazwa __name__
pliku