Dapatkan __name__ untuk memanggil modul fungsi dengan Python

96

Misalkan myapp/foo.pymengandung:

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

Dan myapp/bar.pyberisi:

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

Saya ingin caller_namedisetel ke __name__atribut modul fungsi pemanggil (yaitu 'myapp.foo') dalam kasus ini. Bagaimana ini bisa dilakukan?

Sridhar Ratnakumar
sumber
Asumsikan bahwa beberapa skrip titik masuk lainnya memanggil bar.py .. dan karenanya caller_nametidak dapat__main__
Sridhar Ratnakumar

Jawaban:

123

Lihat modul inspeksi:

inspect.stack() akan mengembalikan informasi tumpukan.

Di dalam suatu fungsi, inspect.stack()[1]akan mengembalikan tumpukan pemanggil Anda. Dari sana, Anda bisa mendapatkan lebih banyak informasi tentang nama fungsi pemanggil, modul, dll.

Lihat dokumen untuk detailnya:

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

Juga, Doug Hellmann memiliki tulisan bagus tentang modul inspeksi dalam seri PyMOTW-nya:

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

EDIT: Berikut beberapa kode yang melakukan apa yang Anda inginkan, menurut saya:

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)
ars
sumber
1
Jadi bagaimana Anda mendapatkan __name__atribut modul ini menggunakan inspectmodul? Misalnya, bagaimana saya mendapatkan kembali myapp.foo(bukan myapp/foo.py) dalam contoh saya di atas? Saya sudah mencoba menggunakan modul inspeksi sebelum memposting di SO.
Sridhar Ratnakumar
6
Ketahuilah bahwa ini akan berinteraksi secara aneh dengan kait impor, tidak akan berfungsi pada ironpython, dan mungkin berperilaku mengejutkan pada jython. Lebih baik jika Anda bisa menghindari sihir seperti ini.
Mesin terbang
2
Perhatikan juga bahwa menyimpan referensi ke bingkai tumpukan dapat mencegah GC Python bekerja dengan benar. Lihat peringatan di sini: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel
6
Perhatikan bahwa jika fungsi pemanggil didekorasi (@ ...), Anda perlu mengakses inspect.stack()[2]pemanggil yang sebenarnya.
Amir Ali Akbari
Perhatikan juga bahwa logika ini gagal berfungsi dengan baik ketika Anda mengkompilasi kode python Anda menjadi exe menggunakan pyinstaller.
panofish
19

Dihadapkan dengan masalah serupa, saya telah menemukan bahwa sys._current_frames () dari modul sys berisi informasi menarik yang dapat membantu Anda, tanpa perlu mengimpor inspeksi, setidaknya dalam kasus penggunaan tertentu.

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

Anda kemudian dapat "naik" menggunakan 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__'

Untuk nama file Anda juga bisa menggunakan f.f_back.f_code.co_filename, seperti yang disarankan oleh Mark Roddy di atas. Saya tidak yakin dengan batasan dan peringatan dari metode ini (beberapa utas kemungkinan besar akan menjadi masalah) tetapi saya bermaksud menggunakannya dalam kasus saya.

Louis LC
sumber
2
catatan: kode inspect.stack GAGAL setelah dikompilasi ke exe menggunakan pyinstaller, tetapi menggunakan sys._current_frames BEKERJA HALUS ... jadi ini adalah teknik yang disukai bagi saya.
panofish
7
Saya pikir lebih mudah untuk mendapatkan bingkai sebelumnya sys._getframe(1), daripada memanggil sys._current_frames()(btw yang mengembalikan pemetaan bingkai untuk setiap utas).
hooblei
Terima kasih hooblei, saya belum mengujinya tetapi tampaknya sangat berguna untuk situasi multi-threaded.
Louis LC
Saya lebih suka menggunakan inspect.currentframe()daripada sys._current_frames().values()[0].
Aran-Fey
3

Saya tidak menyarankan melakukan ini, tetapi Anda dapat mencapai tujuan Anda dengan metode berikut:

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

Kemudian perbarui metode yang ada sebagai berikut:

def info(msg):
    caller = caller_name()
    print '[%s] %s' % (caller, msg)
Mark Roddy
sumber
7
Nama file tidak sama dengan__name__
Sridhar Ratnakumar