Określ nazwę funkcji w obrębie tej funkcji (bez użycia traceback)

479

Czy w Pythonie, bez użycia tracebackmodułu, istnieje sposób na określenie nazwy funkcji z poziomu tej funkcji?

Powiedzmy, że mam moduł foo z paskiem funkcji. Czy podczas wykonywania foo.bar()paska można poznać jego nazwę? A może jeszcze lepiej foo.bar?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?
Obrabować
źródło
6
Nie rozumiem, dlaczego zaakceptowana odpowiedź nie pochodzi od Andreasa Younga (ani żadnej innej, która pokazuje, jak to zrobić). Zamiast tego przyjęta odpowiedź brzmi „nie możesz tego zrobić”, co wydaje się błędne; jedyny wymóg PO nie był wykorzystywany traceback. Wydaje się, że nawet czasy odpowiedzi i komentarzy nie potwierdzają tego.
sancho.s ReinstateMonicaCellio

Odpowiedzi:

198

Python nie ma funkcji dostępu do funkcji lub jej nazwy w samej funkcji. Zostało zaproponowane, ale odrzucone. Jeśli nie chcesz sam grać ze stosem, powinieneś użyć "bar"albo w bar.__name__zależności od kontekstu.

Podane powiadomienie o odrzuceniu to:

Ten PEP został odrzucony. Nie jest jasne, w jaki sposób powinien zostać zaimplementowany ani jaka powinna być precyzyjna semantyka w przypadkach skrajnych, i nie podano wystarczającej liczby ważnych przypadków użycia. odpowiedź była co najwyżej letnia.

Rosz Oxymoron
źródło
17
inspect.currentframe()jest jednym z takich sposobów.
Yuval,
41
Połączenie podejścia @ CamHart z @ Yuvalem pozwala uniknąć „ukrytych” i potencjalnie przestarzałych metod w odpowiedzi @ RoshOxymoron, a także indeksowania numerycznego w stosie dla odpowiedzi @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
płyty
2
czy można streścić, dlaczego został odrzucony?
Charlie Parker,
1
dlaczego jest to wybrana odpowiedź? Pytanie nie dotyczy dostępu do bieżącej funkcji lub samego modułu, tylko nazwę. A funkcje stosu śledzenia / debugowania mają już tę informację.
nurettin
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
Andreas Jung
źródło
47
Jest to świetne, ponieważ możesz również zrobić, [1][3]aby uzyskać nazwę dzwoniącego.
Kos,
57
Można również użyć: print(inspect.currentframe().f_code.co_name)albo dostać nazwisko osoby dzwoniącej: print(inspect.currentframe().f_back.f_code.co_name). Myślę, że powinno to być szybsze, ponieważ nie pobierasz listy wszystkich ramek stosu, jak to inspect.stack()robi.
Michael
7
inspect.currentframe().f_back.f_code.co_namenie działa z dekorowaną metodą, inspect.stack()[0][3]ale ...
Dustin Wyatt
4
Uwaga: inspect.stack () może powodować duże obciążenie ogólne, więc używaj oszczędnie! Na moim ramieniu zajęło 240 ms (z jakiegoś powodu)
gardarh
Wydaje mi się, że coś, co jest obecne w maszynie rekurencyjnej w Pythonie, może zostać wykorzystane do bardziej wydajnego działania
Tom Russell
160

Istnieje kilka sposobów na uzyskanie tego samego wyniku:

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)

Pamiętaj, że inspect.stackpołączenia są tysiące razy wolniejsze niż alternatywy:

$ 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
źródło
5
inspect.currentframe()wydaje się być dobrym kompromisem między czasem wykonania a użyciem prywatnych członków
FabienAndre
1
@mbdevpl Moje liczby to odpowiednio 1,25 ms, 1,24 ms, 0,5us, 0,16us normalny (niepythonic :)) sekund (win7x64, python3.5.1)
Antony Hatchkins
Tylko dlatego nikt nie myśli, że @ mbdevpl jest szalony :) - Przesłałem edycję wyników 3. uruchomienia, ponieważ nie miało to żadnego sensu. Nie mam pojęcia, czy wynik powinien już 0.100 usecalbo 0.199 usecale tak czy inaczej - setki razy szybciej niż w przypadku opcji 1 i 2, porównywalnych z opcją 4 (chociaż Antony Hatchkins znalezionego opcja 3 trzy razy szybciej niż opcja 4).
dwanderson
Używam sys._getframe().f_code.co_namepo inspect.currentframe().f_code.co_nameprostu dlatego, że już zaimportowałem sysmoduł. Czy to rozsądna decyzja? (biorąc pod uwagę, że prędkości wydają się dość podobne)
PatrickT
47

