Xác định tên hàm từ bên trong hàm đó (không sử dụng truy nguyên)


479

Trong Python, không sử dụng tracebackmô-đun, có cách nào để xác định tên của hàm từ bên trong hàm đó không?

Nói rằng tôi có một mô-đun foo với một thanh chức năng. Khi thực hiện foo.bar(), có cách nào để thanh biết tên của thanh không? Hoặc tốt hơn, foo.bartên của?

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

6
Tôi không hiểu tại sao câu trả lời được chấp nhận không phải là từ Andreas Young (hoặc bất kỳ câu trả lời nào khác cho thấy cách thực hiện). Thay vào đó, câu trả lời được chấp nhận là "bạn không thể làm điều đó", điều này có vẻ sai; yêu cầu duy nhất của OP là không sử dụng traceback. Thậm chí không có thời gian của câu trả lời và ý kiến ​​dường như ủng hộ nó.
sancho.s RebstateMonicaCellio

Câu trả lời:


198

Python không có tính năng để truy cập hàm hoặc tên của nó trong chính hàm đó. Nó đã được đề xuất nhưng bị từ chối. Nếu bạn không muốn tự chơi với ngăn xếp, bạn nên sử dụng "bar"hoặc bar.__name__tùy theo ngữ cảnh.

Thông báo từ chối đưa ra là:

PEP này bị từ chối. Không rõ nó nên được thực hiện như thế nào hoặc ngữ nghĩa chính xác nên là gì trong các trường hợp cạnh và không có đủ các trường hợp sử dụng quan trọng được đưa ra. phản ứng đã được ấm áp tốt nhất.


17
inspect.currentframe()là một cách như vậy
Yuval

41
Kết hợp cách tiếp cận của @ CamHart với @ Yuval tránh các phương thức "ẩn" và có khả năng bị phản đối trong câu trả lời của @ RoshOxymoron cũng như lập chỉ mục số vào ngăn xếp cho câu trả lời của @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
hobs

2
Có thể tóm tắt lý do tại sao nó bị từ chối?
Charlie Parker

1
Tại sao đây là câu trả lời được chọn? Câu hỏi không phải là về việc truy cập chức năng hiện tại hoặc chính mô-đun, chỉ là tên. Và các tính năng stacktrace / debugging đã có thông tin này.
Nurettin

410
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
Điều này thật tuyệt vì bạn cũng có thể làm [1][3]để có được tên người gọi.
Kos

57
Bạn cũng có thể sử dụng: print(inspect.currentframe().f_code.co_name)hoặc để lấy tên người gọi : print(inspect.currentframe().f_back.f_code.co_name). Tôi nghĩ rằng nó sẽ nhanh hơn vì bạn không lấy danh sách tất cả các khung ngăn xếp inspect.stack().
Michael

7
inspect.currentframe().f_back.f_code.co_namekhông hoạt động với phương pháp trang trí trong khi inspect.stack()[0][3]...
Dustin Wyatt

4
Xin lưu ý: tests.stack () có thể phải chịu chi phí hoạt động nặng vì vậy hãy sử dụng một cách tiết kiệm! Trên hộp tay của tôi mất 240ms để hoàn thành (vì một số lý do)
vườn

Dường như với tôi như một cái gì đó hiện diện trong bộ máy đệ quy Python có thể được sử dụng để làm việc này hiệu quả hơn
Tom Russell

160

Có một số cách để có được kết quả tương tự:

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)

Lưu ý rằng các inspect.stackcuộc gọi chậm hơn hàng ngàn lần so với các lựa chọn thay thế:

$ 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()có vẻ như một sự đánh đổi tốt giữa thời gian thực hiện và sử dụng các thành viên tư nhân
FabienAndre

1
@mbdevpl Số của tôi là 1,25ms, 1,24ms, 0,5us, 0,16us bình thường (nonpythonic :)) giây tương ứng (win7x64, python3.5.1)
Antony Hatchkins

Chỉ để không ai nghĩ rằng @ mbdevpl là điên :) - Tôi đã gửi một bản chỉnh sửa cho đầu ra của lần chạy thứ 3, vì nó không có ý nghĩa gì. Không biết kết quả có nên 0.100 usechay không, 0.199 usecnhưng bằng cách nào - nhanh hơn hàng trăm lần so với tùy chọn 1 và 2, có thể so sánh với tùy chọn 4 (mặc dù Antony Hatchkins đã tìm thấy tùy chọn 3 nhanh hơn ba lần so với tùy chọn 4).
lùn

Tôi sử dụng sys._getframe().f_code.co_nametrên inspect.currentframe().f_code.co_nameđơn giản chỉ vì tôi đã nhập khẩu các sysmô-đun. Đó có phải là một quyết định hợp lý? (xem xét tốc độ xuất hiện khá giống nhau)
PatrickT

