Tentukan nama fungsi dari dalam fungsi itu (tanpa menggunakan traceback)

479

Dalam Python, tanpa menggunakan tracebackmodul, apakah ada cara untuk menentukan nama fungsi dari dalam fungsi itu?

Katakanlah saya punya modul foo dengan bilah fungsi. Saat menjalankan foo.bar(), apakah ada cara bagi bar untuk mengetahui nama bar? Atau lebih baik lagi, foo.barnama?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?
rampok
sumber
6
Saya tidak mengerti mengapa jawaban yang diterima bukan dari Andreas Young (atau lainnya yang menunjukkan cara melakukannya). Sebaliknya, jawaban yang diterima adalah "Anda tidak bisa melakukan itu", yang tampaknya salah; satu-satunya persyaratan oleh OP tidak menggunakan traceback. Bahkan saat-saat jawaban dan komentar sepertinya tidak mendukungnya.
sancho.s ReinstateMonicaCellio

Jawaban:

198

Python tidak memiliki fitur untuk mengakses fungsi atau namanya di dalam fungsi itu sendiri. Telah diusulkan tetapi ditolak. Jika Anda tidak ingin bermain sendiri dengan tumpukan, Anda harus menggunakan "bar"atau bar.__name__bergantung pada konteks.

Pemberitahuan penolakan yang diberikan adalah:

PEP ini ditolak. Tidak jelas bagaimana itu harus diimplementasikan atau apa semantik yang tepat harus dalam kasus tepi, dan tidak ada cukup kasus penggunaan penting yang diberikan. responsnya paling suam-suam kuku.

Rosh Oxymoron
sumber
17
inspect.currentframe()adalah salah satu cara seperti itu.
Yuval
41
Menggabungkan pendekatan @ CamHart dengan @ Yuval menghindari metode "tersembunyi" dan berpotensi usang dalam jawaban @ RoshOxymoron serta pengindeksan numerik ke dalam tumpukan untuk jawaban @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
hobs
2
apakah mungkin untuk meringkas mengapa ditolak?
Charlie Parker
1
mengapa ini jawaban yang dipilih? Pertanyaan bukan tentang mengakses fungsi saat ini atau modul itu sendiri, hanya namanya. Dan fitur stacktrace / debugging sudah memiliki informasi ini.
nurettin
411
import inspect

def foo():
   print(inspect.stack()[0][3])
   print(inspect.stack()[1][3]) #will give the caller of foos name, if something called foo
Andreas Jung
sumber
47
Ini bagus karena Anda juga bisa melakukannya [1][3]untuk mendapatkan nama penelepon.
Kos
58
Anda juga bisa menggunakan: print(inspect.currentframe().f_code.co_name)atau untuk mendapatkan nama pemanggil: print(inspect.currentframe().f_back.f_code.co_name). Saya pikir itu harus lebih cepat karena Anda tidak mengambil daftar semua frame stack seperti inspect.stack()halnya.
Michael
7
inspect.currentframe().f_back.f_code.co_nametidak bekerja dengan metode yang didekorasi sedangkan inspect.stack()[0][3]...
Dustin Wyatt
4
Harap dicatat: inspect.stack () dapat menyebabkan overhead kinerja yang berat jadi gunakan hemat! Di kotak lengan saya butuh 240ms untuk menyelesaikan (untuk beberapa alasan)
gardarh
Menurut saya sesuatu yang ada pada mesin rekursi Python dapat digunakan untuk melakukan ini dengan lebih efisien
Tom Russell
160

Ada beberapa cara untuk mendapatkan hasil yang sama:

from __future__ import print_function
import sys
import inspect

def what_is_my_name():
    print(inspect.stack()[0][0].f_code.co_name)
    print(inspect.stack()[0][3])
    print(inspect.currentframe().f_code.co_name)
    print(sys._getframe().f_code.co_name)

Perhatikan bahwa inspect.stackpanggilan ribuan kali lebih lambat daripada alternatif:

