วิธีรับชื่อเมธอดของผู้โทรในเมธอดที่เรียกใช้?


187

Python: วิธีการรับชื่อวิธีการของผู้โทรในวิธีการที่เรียกว่า?

สมมติว่าฉันมี 2 วิธี:

def method1(self):
    ...
    a = A.method2()

def method2(self):
    ...

หากฉันไม่ต้องการเปลี่ยนแปลงวิธีการ 1 วิธีรับชื่อของผู้โทร (ในตัวอย่างนี้ชื่อคือวิธีที่ 1) ในวิธีที่ 2?


3
ใช่. ตอนนี้ฉันแค่ต้องการสร้างเอกสารบางอย่างและสำหรับการทดสอบเท่านั้น
zs2020

เทคโนโลยีเป็นสิ่งหนึ่งวิธีการก็เป็นอีกอย่างหนึ่ง
zs2020

คำตอบ:


233

inspect.getframeinfoและฟังก์ชั่นอื่น ๆ ที่เกี่ยวข้องในinspectสามารถช่วย:

>>> import inspect
>>> def f1(): f2()
... 
>>> def f2():
...   curframe = inspect.currentframe()
...   calframe = inspect.getouterframes(curframe, 2)
...   print('caller name:', calframe[1][3])
... 
>>> f1()
caller name: f1

วิปัสสนานี้มีวัตถุประสงค์เพื่อช่วยในการแก้ไขข้อบกพร่องและการพัฒนา ไม่แนะนำให้ใช้เพื่อวัตถุประสงค์ในการใช้งานจริง


18
"ไม่แนะนำให้ใช้มันเพื่อวัตถุประสงค์ด้านการผลิต" ทำไมจะไม่ล่ะ?
Beltsonata

25
@beltsonata มันขึ้นอยู่กับการใช้งาน CPython ดังนั้นการใช้รหัสนี้จะผิดพลาดหากคุณพยายามใช้ PyPy หรือ Jython หรือ runtimes อื่น ๆ ไม่เป็นไรถ้าคุณเพิ่งพัฒนาและแก้ไขจุดบกพร่องในเครื่อง แต่ไม่ใช่สิ่งที่คุณต้องการในระบบการผลิตของคุณ
robru

@EugeneKrevenets นอกเหนือจากรุ่นงูเหลือมฉันเพิ่งพบปัญหากับมันซึ่งทำให้รหัสที่ทำงานภายใต้การทำงานที่สองในหลายนาทีเมื่อนำมาใช้ มันไม่มีประสิทธิภาพอย่างไม่มีการลด
Dmitry

ทำไมไม่กล่าวถึงในเอกสารประกอบ python3 docs.python.org/3/library/inspect.htmlอย่างน้อยพวกเขาก็จะส่งคำเตือนถ้ามันไม่ถูกต้อง?

1
มันเป็นความอัปยศที่มันเป็นที่นิยมในการแสดง การบันทึกอาจทำได้ดีกว่ามากเมื่อใช้สิ่งนี้ (หรือCallerMemberName )
StingyJack

92

รุ่นที่สั้นกว่า:

import inspect

def f1(): f2()

def f2():
    print 'caller name:', inspect.stack()[1][3]

f1()

(ด้วยขอบคุณ @Alex และStefaan Lippen )


สวัสดีฉันได้รับข้อผิดพลาดด้านล่างเมื่อฉันเรียกใช้: File "/usr/lib/python2.7/inspect.py", บรรทัด 528 ใน findource หากไม่ใช่ sourcefile และไฟล์ [0] + ไฟล์ [-1]! = ' <> ': IndexError: ดัชนีสตริงอยู่นอกช่วงคุณช่วยแนะนำได้ไหม ขอบคุณล่วงหน้า
Pooja

วิธีการนี้ทำให้ฉันเกิดข้อผิดพลาด: KeyError: ' main '
Praxiteles

61

ดูเหมือนว่าจะทำงานได้ดี:

import sys
print sys._getframe().f_back.f_code.co_name

1
ดูเหมือนว่าจะเร็วกว่าinspect.stack
kentwait

ยังคงใช้สมาชิกที่มีการป้องกันซึ่งโดยทั่วไปจะไม่แนะนำเพราะมันอาจล้มเหลวหลังจากsysโมดูลได้รับการปรับโครงสร้างอย่างหนัก
filiprem

29

ฉันมากับรุ่นที่ยาวขึ้นเล็กน้อยที่พยายามสร้างชื่อวิธีการแบบเต็มรวมถึงโมดูลและคลาส

https://gist.github.com/2151727 (รอบ 9cccbf)

# Public Domain, i.e. feel free to copy/paste
# Considered a hack in Python 2

import inspect

def caller_name(skip=2):
    """Get a name of a caller in the format module.class.method

       `skip` specifies how many levels of stack to skip while getting caller
       name. skip=1 means "who calls me", skip=2 "who calls my caller" etc.

       An empty string is returned if skipped levels exceed stack height
    """
    stack = inspect.stack()
    start = 0 + skip
    if len(stack) < start + 1:
      return ''
    parentframe = stack[start][0]    

    name = []
    module = inspect.getmodule(parentframe)
    # `modname` can be None when frame is executed directly in console
    # TODO(techtonik): consider using __main__
    if module:
        name.append(module.__name__)
    # detect classname
    if 'self' in parentframe.f_locals:
        # I don't know any way to detect call from the object method
        # XXX: there seems to be no way to detect static method call - it will
        #      be just a function call
        name.append(parentframe.f_locals['self'].__class__.__name__)
    codename = parentframe.f_code.co_name
    if codename != '<module>':  # top level usually
        name.append( codename ) # function or a method

    ## Avoid circular refs and frame leaks
    #  https://docs.python.org/2.7/library/inspect.html#the-interpreter-stack
    del parentframe, stack

    return ".".join(name)