47

Bạn có thể lấy tên mà nó được xác định bằng cách sử dụng cách tiếp cận mà @Andreas Jung hiển thị , nhưng đó có thể không phải là tên mà hàm được gọi với:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Cho dù sự khác biệt đó có quan trọng với bạn hay không tôi không thể nói.


3
Tình trạng tương tự như với .func_name. Đáng nhớ rằng tên lớp và tên hàm trong Python là một thứ và các biến tham chiếu đến chúng là một thứ khác.
Kos

Đôi khi bạn có thể muốn Foo2()in Foo. Ví dụ : Foo2 = function_dict['Foo']; Foo2(). Trong trường hợp này, Foo2 là một con trỏ hàm có lẽ là một trình phân tích cú pháp dòng lệnh.
Harvey

Những loại ngụ ý tốc độ này có?
Robert C. Barth

2
Hàm ý tốc độ liên quan đến những gì? Có tình huống nào bạn cần có thông tin này trong tình huống khó khăn trong thời gian thực hay không?
bgporter

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

Tôi muốn một điều tương tự vì tôi muốn đặt tên hàm trong chuỗi nhật ký đã đi vào một số vị trí trong mã của tôi. Có lẽ không phải là cách tốt nhất để làm điều đó, nhưng đây là một cách để có được tên của hàm hiện tại.


2
Hoàn toàn làm việc, chỉ cần sử dụng sys, không cần tải thêm các mô-đun, nhưng không dễ để nhớ nó: V
m3nda

@ erm3nda Xem câu trả lời của tôi.
nerdfever.com

1
@ nerdfever.com Vấn đề của tôi không phải là tạo ra một chức năng, là không nhớ những gì cần đặt bên trong chức năng đó. Không dễ nhớ nên tôi sẽ luôn cần xem một số lưu ý để xây dựng lại. Tôi sẽ cố gắng ghi nhớ fcho khung và comã. Tôi không sử dụng nó nhiều như vậy sẽ tốt hơn nếu tôi lưu nó trong một số đoạn :-)
m3nda

24

Tôi giữ tiện ích tiện dụng này gần đây:

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

Sử dụng:

myself()

1
Làm thế nào điều này sẽ được thực hiện với sự thay thế được đề xuất ở đây? "Mình = lambda: sys._getframe (). f_code.co_name" không hoạt động (đầu ra là "<lambda>"; Tôi nghĩ vì kết quả được xác định tại thời điểm xác định, không muộn hơn vào thời gian cuộc gọi. Hmm.
NYCeyes

2
@ prismalytics.io: Nếu bạn tự gọi mình (mình ()) và không chỉ sử dụng giá trị của nó (bản thân mình), bạn sẽ nhận được những gì bạn đang tìm kiếm.
ijw

NYCeyes đã đúng, tên được giải quyết bên trong lambda và do đó kết quả là <lambda>. Các phương thức sys._getframe () và notify.cienframe () PHẢI được thực thi trực tiếp bên trong hàm bạn muốn lấy tên của. Phương thức tests.stack () hoạt động vì bạn có thể chỉ định chỉ số 1, thực hiện tests.stack () [0] [3] cũng mang lại <lambda>.
kikones34

20

Tôi đoán inspectlà cách tốt nhất để làm điều này. Ví dụ:

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

17

Tôi tìm thấy một trình bao bọc sẽ viết tên hàm

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

Cái này sẽ in

my_funky_name

SƠ KHAI


1
Là một người trang trí, tôi tự hỏi liệu có cách nào để truy cập func .__ name__ trong bối cảnh của my_funky_name (vì vậy tôi có thể truy xuất giá trị của nó và sử dụng nó trong my_funky_name)
cowbert

Cách để làm điều đó bên trong hàm my_funky_name là my_funky_name.__name__. Bạn có thể truyền func .__ name__ vào hàm dưới dạng tham số mới. func (* args, ** kwargs, my_name = func .__ name__). Để có được tên trang trí của bạn từ bên trong chức năng của bạn, tôi nghĩ rằng sẽ yêu cầu sử dụng kiểm tra. Nhưng nhận được tên của chức năng kiểm soát chức năng của tôi trong chức năng đang chạy của tôi ... nghe có vẻ như là sự khởi đầu của một meme đẹp :)
cad106uk

15

Điều này thực sự bắt nguồn từ các câu trả lời khác cho câu hỏi.

Đây là của tôi:

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

Ưu điểm có khả năng của phiên bản này so với việc sử dụng notify.stack () là nó sẽ nhanh hơn hàng nghìn lần [xem bài đăng và thời gian của Alex Melihoff về việc sử dụng sys._getframe () so với sử dụng notify.stack ()].


12

print(inspect.stack()[0].function) dường như cũng hoạt động (Python 3.5).


11