$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][0].f_code.co_name'
1000 loops, best of 3: 499 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.stack()[0][3]'
1000 loops, best of 3: 497 usec per loop
$ python -m timeit -s 'import inspect, sys' 'inspect.currentframe().f_code.co_name'
10000000 loops, best of 3: 0.1 usec per loop
$ python -m timeit -s 'import inspect, sys' 'sys._getframe().f_code.co_name'
10000000 loops, best of 3: 0.135 usec per loop
Alex Granovsky
sumber
5
inspect.currentframe()tampaknya pertukaran yang baik antara waktu eksekusi dan penggunaan anggota pribadi
FabienAndre
1
@ mbdevpl Nomor saya adalah 1,25ms, 1,24ms, 0,5us, 0,16us normal (nonpythonic :)) detik sesuai (win7x64, python3.5.1)
Antony Hatchkins
Hanya saja tidak ada yang berpikir @ mbdevpl gila :) - Saya mengirimkan hasil edit untuk keluaran ke-3, karena tidak masuk akal. Tidak tahu apakah hasilnya seharusnya 0.100 usecatau 0.199 usecsebaliknya - ratusan kali lebih cepat dari opsi 1 dan 2, sebanding dengan opsi 4 (meskipun Antony Hatchkins menemukan opsi 3 tiga kali lebih cepat dari opsi 4).
dwanderson
Saya menggunakan sys._getframe().f_code.co_namelebih inspect.currentframe().f_code.co_namekarena saya sudah mengimpor sysmodul. Apakah itu keputusan yang masuk akal? (mengingat kecepatan muncul sangat mirip)
PatrickT
47

Anda bisa mendapatkan nama yang didefinisikan dengan menggunakan pendekatan yang ditunjukkan oleh @Andreas Jung , tapi itu mungkin bukan nama yang dipanggil dengan fungsi:

import inspect

def Foo():
   print inspect.stack()[0][3]

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Apakah perbedaan itu penting bagi Anda atau tidak, saya tidak bisa mengatakannya.

bgporter
sumber
3
Situasi yang sama dengan .func_name. Perlu diingat bahwa nama kelas dan nama fungsi dalam Python adalah satu hal dan variabel yang merujuk padanya adalah hal lain.
Kos
Terkadang Anda mungkin ingin Foo2()mencetak Foo. Sebagai contoh: Foo2 = function_dict['Foo']; Foo2(). Dalam hal ini, Foo2 adalah pointer fungsi untuk mungkin parser baris perintah.
Harvey
Implikasi kecepatan seperti apa yang dimilikinya?
Robert C. Barth
2
Implikasi kecepatan berkenaan dengan apa? Apakah ada situasi di mana Anda perlu memiliki informasi ini dalam situasi sulit realtime atau sesuatu?
bgporter
44
functionNameAsString = sys._getframe().f_code.co_name

Saya menginginkan hal yang sangat mirip karena saya ingin memasukkan nama fungsi dalam string log yang masuk ke sejumlah tempat dalam kode saya. Mungkin bukan cara terbaik untuk melakukan itu, tetapi inilah cara untuk mendapatkan nama fungsi saat ini.

Ron Davis
sumber
2
Benar-benar berfungsi, hanya menggunakan sys, tidak perlu memuat lebih banyak modul, tetapi tidak begitu mudah untuk mengingatnya: V
m3nda
@ erm3nda Lihat jawaban saya.
nerdfever.com
1
@ nerdfever.com Masalah saya bukan tentang membuat fungsi, adalah tidak ingat apa yang harus dimasukkan ke dalam fungsi itu. Tidak mudah diingat sehingga saya akan selalu perlu melihat beberapa catatan untuk membangun itu lagi. Saya akan mencoba untuk mengingat funtuk frame dan cokode. Saya tidak menggunakannya terlalu banyak sehingga lebih baik jika saya menyimpannya di beberapa cuplikan :-)
m3nda
24

Saya menyimpan utilitas praktis ini di dekat:

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

Pemakaian:

