Определить имя функции из этой функции (без использования traceback)


479

В Python, без использования tracebackмодуля, есть ли способ определить имя функции из этой функции?

Скажем, у меня есть модуль foo с функциональной панелью. При выполнении foo.bar(), есть ли способ для бара узнать имя бара? Или еще лучше, foo.barкак зовут?

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

6
Я не понимаю, почему принятый ответ не от Андреаса Янга (или любого другого, который показывает, как это сделать). Вместо этого принятый ответ: «Вы не можете сделать это», что кажется неправильным; единственным требованием к ОП не было использование traceback. Даже времена ответов и комментариев, кажется, не подтверждают это.
sancho.s ReinstateMonicaCellio

Ответы:


198

Python не имеет возможности доступа к функции или ее имени внутри самой функции. Это было предложено, но отклонено. Если вы не хотите играть со стеком самостоятельно, вы должны либо использовать, "bar"либо в bar.__name__зависимости от контекста.

Данное уведомление об отказе:

Этот ПКП отклонен. Неясно, как это должно быть реализовано или какой должна быть точная семантика в крайних случаях, и дано недостаточно важных вариантов использования. Ответ был в лучшем случае теплым.


17
inspect.currentframe()это один из таких способов.
Юваль

41
Сочетание подхода @ CamHart с @ Yuval позволяет избежать «скрытых» и потенциально устаревших методов в ответе @ RoshOxymoron, а также числовой индексации в стеке для ответа @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
hobs

2
Можно ли обобщить, почему он был отклонен?
Чарли Паркер

1
почему это выбранный ответ? Вопрос не в доступе к текущей функции или самому модулю, а в названии. И функции отслеживания стека / отладки уже имеют эту информацию.
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

47
Это здорово, потому что вы также [1][3]можете узнать имя звонящего.
Кос

58
Вы также можете использовать: print(inspect.currentframe().f_code.co_name)или получить имя вызывающего абонента: print(inspect.currentframe().f_back.f_code.co_name). Я думаю, что это должно быть быстрее, так как вы не получаете список всех кадров стека, как это inspect.stack()происходит.
Майкл

7
inspect.currentframe().f_back.f_code.co_nameне работает с декорированным методом, в то время inspect.stack()[0][3]как ...
Дастин Уайетт

4
Обратите внимание: inspect.stack () может привести к значительным потерям производительности, поэтому используйте его с осторожностью! На моем ручном ящике потребовалось 240 мс (по какой-то причине)
gardarh

Мне кажется, что нечто, присутствующее в механизме рекурсии Python, может быть использовано для более эффективной работы
Том Рассел

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,25 мс, 1,24 мс, 0,5 мкс, 0,16 мкс нормальное (не пифоническое :)) секунды соответственно (win7x64, python3.5.1)
Энтони Хаткинс

Точно так же никто не думает, что @ mbdevpl сумасшедший :) - Я отправил правку для вывода 3-го цикла, так как это не имело никакого смысла. Не знаю , если результат должен был быть 0.100 usecили , 0.199 usecно в любом случае - в сотни раз быстрее , чем варианты 1 и 2, сравнимых с опцией 4 (хотя Энтони Hatchkins нашел вариант 3 в три раза быстрее , чем вариант 4).
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 - это одно, а переменные, ссылающиеся на них, - другое.
Кос,

Иногда вы можете захотеть Foo2()распечатать Foo. Например: Foo2 = function_dict['Foo']; Foo2(). В этом случае Foo2 является указателем на функцию, возможно, для анализатора командной строки.
Харви

Какое значение имеет скорость?
Роберт С. Барт

2
Смысл скорости относительно чего? Есть ли ситуация, когда вам нужно иметь эту информацию в сложной ситуации в реальном времени или что-то?
bgporter

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

Я хотел очень похожую вещь, потому что я хотел поместить имя функции в строку журнала, которая вошла в ряд мест в моем коде. Возможно, это не лучший способ сделать это, но вот способ получить имя текущей функции.