Nazwę, którą zdefiniowano, można uzyskać, stosując podejście pokazane przez @Andreas Jung , ale może to nie być nazwa, z którą funkcja została wywołana:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Czy to rozróżnienie jest dla ciebie ważne, czy nie, nie mogę powiedzieć.

bgporter
źródło
3
Taka sama sytuacja jak z .func_name. Warto pamiętać, że nazwy klas i nazwy funkcji w Pythonie to jedno, a odnoszące się do nich zmienne to inna.
Kos,
Czasami możesz chcieć Foo2()wydrukować Foo. Na przykład: Foo2 = function_dict['Foo']; Foo2(). W tym przypadku Foo2 jest wskaźnikiem funkcji dla być może parsera wiersza poleceń.
Harvey
Jaki to ma wpływ na prędkość?
Robert C. Barth
2
Implikacja prędkości w odniesieniu do czego? Czy istnieje sytuacja, w której trzeba mieć te informacje w trudnej sytuacji w czasie rzeczywistym lub coś takiego?
bporter
44
functionNameAsString = sys._getframe().f_code.co_name

Chciałem bardzo podobną rzecz, ponieważ chciałem umieścić nazwę funkcji w ciągu dziennika, który poszedł w wielu miejscach w moim kodzie. Prawdopodobnie nie jest to najlepszy sposób, aby to zrobić, ale oto sposób na uzyskanie nazwy bieżącej funkcji.

Ron Davis
źródło
2
Całkowicie działający, tylko używając sys, nie musi ładować więcej modułów, ale nie jest to takie łatwe do zapamiętania: V
m3nda
@ erm3nda Zobacz moją odpowiedź.
nerdfever.com
1
@ nerdfever.com Mój problem nie polega na utworzeniu funkcji, to na tym, że nie pamiętam, co w niej umieścić. Nie jest to łatwe do zapamiętania, więc zawsze będę musiał zobaczyć notatkę, aby zbudować ją ponownie. Spróbuję pamiętać fo ramce i cokodzie. Nie używam tego zbyt często, więc lepiej, jeśli tylko
zachowam
24

Trzymam to przydatne narzędzie w pobliżu:

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

Stosowanie:

myself()
xxyzzy
źródło
1
Jak można to zrobić z proponowaną tutaj alternatywą? „ja = lambda: sys._getframe (). f_code.co_name” nie działa (wynik to „<lambda>”; Myślę, że ponieważ wynik jest określany w czasie definicji, a nie w czasie połączenia. Hmm.
NYCeyes
2
@ prismalytics.io: Jeśli nazwiesz siebie (siebie ()) i nie wykorzystasz jej wartości (siebie), dostaniesz to, czego szukasz.
ijw
NYCeyes miał rację, nazwa została rozwiązana w lambda, a zatem wynikiem jest <lambda>. Metody sys._getframe () i inspect.currentframe () MUSZĄ być wykonywane bezpośrednio w funkcji, dla której chcesz uzyskać nazwę. Metoda inspect.stack () działa, ponieważ można określić indeks 1, wykonywanie inspect.stack () [0] [3] również daje <lambda>.
kikones34
20

Myślę, że inspectto najlepszy sposób na zrobienie tego. Na przykład:

import inspect
def bar():
    print("My name is", inspect.stack()[0][3])
Bjorn
źródło
17

Znalazłem opakowanie, które zapisze nazwę funkcji

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

To zostanie wydrukowane

my_funky_name

KIKUT

cad106uk
źródło
1
Jako dekorator noob zastanawiam się, czy istnieje sposób na uzyskanie dostępu do func .__ name__ w kontekście my_funky_name (dzięki czemu mogę odzyskać jego wartość i użyć go wewnątrz my_funky_name)
cowbert
Sposób na wykonanie tego w funkcji my_funky_name to my_funky_name.__name__. Możesz przekazać func .__ name__ do funkcji jako nowy parametr. func (* args, ** kwargs, my_name = func .__ name__). Aby uzyskać nazwę dekoratorów z wnętrza funkcji, myślę, że wymagałoby to użycia inspekcji. Ale uzyskanie nazwy funkcji kontrolującej moją funkcję w ramach mojej działającej funkcji ... cóż, to brzmi jak początek pięknego mema :)
cad106uk
15

Wynika to z innych odpowiedzi na pytanie.

Oto moje zdanie:

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

Prawdopodobną zaletą tej wersji w porównaniu z użyciem inspect.stack () jest to, że powinna być tysiące razy szybsza [zobacz post Alexa Melihoffa i terminy dotyczące korzystania z sys._getframe () w porównaniu z użyciem inspect.stack ()].

