กำหนดชื่อฟังก์ชั่นจากภายในฟังก์ชั่นนั้น (โดยไม่ต้องใช้การย้อนกลับ)


479

ใน Python โดยไม่ต้องใช้tracebackโมดูลมีวิธีกำหนดชื่อของฟังก์ชันจากภายในฟังก์ชันนั้นหรือไม่?

บอกว่าฉันมีโมดูลฟูด้วยแถบฟังก์ชั่น เมื่อดำเนินการfoo.bar()มีวิธีที่บาร์จะรู้ชื่อของแถบหรือไม่ หรือดีกว่ายังfoo.barชื่อ?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?

6
ฉันไม่เข้าใจว่าทำไมคำตอบที่ยอมรับไม่ใช่จาก Andreas Young (หรืออื่น ๆ ที่แสดงวิธีการทำ) แต่คำตอบที่ยอมรับคือ "คุณทำไม่ได้" ซึ่งดูเหมือนผิด ความต้องการโดย OP tracebackเท่านั้นไม่ได้ใช้ ไม่แม้แต่เวลาที่คำตอบและความคิดเห็นดูเหมือนจะกลับมา
sancho.s ReinstateMonicaCellio

คำตอบ:


198

Python ไม่มีคุณสมบัติในการเข้าถึงฟังก์ชั่นหรือชื่อของมันภายในฟังก์ชั่นของมันเอง มันได้รับการเสนอแต่ปฏิเสธ หากคุณไม่ต้องการเล่นกับสแต็คด้วยตนเองคุณควรใช้"bar"หรือbar.__name__ขึ้นอยู่กับบริบท

การแจ้งเตือนการปฏิเสธที่ได้รับคือ:

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


17
inspect.currentframe()เป็นวิธีหนึ่งดังกล่าว
Yuval

41
การรวมวิธีการ @ CamHart เข้ากับ @ Yuval หลีกเลี่ยงวิธี "ซ่อน" และอาจเลิกใช้ในคำตอบของ @ RoshOxymoron เช่นเดียวกับการสร้างดัชนีตัวเลขลงในสแต็คสำหรับคำตอบของ @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
hobs

2
เป็นไปได้หรือไม่ที่จะสรุปว่าทำไมถูกปฏิเสธ
Charlie Parker

1
ทำไมนี่คือคำตอบที่เลือก? คำถามไม่ได้เกี่ยวกับการเข้าถึงฟังก์ชั่นปัจจุบันหรือโมดูลตัวเองเพียงแค่ชื่อ และคุณสมบัติ stacktrace / debugging มีข้อมูลนี้อยู่แล้ว
นูเรตอิน

409
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

47
สิ่งนี้ดีมากเพราะคุณสามารถ[1][3]รับชื่อผู้โทรได้ด้วย
Kos

57
นอกจากนี้คุณยังสามารถใช้: หรือจะได้รับชื่อของผู้โทร:print(inspect.currentframe().f_code.co_name) print(inspect.currentframe().f_back.f_code.co_name)ฉันคิดว่ามันน่าจะเร็วกว่าเพราะคุณไม่ได้เรียกคืนรายการสแต็กเฟรมทั้งหมดเหมือนที่เคยinspect.stack()ทำ
Michael

7
inspect.currentframe().f_back.f_code.co_nameไม่ได้ทำงานกับวิธีการตกแต่งในขณะที่inspect.stack()[0][3]ไม่ ...
ดัสตินไวแอตต์

4
โปรดทราบ: inspect.stack () สามารถใช้งานได้อย่างมีประสิทธิภาพเหนือระดับดังนั้นใช้งานได้อย่างประหยัด! บนกล่องแขนของฉันมันต้องใช้เวลาที่จะเสร็จสมบูรณ์ 240ms (ด้วยเหตุผลบางอย่าง)
gardarh

ดูเหมือนว่าฉันจะมีบางสิ่งบางอย่างอยู่ในเครื่องจักรแบบเรียกซ้ำของ Python ซึ่งอาจใช้เพื่อทำสิ่งนี้ได้อย่างมีประสิทธิภาพมากขึ้น
Tom Russell

160

มีหลายวิธีที่จะได้ผลลัพธ์เดียวกัน:

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)

โปรดทราบว่าการinspect.stackโทรช้ากว่าทางเลือกหลายพันครั้ง:

$ 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