เยี่ยมมากมันใช้งานได้ดีสำหรับฉันในรหัสบันทึกซึ่งฉันสามารถเรียกได้จากหลาย ๆ ที่ ขอบคุณมาก ๆ.
little_birdie

1
เว้นแต่คุณจะลบยังstackก็ยังคงรั่วไหลเฟรมเนื่องจาก refs cirular ตามที่อธิบายไว้ในการตรวจสอบเอกสาร-
ankostis

@ankostis คุณมีรหัสทดสอบเพื่อพิสูจน์ว่าอย่างไร
Anatoly techtonik

1
ยากที่จะแสดงความคิดเห็น ... คัดลอกวางในตัวแก้ไขรหัสการขับขี่นี้ (พิมพ์จากหน่วยความจำ) และลองใช้รหัสของคุณทั้งสองรุ่น: `` `นำเข้าจุดอ่อนระดับ C: pass def kill (): พิมพ์ ('ฆ่า' ) def รั่ว (): caller_name () local_var = C () weakref.finalize (local_var ฆ่า) พิมพ์รั่ว () พิมพ์ ("Local_var ต้องถูกฆ่าตาย") `` `คุณควรได้รับ:` `` ฆ่า Local_var ต้องเป็น ฆ่า `` `และไม่ใช่:` `` Local_var ต้องถูกฆ่าตาย `` `
ankostis

1
! น่ากลัว วิธีนี้ใช้ได้ผลเมื่อโซลูชันอื่นล้มเหลว! ฉันใช้วิธีการเรียนและการแสดงออกแลมบ์ดาดังนั้นจึงเป็นเรื่องยาก
osa

17

inspect.currentframe().f_back.f_code.co_nameผมจะใช้ การใช้งานยังไม่ครอบคลุมในคำตอบก่อนหน้าซึ่งส่วนใหญ่เป็นหนึ่งในสามประเภท:

  • บางคำตอบก่อนใช้inspect.stackแต่ก็รู้จักที่จะเกินไปช้า
  • บางคำตอบก่อนหน้านี้ใช้sys._getframeซึ่งเป็นฟังก์ชั่นส่วนตัวภายในเนื่องจากขีดเส้นใต้นำและดังนั้นจึงไม่แนะนำให้ใช้โดยปริยาย
  • หนึ่งคำตอบก่อนหน้านี้ใช้inspect.getouterframes(inspect.currentframe(), 2)[1][3]แต่ก็ไม่ชัดเจนว่า[1][3]มีการเข้าถึงอะไร
import inspect
from types import FrameType
from typing import cast


def caller_name() -> str:
    """Return the calling function's name."""
    # Ref: https://stackoverflow.com/a/57712700/
    return cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name


if __name__ == '__main__':
    def _test_caller_name() -> None:
        assert caller_name() == '_test_caller_name'
    _test_caller_name()

โปรดทราบว่าถูกนำมาใช้เพื่อตอบสนองความcast(FrameType, frame)mypy


Acknowlegement: ความคิดเห็นก่อนโดย 1313e สำหรับคำตอบ


10

บิตของการรวมกันของสิ่งต่าง ๆ ข้างต้น แต่นี่คือรอยแตกของฉันที่มัน

def print_caller_name(stack_size=3):
    def wrapper(fn):
        def inner(*args, **kwargs):
            import inspect
            stack = inspect.stack()

            modules = [(index, inspect.getmodule(stack[index][0]))
                       for index in reversed(range(1, stack_size))]
            module_name_lengths = [len(module.__name__)
                                   for _, module in modules]

            s = '{index:>5} : {module:^%i} : {name}' % (max(module_name_lengths) + 4)
            callers = ['',
                       s.format(index='level', module='module', name='name'),
                       '-' * 50]

            for index, module in modules:
                callers.append(s.format(index=index,
                                        module=module.__name__,
                                        name=stack[index][3]))

            callers.append(s.format(index=0,
                                    module=fn.__module__,
                                    name=fn.__name__))
            callers.append('')
            print('\n'.join(callers))

            fn(*args, **kwargs)
        return inner
    return wrapper

ใช้:

@print_caller_name(4)
def foo():
    return 'foobar'

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

ผลลัพธ์คือ

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo

2
สิ่งนี้จะเพิ่ม IndexError หากความลึกของสแต็กที่ร้องขอนั้นมากกว่าความเป็นจริง ใช้modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]เพื่อรับโมดูล
jake77

1

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

import inspect, os

class ClassOne:
    def method1(self):
        classtwoObj.method2()

class ClassTwo:
    def method2(self):
        curframe = inspect.currentframe()
        calframe = inspect.getouterframes(curframe, 4)
        print '\nI was called from', calframe[1][3], \
        'in', calframe[1][4][0][6: -2]

# create objects to access class methods
classoneObj = ClassOne()
classtwoObj = ClassTwo()

# start the program
os.system('cls')
classoneObj.method1()

0
#!/usr/bin/env python
import inspect

called=lambda: inspect.stack()[1][3]

def caller1():
    print "inside: ",called()

def caller2():
    print "inside: ",called()

if __name__=='__main__':
    caller1()
    caller2()
shahid@shahid-VirtualBox:~/Documents$ python test_func.py 
inside:  caller1
inside:  caller2
shahid@shahid-VirtualBox:~/Documents$
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.