Làm thế nào để có được tên phương thức của người gọi trong phương thức được gọi?


182

Python: Làm thế nào để có được tên phương thức của người gọi trong phương thức được gọi?

Giả sử tôi có 2 phương thức:

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

def method2(self):
    ...

Nếu tôi không muốn thực hiện bất kỳ thay đổi nào cho phương thức 1, làm thế nào để có được tên của người gọi (trong ví dụ này, tên là phương thức 1) trong phương thức 2?


3
Đúng. Bây giờ tôi chỉ muốn tạo ra một số công cụ tài liệu và chỉ để thử nghiệm.
zs2020

Công nghệ là một chuyện, phương pháp học là một chuyện khác.
zs2020

Câu trả lời:


228

Kiểm tra.getframeinfo và các chức năng liên quan khác inspectcó thể giúp:

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

hướng nội này nhằm giúp gỡ lỗi và phát triển; Không nên dựa vào nó cho các mục đích chức năng sản xuất.


18
"không nên dựa vào nó cho các mục đích chức năng sản xuất." Tại sao không?
Beltonata

25
@beltsonata nó phụ thuộc vào việc triển khai CPython, vì vậy mã sử dụng mã này sẽ bị hỏng nếu bạn từng cố gắng sử dụng PyPy hoặc Jython hoặc các thời gian chạy khác. thật tốt nếu bạn chỉ đang phát triển và gỡ lỗi cục bộ nhưng không thực sự là thứ bạn muốn trong hệ thống sản xuất của bạn.
robru 10/2/2015

@EugeneKrevenets Không chỉ là phiên bản python, tôi chỉ gặp vấn đề với nó khi nó tạo mã chạy dưới lần chạy thứ hai trong nhiều phút sau khi được giới thiệu. nó không hiệu quả lắm
Dmitry

Tại sao điều này không được đề cập trong tài liệu python3? docs.python.org/3/l Library / inspect.html Họ ít nhất sẽ đưa ra một cảnh báo nếu nó xấu phải không?

1
Thật xấu hổ vì đây là một điểm nhấn về hiệu suất. Ghi nhật ký có thể đẹp hơn rất nhiều với cái gì đó như thế này (hoặc CallerMemberName ).
StingyJack

92

Phiên bản ngắn hơn:

import inspect

def f1(): f2()

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

f1()

(cảm ơn @Alex và Stefaan Lippen )


Xin chào, tôi gặp lỗi dưới đây khi tôi chạy tệp này: Tệp "/usr/lib/python2.7/inspect.py", dòng 528, trong findource nếu không phải là tệp nguồn và tệp [0] + tệp [-1]! = ' <> ': IndexError: chỉ mục chuỗi nằm ngoài phạm vi Bạn có thể vui lòng cung cấp đề xuất. Thanx trước.
Pooja

Cách tiếp cận này đã cho tôi một lỗi: KeyError: ' main '
Praxiteles

60

Điều này dường như chỉ hoạt động tốt:

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

1
Điều này dường như nhanh hơn nhiều so vớiinspect.stack
kentwait 23/2/19

Tuy nhiên, nó sử dụng một thành viên được bảo vệ, thường không được khuyến khích, bởi vì nó có thể thất bại sau khi sysmô-đun nhận được một số tái cấu trúc nặng.
filiprem

28

Tôi đã đưa ra một phiên bản dài hơn một chút để cố gắng xây dựng một tên phương thức đầy đủ bao gồm mô-đun và lớp.

https://gist.github.com/2151727 (rev 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)

Thật tuyệt vời, điều này làm việc tốt cho tôi trong mã đăng nhập của tôi, nơi tôi có thể được gọi từ rất nhiều nơi khác nhau. Cảm ơn rất nhiều.
little_birdie 24/07/2015

1
Trừ khi bạn cũng xóa stack, nó vẫn rò rỉ các khung hình do các ref của cirular như được giải thích trong các tài liệu kiểm tra
ankostis

@ankostis bạn có một số mã kiểm tra để chứng minh điều đó không?
anatoly techtonik

1
Khó hiển thị trong một nhận xét ... Sao chép-dán trong trình chỉnh sửa mã lái xe này (nhập từ bộ nhớ) và thử cả hai phiên bản mã của bạn: `` `nhập yếu lớp C: pass def kill (): print ('Bị giết' ) def rò rỉ (): caller_name () local_var = C () yếuref.finalize (local_var, kill) rò rỉ () print ("Local_var phải bị giết") bị giết `` `và không:` `` Local_var phải bị giết Bị giết `` `
ankostis

1
Tuyệt vời! Điều đó đã làm việc khi các giải pháp khác thất bại! Tôi đang sử dụng các phương thức lớp và biểu thức lambda vì vậy nó rất khó.
osa

16

Tôi sẽ sử dụng inspect.currentframe().f_back.f_code.co_name. Việc sử dụng nó đã không được đề cập trong bất kỳ câu trả lời trước nào, chủ yếu thuộc một trong ba loại:

  • Một số câu trả lời trước sử dụng inspect.stacknhưng nó được biết là quá chậm .
  • Một số câu trả lời trước sử dụng sys._getframelà một chức năng riêng tư nội bộ được đặt dưới dấu gạch dưới hàng đầu của nó, và vì vậy việc sử dụng nó hoàn toàn không được khuyến khích.
  • Một câu trả lời trước sử dụng inspect.getouterframes(inspect.currentframe(), 2)[1][3]nhưng hoàn toàn không rõ những gì [1][3]đang truy cập.
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()

Lưu ý rằng cast(FrameType, frame)được sử dụng để đáp ứng mypy.


Lời cảm ơn: bình luận trước 1313e cho câu trả lời .


10

Bit của một sự pha trộn của những thứ ở trên. Nhưng đây là vết nứt của tôi ở đó.

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

Sử dụng:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

đầu ra là

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

2
Điều này sẽ tăng IndexError nếu độ sâu ngăn xếp được yêu cầu lớn hơn thực tế. Sử dụng modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]để có được các mô-đun.
jake77

1

Tôi đã tìm thấy một cách nếu bạn đi ngang qua các lớp và muốn lớp đó phương thức thuộc về AND phương thức. Nó cần một chút công việc khai thác nhưng nó làm cho quan điểm của nó. Điều này hoạt động trong 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$
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.