Bagaimana cara mendapatkan nama metode pemanggil dalam metode yang dipanggil?

188

Python: Bagaimana cara mendapatkan nama metode pemanggil dalam metode yang dipanggil?

Asumsikan saya memiliki 2 metode:

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

def method2(self):
    ...

Jika saya tidak ingin melakukan perubahan apa pun untuk method1, bagaimana cara mendapatkan nama pemanggil (dalam contoh ini, namanya adalah method1) di method2?

zs2020
sumber
3
Iya. Sekarang saya hanya ingin menghasilkan beberapa hal dokumentasi dan hanya untuk pengujian.
zs2020
Teknologi adalah satu hal, metodologi adalah hal lain.
zs2020

Jawaban:

233

inspect.getframeinfo dan fungsi terkait lainnya di inspectdapat membantu:

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

introspeksi ini dimaksudkan untuk membantu debugging dan pengembangan; tidak disarankan untuk mengandalkannya untuk tujuan fungsionalitas produksi.

Alex Martelli
sumber
18
"Tidak disarankan untuk mengandalkannya untuk tujuan fungsionalitas produksi." Kenapa tidak?
beltsonata
25
@beltsonata tergantung pada implementasi CPython, jadi kode yang menggunakan ini akan rusak jika Anda pernah mencoba menggunakan PyPy atau Jython atau runtimes lainnya. tidak apa-apa jika Anda hanya mengembangkan dan men-debug secara lokal tetapi tidak benar-benar sesuatu yang Anda inginkan dalam sistem produksi Anda.
Robru
@EugeneKrevenets Di luar hanya versi python, saya hanya mengalami masalah dengan itu di mana ia membuat kode yang berjalan di bawah menjalankan kedua dalam beberapa menit setelah diperkenalkan. itu sangat tidak efisien
Dmitry
Mengapa ini tidak disebutkan dalam dokumentasi python3? docs.python.org/3/library/inspect.html Mereka setidaknya akan memberi peringatan jika itu benar buruk?
1
Sayang sekali ini adalah hit pada kinerja. Logging bisa jadi jauh lebih baik dengan sesuatu seperti ini (atau CallerMemberName ).
StingyJack
92

Versi lebih pendek:

import inspect

def f1(): f2()

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

f1()

(dengan terima kasih kepada @Alex, dan Stefaan Lippen )

Todd Owen
sumber
Halo, saya mendapatkan kesalahan di bawah ini ketika saya menjalankan ini: File "/usr/lib/python2.7/inspect.py", baris 528, di foundource jika bukan sourcefile dan file [0] + file [-1]! = ' <> ': IndexError: indeks string di luar jangkauan Bisakah Anda memberikan saran. Terima kasih sebelumnya.
Pooja
Pendekatan ini memberi saya kesalahan: KeyError: ' main '
Praxiteles
61

Ini sepertinya bekerja dengan baik:

import sys
print sys._getframe().f_back.f_code.co_name
Augiwan
sumber
1
Ini tampaknya jauh lebih cepat daripadainspect.stack
menunggu
Masih menggunakan anggota yang dilindungi, yang umumnya tidak disarankan, karena bisa gagal setelah sysmodul mendapat beberapa refactoring berat.
filiprem
29

Saya datang dengan versi yang sedikit lebih panjang yang mencoba membangun nama metode lengkap termasuk modul dan kelas.

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)
techtonik anatoly
sumber
Luar biasa, ini bekerja dengan baik untuk saya dalam kode logging saya, di mana saya dapat dipanggil dari banyak tempat berbeda. Terima kasih banyak.
little_birdie
1
Kecuali Anda menghapus juga stack, itu masih bocor frame karena ref cirular seperti yang dijelaskan dalam
inspect
@ankostis Anda punya beberapa kode uji untuk membuktikannya?
anatoly techtonik
1
Sulit untuk ditampilkan dalam komentar ... Salin-tempel dalam editor kode mengemudi ini (mengetik dari memori) dan coba kedua versi kode Anda: `` `impor kelemahan kelas C: lulus def membunuh (): cetak ('Dibunuh' ) def bocor (): caller_name () local_var = C () lemahref.finalize (local_var, kill) bocor () cetak ("Local_var pasti sudah terbunuh") `` `Anda harus mendapatkan:` `Killed Local_var pasti sudah dibunuh `` `dan tidak:` `` Local_var pasti terbunuh Tewas `` `
ankostis
1
Luar biasa! Itu bekerja ketika solusi lain gagal! Saya menggunakan metode kelas dan ekspresi lambda sehingga sulit.
Osa
17

Saya akan menggunakan inspect.currentframe().f_back.f_code.co_name. Penggunaannya belum tercakup dalam salah satu jawaban sebelumnya yang sebagian besar dari salah satu dari tiga jenis:

  • Beberapa jawaban sebelumnya digunakan inspect.stacktetapi diketahui terlalu lambat .
  • Beberapa jawaban sebelumnya menggunakan sys._getframefungsi pribadi internal yang diberi garis bawah utama, dan penggunaannya secara implisit tidak disarankan.
  • Satu jawaban sebelumnya digunakan inspect.getouterframes(inspect.currentframe(), 2)[1][3]tetapi sama sekali tidak jelas apa [1][3]yang diakses.
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()

Catatan yang cast(FrameType, frame)digunakan untuk memuaskan mypy.


Ucapan terima kasih: komentar sebelumnya oleh 1313e untuk jawaban .

Acumenus
sumber
10

Sedikit penggabungan dari hal-hal di atas. Tapi ini celah saya.

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

Menggunakan:

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

def bar():
    return foo()

def baz():
    return bar()

def fizz():
    return baz()

fizz()

output adalah

level :             module             : name
--------------------------------------------------
    3 :              None              : fizz
    2 :              None              : baz
    1 :              None              : bar
    0 :            __main__            : foo
migpok35
sumber
2
Ini akan meningkatkan IndexError jika kedalaman tumpukan requeted lebih besar dari yang sebenarnya. Gunakan modules = [(index, inspect.getmodule(stack[index][0])) for index in reversed(range(1, min(stack_size, len(inspect.stack()))))]untuk mendapatkan modul.
jake77
1

Saya menemukan cara jika Anda akan melintasi kelas dan ingin kelas metode milik DAN metode. Dibutuhkan sedikit pekerjaan ekstraksi tetapi itu membuat titik. Ini berfungsi dalam 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()
Michael Swartz
sumber
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$
Mohammad Shahid Siddiqui
sumber