5
inspect.currentframe()ดูเหมือนจะเป็นการแลกเปลี่ยนที่ดีระหว่างเวลาดำเนินการและการใช้งานสมาชิกส่วนตัว
FabienAndre

1
@mbdevpl ตัวเลขของฉันคือ 1.25ms, 1.24ms, 0.5us, 0.16us ปกติ (nonpythonic :)) วินาทีตามลำดับ (win7x64, python3.5.1)
Antony Hatchkins

ไม่มีใครคิดว่า @ mbdevpl มันบ้า :) - ฉันส่งการแก้ไขสำหรับผลลัพธ์ของการวิ่งครั้งที่ 3 เนื่องจากมันไม่สมเหตุสมผล ไม่มีความคิดว่าผลลัพธ์ควรได้รับ0.100 usecหรือ0.199 usecอย่างใดอย่างหนึ่ง - เร็วกว่าตัวเลือก 1 และ 2 หลายร้อยเท่าเทียบกับตัวเลือก 4 (แม้ว่า Antony Hatchkins พบตัวเลือก 3 เร็วกว่าตัวเลือก 3 สามครั้ง)
dwanderson

ฉันใช้sys._getframe().f_code.co_nameมากกว่าinspect.currentframe().f_code.co_nameเพราะฉันได้นำเข้าsysโมดูลแล้ว นั่นเป็นการตัดสินใจที่สมเหตุสมผลหรือไม่? (การพิจารณาความเร็วนั้นค่อนข้างคล้ายคลึงกัน)
PatrickT

47

คุณสามารถรับชื่อที่ถูกกำหนดโดยใช้วิธีการที่ @Andreas Jung แสดงแต่อาจไม่ใช่ชื่อที่เรียกใช้ฟังก์ชันด้วย:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

ความแตกต่างนั้นสำคัญสำหรับคุณหรือไม่ฉันไม่สามารถพูดได้


3
.func_nameสถานการณ์เช่นเดียวกับกับ ควรจำไว้ว่าชื่อคลาสและชื่อฟังก์ชันใน Python เป็นสิ่งหนึ่งและตัวแปรที่อ้างอิงถึงเป็นอีกสิ่งหนึ่ง
Kos

บางครั้งคุณอาจต้องการที่จะพิมพ์Foo2() Fooตัวอย่างเช่นFoo2 = function_dict['Foo']; Foo2(). ในกรณีนี้ Foo2 เป็นตัวชี้ฟังก์ชั่นสำหรับตัวแยกวิเคราะห์บรรทัดคำสั่ง
ฮาร์วีย์

สิ่งนี้เกี่ยวข้องกับความเร็วอย่างไร
Robert C. Barth

2
ความหมายความเร็วเกี่ยวกับอะไร มีสถานการณ์ที่คุณต้องมีข้อมูลนี้ในสถานการณ์เรียลไทม์หรืออะไร?
bgporter

44
functionNameAsString = sys._getframe().f_code.co_name

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


2
ใช้งานได้เต็มที่เพียงแค่ใช้ sys ไม่จำเป็นต้องโหลดโมดูลมากขึ้น แต่ก็ไม่สามารถจำได้ง่ายนัก: V
m3nda

@ erm3nda ดูคำตอบของฉัน
nerdfever.com

1
@ nerdfever.com ปัญหาของฉันไม่ได้เกี่ยวกับการสร้างฟังก์ชั่นคือการจำไม่ได้ว่าจะใส่อะไรเข้าไปในฟังก์ชั่นนั้น ไม่ใช่เรื่องง่ายที่จะจำดังนั้นฉันจะต้องเห็นบันทึกบางอย่างเพื่อสร้างมันอีกครั้ง ฉันจะพยายามจำfกรอบและcoรหัสไว้ ฉันไม่ได้ใช้มันอย่างนั้นดีกว่านี้ถ้าเพียงแค่ฉันบันทึกไว้ในตัวอย่างบางส่วน :-)
m3nda

24

ฉันเก็บโปรแกรมอรรถประโยชน์นี้ไว้ใกล้ ๆ :

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

การใช้งาน:

myself()

