รับ __name__ ของโมดูลการเรียกฟังก์ชันใน Python


99

สมมติว่าmyapp/foo.pyประกอบด้วย:

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

และmyapp/bar.pyประกอบด้วย:

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

ฉันต้องการcaller_nameตั้งค่าเป็น__name__แอตทริบิวต์ของโมดูลการเรียกฟังก์ชัน (ซึ่งก็คือ 'myapp.foo') ในกรณีนี้ จะทำได้อย่างไร?


สมมติว่าสคริปต์จุดเข้าอื่นเรียกใช้ bar.py .. จึงcaller_nameไม่สามารถเป็นได้__main__
Sridhar Ratnakumar

คำตอบ:


126

ตรวจสอบโมดูลการตรวจสอบ:

inspect.stack() จะส่งคืนข้อมูลสแต็ก

ภายในฟังก์ชันinspect.stack()[1]จะส่งคืนสแต็กผู้โทรของคุณ จากนั้นคุณจะได้รับข้อมูลเพิ่มเติมเกี่ยวกับชื่อฟังก์ชันโมดูล ฯลฯ ของผู้โทร

ดูเอกสารเพื่อดูรายละเอียด:

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

นอกจากนี้ Doug Hellmann ยังมีการเขียนโมดูลการตรวจสอบที่ดีในซีรีส์ PyMOTW ของเขา:

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

แก้ไข: นี่คือรหัสบางส่วนที่ทำในสิ่งที่คุณต้องการฉันคิดว่า:

import inspect 

def info(msg):
    frm = inspect.stack()[1]
    mod = inspect.getmodule(frm[0])
    print '[%s] %s' % (mod.__name__, msg)

1
แล้วคุณจะได้รับ__name__แอตทริบิวต์ของโมดูลนี้โดยใช้inspectโมดูลได้อย่างไร? ตัวอย่างเช่นฉันจะกลับmyapp.foo(ไม่myapp/foo.py) ในตัวอย่างข้างต้นได้อย่างไร ฉันได้ลองใช้โมดูลการตรวจสอบแล้วก่อนที่จะโพสต์ที่ SO
Sridhar Ratnakumar

6
โปรดทราบว่าสิ่งนี้จะโต้ตอบกับตะขอนำเข้าอย่างแปลก ๆ ไม่สามารถใช้งานได้กับ ironpython และอาจทำงานในรูปแบบที่น่าแปลกใจบน jython จะดีที่สุดถ้าคุณสามารถหลีกเลี่ยงเวทมนตร์เช่นนี้ได้
Glyph

2
โปรดทราบว่าการอ้างอิงถึงสแต็กเฟรมสามารถป้องกันไม่ให้ GC ของ Python ทำงานได้อย่างถูกต้อง ดูคำเตือนที่นี่: docs.python.org/library/inspect.html#the-interpreter-stack
Kamil Kisiel

6
โปรดทราบว่าหากมีการตกแต่งฟังก์ชันผู้โทร (@ ... ) คุณจะต้องเข้าถึงinspect.stack()[2]สำหรับผู้โทรจริง
Amir Ali Akbari

โปรดทราบว่าตรรกะนี้ทำงานไม่ถูกต้องเมื่อคุณคอมไพล์โค้ด python ลงใน exe โดยใช้ pyinstaller
panofish

19

เมื่อเผชิญกับปัญหาที่คล้ายกันฉันพบว่าsys._current_frames ()จากโมดูล sys มีข้อมูลที่น่าสนใจที่สามารถช่วยคุณได้โดยไม่จำเป็นต้องนำเข้าการตรวจสอบอย่างน้อยก็ในกรณีการใช้งานเฉพาะ

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

จากนั้นคุณสามารถ "เลื่อนขึ้น" โดยใช้ 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__'

สำหรับชื่อไฟล์คุณสามารถใช้ f.f_back.f_code.co_filename ได้ตามที่ Mark Roddy แนะนำไว้ข้างต้น ฉันไม่แน่ใจเกี่ยวกับขีด จำกัด และข้อควรระวังของวิธีนี้ (หลายเธรดน่าจะเป็นปัญหา) แต่ฉันตั้งใจจะใช้ในกรณีของฉัน


2
หมายเหตุ: รหัส inspect.stack FAILS หลังจากคอมไพล์เป็น exe โดยใช้ pyinstaller แต่ใช้ sys._current_frames WORKS FINE ... ดังนั้นนี่จึงเป็นเทคนิคที่ต้องการสำหรับฉัน
panofish

7
ฉันคิดว่ามันง่ายกว่าที่จะรับเฟรมก่อนหน้าโดยsys._getframe(1)แทนที่จะเรียกsys._current_frames()(btw ซึ่งส่งคืนการแม็ปเฟรมสำหรับทุกเธรด)
hooblei

ขอบคุณ hooblei ฉันยังไม่ได้ทดสอบ แต่ดูเหมือนจะมีประโยชน์มากสำหรับสถานการณ์แบบมัลติเธรด
Louis LC

ฉันชอบที่จะใช้inspect.currentframe()แทนsys._current_frames().values()[0].
Aran-Fey

3

ฉันไม่แนะนำให้ทำเช่นนี้ แต่คุณสามารถบรรลุเป้าหมายได้ด้วยวิธีการต่อไปนี้:

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

จากนั้นอัปเดตวิธีการที่มีอยู่ของคุณดังนี้:

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

7
ชื่อไฟล์ไม่เหมือนกับ__name__
Sridhar Ratnakumar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.