myself()
xxyzzy
sumber
1
Bagaimana ini dilakukan dengan alternatif yang diusulkan di sini? "diriku = lambda: sys._getframe (). f_code.co_name" tidak berfungsi (outputnya "<lambda>"; Saya pikir karena hasilnya ditentukan pada waktu definisi, bukan nanti pada waktu panggilan. Hmm.
NYCeyes
2
@ prismalytics.io: Jika Anda menyebut diri saya (diri sendiri ()) dan tidak hanya menggunakan nilainya (diri sendiri), Anda akan mendapatkan apa yang Anda cari.
ijw
NYCeyes benar, namanya diselesaikan di dalam lambda dan dengan demikian hasilnya adalah <lambda>. Metode sys._getframe () dan inspect.currentframe () HARUS dijalankan langsung di dalam fungsi yang Anda ingin dapatkan namanya. Metode inspect.stack () berfungsi karena Anda dapat menentukan indeks 1, melakukan inspect.stack () [0] [3] juga menghasilkan <lambda>.
kikones34
20

Saya kira inspectadalah cara terbaik untuk melakukan ini. Sebagai contoh:

import inspect
def bar():
    print("My name is", inspect.stack()[0][3])
Bjorn
sumber
17

Saya menemukan pembungkus yang akan menulis nama fungsi

from functools import wraps

def tmp_wrap(func):
    @wraps(func)
    def tmp(*args, **kwargs):
        print func.__name__
        return func(*args, **kwargs)
    return tmp

@tmp_wrap
def my_funky_name():
    print "STUB"

my_funky_name()

Ini akan dicetak

my_funky_name

STUB

cad106uk
sumber
1
Sebagai dekorator dekorator, saya ingin tahu apakah ada cara untuk mengakses func .__ name__ dalam konteks my_funky_name (jadi saya dapat mengambil nilainya dan menggunakannya di dalam my_funky_name)
cowbert
Cara untuk melakukannya di dalam fungsi my_funky_name adalah my_funky_name.__name__. Anda bisa melewatkan func .__ name__ ke dalam fungsi sebagai parameter baru. func (* args, ** kwargs, my_name = func .__ name__). Untuk mendapatkan nama dekorator Anda dari dalam fungsi Anda, saya pikir itu perlu menggunakan inspeksi. Tetapi mendapatkan nama fungsi yang mengendalikan fungsi saya di dalam fungsi berlari saya ... yah itu terdengar seperti permulaan meme yang indah :)
cad106uk
15

Ini sebenarnya berasal dari jawaban lain untuk pertanyaan itu.

Inilah pendapat saya:

import sys

# for current func name, specify 0 or no argument.
# for name of caller of current func, specify 1.
# for name of caller of caller of current func, specify 2. etc.
currentFuncName = lambda n=0: sys._getframe(n + 1).f_code.co_name


def testFunction():
    print "You are in function:", currentFuncName()
    print "This function's caller was:", currentFuncName(1)    


def invokeTest():
    testFunction()


invokeTest()

# end of file

Kemungkinan keuntungan dari versi ini daripada menggunakan inspect.stack () adalah bahwa itu harus ribuan kali lebih cepat [lihat posting dan waktu Alex Melihoff mengenai penggunaan sys._getframe () dibandingkan menggunakan inspect.stack ()].

Gino
sumber
12

print(inspect.stack()[0].function) tampaknya berfungsi juga (Python 3.5).

Pierre Voisin
sumber
11

Berikut ini adalah pendekatan yang terbukti di masa depan.

Menggabungkan saran @ CamHart dan @ Yuval dengan jawaban yang diterima @ RoshOxymoron memiliki manfaat menghindari:

  • _hidden dan metode yang berpotensi usang
  • pengindeksan ke dalam tumpukan (yang dapat disusun kembali dalam ular piton masa depan)

Jadi saya pikir ini bagus untuk versi python masa depan (diuji pada 2.7.3 dan 3.3.2):

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))
hobs
sumber
11
import sys

def func_name():
    """
    :return: name of caller
    """
    return sys._getframe(1).f_code.co_name

class A(object):
    def __init__(self):
        pass
    def test_class_func_name(self):
        print(func_name())

def test_func_name():
    print(func_name())

Uji:

a = A()
a.test_class_func_name()
test_func_name()

Keluaran:

test_class_func_name
test_func_name
tidak lahir
sumber
11

Saya tidak yakin mengapa orang membuatnya rumit:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))
karthik r
sumber
mungkin karena Anda menggunakan fungsi pribadi dari modul sys, di luarnya. Secara umum itu dianggap praktik yang buruk, bukan?
tikej
@tikej, penggunaan setuju dengan praktik terbaik yang dinyatakan di sini: docs.quantifiedcode.com/python-anti-patterns/correctness/…
karthik r
10
import inspect

def whoami():
    return inspect.stack()[1][3]

def whosdaddy():
    return inspect.stack()[2][3]

def foo():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())
    bar()

def bar():
    print "hello, I'm %s, daddy is %s" % (whoami(), whosdaddy())

foo()
bar()

Dalam IDE output kode

halo, aku foo, ayah

halo, saya bar, ayah foo

halo, saya bar, ayah

Lee
sumber
8

Anda dapat menggunakan dekorator:

def my_function(name=None):
    return name

def get_function_name(function):
    return function(name=function.__name__)