Đây là một cách tiếp cận bằng chứng trong tương lai.

Kết hợp các đề xuất của @ CamHart và @ Yuval với câu trả lời được chấp nhận của @ RoshOxymoron có lợi ích là tránh:

  • _hidden và các phương pháp có khả năng không dùng nữa
  • lập chỉ mục vào ngăn xếp (có thể được sắp xếp lại trong các con trăn trong tương lai)

Vì vậy, tôi nghĩ rằng điều này chơi tốt với các phiên bản python trong tương lai (được thử nghiệm trên 2.7.3 và 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())

Kiểm tra:

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

Đầu ra:

test_class_func_name
test_func_name

11

Tôi không chắc tại sao mọi người làm cho nó phức tạp:

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

có thể bởi vì bạn đang sử dụng chức năng riêng của mô-đun sys, bên ngoài nó. Nói chung, nó được coi là một thực hành xấu, phải không?
tikej

@tikej, việc sử dụng đồng ý với thực tiễn tốt nhất được nêu ở đây: docs.quantifiedcode.com/python-anti-potypes/c Corrness / Lỗi
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()

Trong IDE, đầu ra mã

xin chào, tôi là bố, bố là

xin chào, tôi là quán bar, bố là foo

xin chào, tôi là quán bar, bố là


8

Bạn có thể sử dụng một trang trí:

def my_function(name=None):
    return name

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

>>> get_function_name(my_function)
'my_function'

Làm thế nào là trả lời câu hỏi của người gửi? Bạn có thể mở rộng điều này để bao gồm một ví dụ về cách biết tên hàm từ bên trong hàm không?
parvus

@parvus: Câu trả lời của tôi như là là một ví dụ chứng minh rằng một câu trả lời cho câu hỏi OP của
Douglas Denhartog

Ok, my_feft là chức năng người dùng ngẫu nhiên của OP. Đổ lỗi cho sự thiếu hiểu biết của tôi về trang trí. @ Ở đâu? Làm thế nào điều này sẽ làm việc cho các chức năng mà các đối số bạn không muốn thích nghi? Làm thế nào tôi hiểu giải pháp của bạn: khi tôi muốn biết tên hàm, tôi phải nối nó với @get_feft_name và thêm đối số tên, hy vọng nó không có ở mục đích khác. Tôi có thể thiếu một cái gì đó, xin lỗi vì điều đó.
parvus

Không bắt đầu khóa học python của riêng tôi trong một nhận xét: 1. các hàm là các đối tượng; 2. bạn có thể đính kèm một thuộc tính name vào hàm, in / ghi tên hoặc thực hiện bất kỳ số lượng nào với "tên" bên trong trang trí; 3. trang trí có thể được đính kèm nhiều cách (ví dụ @ hoặc trong ví dụ của tôi); 4. người trang trí có thể sử dụng @wraps và / hoặc là chính các lớp; 5. Tôi có thể tiếp tục, nhưng, lập trình hạnh phúc!
Douglas Denhartog 30/03/19

Đây chỉ là một cách phức tạp để đi đến __name__thuộc tính của hàm. Việc sử dụng đòi hỏi phải biết thứ bạn đang cố gắng để có được, điều này dường như không hữu ích đối với tôi trong những trường hợp đơn giản khi các chức năng không được xác định một cách nhanh chóng.
Avi

3

Tôi thực hiện phương pháp của riêng mình được sử dụng để gọi siêu với sự an toàn bên trong nhiều kịch bản thừa kế (tôi đặt tất cả mã)

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

sử dụng mẫu:

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

kiểm tra nó:

a = C()
a.test()

đầu ra:

called from C
called from A
called from B

Bên trong mỗi phương thức trang trí @with_name, bạn có quyền truy cập vào chính mình .__ fname__ làm tên hàm hiện tại.


3

Gần đây tôi đã cố gắng sử dụng các câu trả lời ở trên để truy cập chuỗi doc của hàm từ ngữ cảnh của hàm đó nhưng vì các câu hỏi trên chỉ trả về chuỗi tên nên nó không hoạt động.

May mắn thay tôi tìm thấy một giải pháp đơn giản. Nếu giống như tôi, bạn muốn tham khảo hàm chứ không chỉ đơn giản là lấy chuỗi đại diện cho tên mà bạn có thể áp dụng eval () cho chuỗi của tên hàm.

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

3

Tôi đề nghị không dựa vào các yếu tố ngăn xếp. Nếu ai đó sử dụng mã của bạn trong các bối cảnh khác nhau (ví dụ trình thông dịch python), ngăn xếp của bạn sẽ thay đổi và phá vỡ chỉ mục của bạn ([0] [3]).

Tôi đề nghị bạn một cái gì đó như thế:

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

Điều này là khá dễ dàng để thực hiện với một trang trí.

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