Gino
źródło
12

print(inspect.stack()[0].function) wydaje się również działać (Python 3.5).

Pierre Voisin
źródło
11

Oto podejście przyszłościowe.

Połączenie sugestii @ CamHart i @ Yuval z zaakceptowaną odpowiedzią @ RoshOxymoron ma tę zaletę, że pozwala uniknąć:

  • _hidden i potencjalnie przestarzałe metody
  • indeksowanie do stosu (które można zmienić w przyszłych pythonach)

Myślę więc, że gra to dobrze w przyszłych wersjach Pythona (testowanych na 2.7.3 i 3.3.2):

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))
płyty grzewcze
źródło
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())

Test:

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

Wynik:

test_class_func_name
test_func_name
Nordborn
źródło
11

Nie jestem pewien, dlaczego ludzie to komplikują:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))
karthik r
źródło
może dlatego, że używasz prywatnej funkcji modułu sys, poza nim. Ogólnie uważa się to za złą praktykę, prawda?
tikej
@tikej, użycie zgadza się z najlepszą praktyką podaną tutaj: 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()

W IDE kod wychodzi

cześć, jestem foo, tata jest

cześć, jestem barem, tata jest głupcem

cześć, jestem barem, tata jest

Zawietrzny
źródło
8

Możesz użyć dekoratora:

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
źródło
Jak to odpowiada na pytanie plakatu? Czy możesz to rozszerzyć o przykład, w jaki sposób nazwa funkcji jest znana z funkcji?
parvus
@parvus: moja obecna odpowiedź jest przykładem, który pokazuje odpowiedź na pytanie OP
Douglas Denhartog
Ok, moja_funkcja jest funkcją losowego użytkownika OP. Obwiniaj to za moje niezrozumienie dekoratorów. Gdzie @? Jak to zadziała dla funkcji, których argumentów nie chcesz dostosowywać? Jak rozumiem twoje rozwiązanie: kiedy chcę poznać nazwę funkcji, muszę dołączyć ją do @get_function_name i dodać argument name, mając nadzieję, że nie ma go już w innym celu. Prawdopodobnie coś mi brakuje, przepraszam za to.
Parvus
Bez uruchamiania własnego kursu python w komentarzu: 1. funkcje są obiektami; 2. możesz dołączyć atrybut funkcji do funkcji, wydrukować / zarejestrować nazwę lub wykonać dowolną liczbę czynności z „imieniem” wewnątrz dekoratora; 3. dekoratorzy mogą być podłączeni na wiele sposobów (np. @ Lub w moim przykładzie); 4. dekoratorzy mogą sami korzystać z @wraps i / lub być klasami; 5. Mógłbym kontynuować, ale miłego programowania!
Douglas Denhartog
To po prostu wygląda na zawiły sposób na uzyskanie __name__atrybutu funkcji. Korzystanie wymaga znajomości rzeczy, którą próbujesz uzyskać, co nie wydaje mi się bardzo przydatne w prostych przypadkach, w których funkcje nie są definiowane w locie.
Avi
3

Robię swoje własne podejście używane do wywoływania super z bezpieczeństwem w scenariuszu wielokrotnego dziedziczenia (umieszczam cały kod)

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

przykładowe użycie:

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

testowanie:

a = C()
a.test()

wynik:

called from C
called from A
called from B

Wewnątrz każdej dekorowanej metody @with_name masz dostęp do siebie .__ fname__ jako bieżąca nazwa funkcji.

Mel Viso Martinez
źródło
3

Niedawno próbowałem użyć powyższych odpowiedzi, aby uzyskać dostęp do dokumentacji funkcji z kontekstu tej funkcji, ale ponieważ powyższe pytania zwracały tylko ciąg nazwy, nie działało.

Na szczęście znalazłem proste rozwiązanie. Jeśli tak jak ja, chcesz odwoływać się do funkcji zamiast po prostu uzyskać ciąg reprezentujący nazwę, którą możesz zastosować eval () do ciągu nazwy funkcji.

import sys
def foo():
    """foo docstring"""
    print(eval(sys._getframe().f_code.co_name).__doc__)
John Forbes
źródło
3

Sugeruję, aby nie polegać na elementach stosu. Jeśli ktoś użyje twojego kodu w różnych kontekstach (na przykład interpreter Pythona), twój stos zmieni się i zepsuje Twój indeks ([0] [3]).

Sugeruję coś takiego:

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
źródło
0

Jest to dość łatwe do osiągnięcia dzięki dekoratorowi.

>>> 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
źródło