>>> get_function_name(my_function)
'my_function'
Douglas Denhartog
sumber
Bagaimana itu menjawab pertanyaan poster? Bisakah Anda memperluas ini untuk memasukkan contoh bagaimana nama fungsi diketahui dari dalam fungsi?
parvus
@parvus: jawaban saya apa adanya adalah contoh yang menunjukkan jawaban atas pertanyaan OP
Douglas Denhartog
Ok, my_function adalah fungsi pengguna acak OP. Salahkan ini karena kurangnya pemahaman saya tentang dekorator. Dimana @? Bagaimana ini akan berfungsi untuk fungsi yang argumennya tidak ingin Anda sesuaikan? Bagaimana saya memahami solusi Anda: ketika saya ingin tahu nama fungsinya, saya harus menambahkannya dengan @get_function_name, dan menambahkan argumen nama, berharap itu belum ada untuk tujuan lain. Saya mungkin melewatkan sesuatu, maaf untuk itu.
parvus
Tanpa memulai kursus python saya sendiri di dalam komentar: 1. fungsi adalah objek; 2. Anda dapat melampirkan atribut nama ke fungsi, mencetak / mencatat nama, atau melakukan sejumlah hal dengan "nama" di dalam dekorator; 3. dekorator dapat dilampirkan beberapa cara (misalnya @ atau dalam contoh saya); 4. dekorator dapat menggunakan @wraps dan / atau menjadi kelas sendiri; 5. Saya bisa melanjutkan, tapi, selamat pemrograman!
Douglas Denhartog
Ini hanya terlihat seperti cara berbelit-belit untuk mendapatkan __name__atribut fungsi. Penggunaan membutuhkan mengetahui hal yang Anda coba dapatkan, yang tampaknya tidak terlalu berguna bagi saya dalam kasus sederhana di mana fungsi tidak didefinisikan dengan cepat.
Avi
3

Saya melakukan pendekatan saya sendiri yang digunakan untuk memanggil super dengan aman di dalam beberapa skenario warisan (saya meletakkan semua kode)

def safe_super(_class, _inst):
    """safe super call"""
    try:
        return getattr(super(_class, _inst), _inst.__fname__)
    except:
        return (lambda *x,**kx: None)


def with_name(function):
    def wrap(self, *args, **kwargs):
        self.__fname__ = function.__name__
        return function(self, *args, **kwargs)
return wrap

penggunaan sampel:

class A(object):

    def __init__():
        super(A, self).__init__()

    @with_name
    def test(self):
        print 'called from A\n'
        safe_super(A, self)()

class B(object):

    def __init__():
        super(B, self).__init__()

    @with_name
    def test(self):
        print 'called from B\n'
        safe_super(B, self)()

class C(A, B):

    def __init__():
        super(C, self).__init__()

    @with_name
    def test(self):
        print 'called from C\n'
        safe_super(C, self)()

mengujinya:

a = C()
a.test()

keluaran:

called from C
called from A
called from B

Di dalam setiap metode dihiasi @with_name Anda memiliki akses ke self .__ fname__ sebagai nama fungsi saat ini.

Mel Viso Martinez
sumber
3

Saya baru-baru ini mencoba menggunakan jawaban di atas untuk mengakses dokumen dari suatu fungsi dari konteks fungsi itu tetapi karena pertanyaan di atas hanya mengembalikan string nama itu tidak berfungsi.

Untungnya saya menemukan solusi sederhana. Jika seperti saya, Anda ingin merujuk ke fungsi daripada hanya mendapatkan string yang mewakili nama yang dapat Anda terapkan eval () ke string nama fungsi.

import sys
def foo():
    """foo docstring"""
    print(eval(sys._getframe().f_code.co_name).__doc__)
John Forbes
sumber
3

Saya sarankan untuk tidak mengandalkan elemen stack. Jika seseorang menggunakan kode Anda dalam konteks yang berbeda (misalnya interpreter python) tumpukan Anda akan berubah dan menghancurkan indeks Anda ([0] [3]).

Saya menyarankan Anda sesuatu seperti itu:

class MyClass:

    def __init__(self):
        self.function_name = None

    def _Handler(self, **kwargs):
        print('Calling function {} with parameters {}'.format(self.function_name, kwargs))
        self.function_name = None

    def __getattr__(self, attr):
        self.function_name = attr
        return self._Handler


mc = MyClass()
mc.test(FirstParam='my', SecondParam='test')
mc.foobar(OtherParam='foobar')
Genschi
sumber
0

Ini sangat mudah dilakukan dengan dekorator.

>>> from functools import wraps

>>> def named(func):
...     @wraps(func)
...     def _(*args, **kwargs):
...         return func(func.__name__, *args, **kwargs)
...     return _
... 

>>> @named
... def my_func(name, something_else):
...     return name, something_else
... 

>>> my_func('hello, world')
('my_func', 'hello, world')
Jeff Laughlin
sumber