Визначення назви функції з цієї функції (без використання прослідкування)


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__залежно від контексту.

Дане повідомлення про відхилення:

Цей PEP відхилено. Не зрозуміло, як це слід реалізувати або якою має бути точна семантика в кращих випадках, і недостатньо важливих випадків використання. реакція була в кращому випадку теплою.


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

41
Поєднання @ підходу CamHart з @ уникає Юваль в «прихованих» і потенційно застарілих методів в @ відповідь RoshOxymoron, а також чисельним індексації в стек для @ нейро / @ AndreasJung відповідають:print(inspect.currentframe().f_code.co_name)
варильні панелі

2
чи можна підсумувати, чому його відхилено?
Чарлі Паркер

1
чому це обрана відповідь? Питання не в тому, щоб отримати доступ до поточної функції або самого модуля, а лише назва. І функції стеки / відладки вже мають цю інформацію.
Нуреттін

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]ім'я абонента.
Кос

57
Ви також можете використовувати: 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 (хоча Ентоні Хеткінс знайшов варіант 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
Як би це було зроблено з запропонованою тут альтернативою? "Я = lambda: sys._getframe (). f_code.co_name" не працює (вихід "<lambda>"; я думаю, що результат визначається в час визначення, а не пізніше під час виклику. Хм.
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
Як декоратор noob, мені цікаво, чи є спосіб отримати доступ до func .__ name__ всередині контексту my_funky_name (тому я можу отримати його значення та використовувати його всередині my_funky_name)
cowbert

Спосіб зробити це всередині функції my_funky_name є my_funky_name.__name__. Ви можете передати функцію .__ name__ у функцію як новий параметр. func (* args, ** kwargs, my_name = func .__ name__). Щоб отримати назву ваших декораторів всередині вашої функції, я думаю, що це вимагатиме використання перевірки. Але отримання назви функції, яка контролює мою функцію в рамках моєї запущеної функції ... добре, що просто звучить як початок гарного мема :)
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 код виводить

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

Гаразд, моя_функція - це функція випадкового користувача ОП. Звинувачуйте це в моєму нерозумінні декораторів. Де @? Як це буде працювати для функцій, аргументи яких ви не хочете адаптувати? Як я розумію ваше рішення: коли я хочу знати ім’я функції, я повинен додати його до @get_function_name та додати аргумент імені, сподіваючись, що воно вже не існує для іншої мети. Я, певно, щось пропускаю, вибачте за це.
parvus

Не запускаючи власний курс пітона всередині коментаря: 1. функції - це об'єкти; 2. ви можете приєднати атрибут імені до функції, надрукувати / записати ім’я або зробити будь-яку кількість речей із "ім'ям" всередині декоратора; 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, ви маєте доступ до себе .__ fname__ як поточна назва функції.


3

Нещодавно я намагався використати вищезазначені відповіді для доступу до docstring функції з контексту цієї функції, але оскільки вищезазначені питання повертали лише рядок імені, це не спрацювало.

На щастя, я знайшов просте рішення. Якщо мені подобається, ви хочете звернутися до функції, а не просто отримати рядок, що представляє ім'я, яке ви можете застосувати 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.