1
จะทำอย่างไรกับทางเลือกที่เสนอที่นี่? "yourself = lambda: sys._getframe (). f_code.co_name" ไม่ทำงาน (ผลลัพธ์คือ "<lambda>"; ฉันคิดว่าเพราะผลลัพธ์ถูกกำหนดตามเวลาที่กำหนดไม่ใช่เวลาต่อมา Hmm.
NYCeyes

2
@ prismalytics.io: หากคุณเรียกตัวเอง (ตัวเอง ()) และไม่เพียงแค่ใช้คุณค่า (ตัวเอง) คุณจะได้สิ่งที่คุณต้องการ
ijw

NYCeyes ถูกต้องชื่อจะได้รับการแก้ไขภายในแลมบ์ดาและทำให้ผลลัพธ์คือ <lambda> เมธอด sys._getframe () และ inspect.currentframe () ต้องดำเนินการโดยตรงภายในฟังก์ชันที่คุณต้องการรับชื่อ เมธอด inspect.stack () ทำงานได้เพราะคุณสามารถระบุดัชนี 1 โดยทำ inspect.stack () [0] [3] ให้ผลตอบแทนกับ <lambda>
kikones34

20

ฉันคิดว่าinspectเป็นวิธีที่ดีที่สุดในการทำเช่นนี้ ตัวอย่างเช่น:

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

17

ฉันพบเสื้อคลุมที่จะเขียนชื่อฟังก์ชั่น

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

สิ่งนี้จะพิมพ์

my_funky_name

STUB


1
ในฐานะนักออกแบบมัณฑนากรฉันสงสัยว่ามีวิธีการเข้าถึง func .__ name__ ในบริบทของ my_funky_name (ดังนั้นฉันสามารถดึงค่าของมันและใช้มันใน my_funky_name)
cowbert

วิธีที่จะทำอย่างนั้นภายในฟังก์ชัน my_funky_name my_funky_name.__name__คือ คุณสามารถส่ง func .__ name__ ไปยังฟังก์ชันเป็นพารามิเตอร์ใหม่ func (* args, ** kwargs, my_name = func .__ name__) ในการเรียกชื่อมัณฑนากรของคุณจากภายในฟังก์ชั่นของคุณฉันคิดว่าต้องใช้การตรวจสอบ แต่การได้รับชื่อของฟังก์ชั่นที่ควบคุมฟังก์ชั่นของฉันภายในฟังก์ชั่นการวิ่งของฉัน ... ดีที่ฟังดูเหมือนเป็นจุดเริ่มต้นของ meme ที่สวยงาม :)
cad106uk

15

อันนี้มาจากคำตอบอื่น ๆ ของคำถาม

นี่คือของฉัน:

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

ข้อดีที่เป็นไปได้ของรุ่นนี้มากกว่าการใช้ inspect.stack () คือควรเร็วกว่านับพันครั้ง [ดูโพสต์และการกำหนดเวลาของ Alex Melihoff เกี่ยวกับการใช้ sys._getframe () กับการใช้ inspect.stack ()



11

นี่คือวิธีการพิสูจน์ในอนาคต

การรวมคำแนะนำของ @ CamHart's และ @ Yuval เข้ากับคำตอบที่ได้รับการยอมรับของ @ RoshOxymoron มีประโยชน์ในการหลีกเลี่ยง:

  • _hidden และวิธีการที่อาจเลิกใช้แล้ว
  • การจัดทำดัชนีลงในสแต็ก (ซึ่งสามารถจัดลำดับใหม่ใน pythons ในอนาคต)

ดังนั้นฉันคิดว่านี่เล่นได้ดีกับเวอร์ชันงูหลามในอนาคต (ทดสอบบน 2.7.3 และ 3.3.2):

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))

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

ทดสอบ:

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

เอาท์พุท:

test_class_func_name
test_func_name

11

ฉันไม่แน่ใจว่าทำไมคนถึงทำให้มันซับซ้อน:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))

อาจเป็นเพราะคุณกำลังใช้งานฟังก์ชั่นส่วนตัวของโมดูล sys อยู่ด้านนอก โดยทั่วไปถือว่าเป็นการปฏิบัติที่ไม่ดีใช่หรือไม่?
tikej

@tikej การใช้งานเห็นด้วยกับแนวปฏิบัติที่ดีที่สุดที่ระบุไว้ที่นี่: 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()

ใน IDE รหัสเอาท์พุท

สวัสดีฉัน foo พ่อคือ

สวัสดีฉันคือบาร์พ่อเป็นคนฟู

สวัสดีฉันคือบาร์พ่อคือ


8

คุณสามารถใช้มัณฑนากร:

def my_function(name=None):
    return name

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