2
Полностью работающий, просто с помощью sys, не нужно загружать больше модулей, но не так легко запомнить это: V
m3nda

@ erm3nda Посмотри мой ответ.
nerdfever.com

1
@ nerdfever.com Моя проблема не в том, чтобы создать функцию, а в том, чтобы не помнить, что поместить в эту функцию. Это нелегко запомнить, поэтому мне всегда нужно будет видеть примечание, чтобы построить это снова. Я постараюсь иметь в виду fдля кадра и coдля кода. Я не пользуюсь этим, так что лучше, если я просто
сохраню

24

Я держу эту удобную утилиту поблизости:

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

Применение:

myself()

1
Как это будет сделано с альтернативой, предложенной здесь? «Я = лямбда: sys._getframe (). f_code.co_name» не работает (вывод «<лямбда>»; я думаю, потому что результат определяется во время определения, а не позже во время вызова. Хм.
NYCeyes

2
@ prismalytics.io: Если вы называете себя (себя ()) и не просто используете его значение (себя), вы получите то, что ищете.
ijw

NYCeyes был прав, имя разрешается внутри лямбда-выражения, поэтому получается <лямбда>. Методы 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__). Чтобы получить имя вашего декоратора из вашей функции, я думаю, что для этого потребуется использовать inspect. Но получение названия функции, управляющей моей функцией, в рамках моей функции бега ... ну, это звучит как начало красивого мема :)
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 () состоит в том, что она должна быть в тысячи раз быстрее [см. Сообщение Алекса Мелихоффа о времени использования sys._getframe () по сравнению с использованием inspect.stack ()].



11

Вот подход, ориентированный на будущее.

Сочетание предложений @ CamHart и @ Yuval с принятым ответом @ RoshOxymoron позволяет избежать:

  • _hidden и потенциально устаревшие методы
  • индексирование в стек (которое может быть переупорядочено в будущих питонах)

Поэтому я думаю, что это хорошо работает с будущими версиями Python (протестировано на 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 код выводит

привет, я фу, папа

привет я бар, папа фу

привет, я бар, папа


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: мой ответ как есть - пример, демонстрирующий ответ на вопрос ОП
Дуглас Денхартог

Хорошо, my_function - это функция случайного пользователя в OP. Вините в этом мое непонимание декораторов. Где @? Как это будет работать для функций, аргументы которых вы не хотите адаптировать? Как я понимаю ваше решение: когда я хочу узнать имя функции, я должен добавить его с @get_function_name и добавить аргумент name, надеясь, что его уже нет для другой цели. Я, вероятно, что-то упустил, извините за это.
Парвус

Без запуска собственного курса Python внутри комментария: 1. функции являются объектами; 2. вы можете прикрепить атрибут name к функции, напечатать / записать имя в журнал или сделать любое количество вещей с «name» внутри декоратора; 3. декораторы могут быть прикреплены несколькими способами (например, @ или в моем примере); 4. декораторы могут использовать @wraps и / или сами быть классами; 5. Я мог бы продолжать, но счастливое программирование!
Дуглас Денхартог

Это просто выглядит как запутанный способ добраться до __name__атрибута функции. Использование требует знания того, что вы пытаетесь получить, что не кажется мне очень полезным в простых случаях, когда функции не определены на лету.
Ави

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 у вас есть доступ к self .__ fname__ в качестве текущего имени функции.


3

Недавно я попытался использовать приведенные выше ответы для доступа к строке документации функции из контекста этой функции, но поскольку приведенные выше вопросы возвращали только строку имени, это не сработало.

К счастью, я нашел простое решение. Если, как и я, вы хотите обратиться к функции, а не просто получить строку, представляющую имя, которую вы можете применить eval () к строке имени функции.

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

3

Я предлагаю не полагаться на элементы стека. Если кто-то использует ваш код в разных контекстах (например, интерпретатор Python), ваш стек изменится и нарушит ваш индекс ([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')
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.