>>> get_function_name(my_function)
'my_function'

การตอบคำถามของโปสเตอร์เป็นอย่างไร? คุณสามารถขยายสิ่งนี้เพื่อรวมตัวอย่างว่าชื่อฟังก์ชันเป็นที่รู้จักจากภายในฟังก์ชันได้อย่างไร
parvus

@parvus: คำตอบของฉันตามที่เป็นตัวอย่างที่แสดงคำตอบสำหรับคำถามของ OP
Douglas Denhartog

ตกลง my_function เป็นฟังก์ชั่นของผู้ใช้แบบสุ่มของ OP ตำหนิสิ่งนี้กับการขาดความเข้าใจในการตกแต่ง ที่ไหน @? วิธีนี้ใช้ได้ผลกับฟังก์ชั่นที่คุณไม่ต้องการปรับอาร์กิวเมนต์? ฉันเข้าใจวิธีแก้ปัญหาของคุณอย่างไร: เมื่อฉันต้องการทราบชื่อฟังก์ชั่นฉันต้องผนวกมันด้วย @get_function_name และเพิ่มอาร์กิวเมนต์ชื่อโดยหวังว่ามันจะไม่ได้มีไว้เพื่อวัตถุประสงค์อื่น ฉันอาจจะพลาดบางสิ่งบางอย่างขอโทษสำหรับสิ่งนั้น
parvus

โดยไม่ต้องเริ่มต้นหลักสูตร ธ ของฉันเองภายในความคิดเห็น: 1. ฟังก์ชั่นเป็นวัตถุ; 2. คุณสามารถแนบ attribute name เข้ากับฟังก์ชั่นพิมพ์ / บันทึกชื่อหรือทำสิ่งต่าง ๆ ด้วย "ชื่อ" ในมัณฑนากร 3. ผู้ตกแต่งสามารถแนบได้หลายวิธี (เช่น @ หรือในตัวอย่างของฉัน) 4. ผู้ตกแต่งสามารถใช้ @wraps และ / หรือเป็นคลาสของตัวเองได้ 5. ฉันสามารถไปต่อได้ แต่การเขียนโปรแกรมมีความสุข!
Douglas Denhartog

นี่ดูเหมือนเป็นวิธีที่ซับซ้อนในการเข้าถึง__name__คุณลักษณะของฟังก์ชัน การใช้ต้องรู้สิ่งที่คุณพยายามรับซึ่งดูเหมือนจะไม่เป็นประโยชน์กับฉันในกรณีง่าย ๆ ที่ฟังก์ชันไม่ได้ถูกกำหนดทันที
Avi

3

ฉันใช้วิธีการของตัวเองในการเรียกซูเปอร์อย่างปลอดภัยในสถานการณ์การรับมรดกหลายครั้ง (ฉันใส่รหัสทั้งหมด)

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

การใช้งานตัวอย่าง:

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

ทดสอบมัน:

a = C()
a.test()

เอาท์พุท:

called from C
called from A
called from B

ภายในแต่ละวิธีการตกแต่งที่ @with_name คุณสามารถเข้าถึงตัวเอง. _ fname__ เป็นชื่อฟังก์ชันปัจจุบัน


3

ฉันเพิ่งพยายามใช้คำตอบข้างต้นเพื่อเข้าถึง docstring ของฟังก์ชั่นจากบริบทของฟังก์ชั่นนั้น แต่เนื่องจากคำถามข้างต้นเป็นเพียงการส่งกลับสตริงชื่อมันไม่ทำงาน

โชคดีที่ฉันพบวิธีแก้ไขง่ายๆ ถ้าชอบฉันคุณต้องการอ้างถึงฟังก์ชั่นมากกว่าเพียงแค่รับสายแทนชื่อที่คุณสามารถใช้ eval () กับสตริงของชื่อฟังก์ชั่น

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

3

ฉันไม่แนะนำให้พึ่งพาองค์ประกอบสแต็ก หากมีคนใช้รหัสของคุณภายในบริบทที่แตกต่างกัน (เช่นล่ามไพ ธ อน) สแต็คของคุณจะเปลี่ยนและทำลายดัชนีของคุณ ([0] [3])

ฉันขอแนะนำบางอย่างให้คุณ:

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

0

มันค่อนข้างง่ายที่จะทำให้สำเร็จด้วยมัณฑนากร

>>> 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')
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.