Determina il nome della funzione dall'interno di quella funzione (senza usare traceback)


479

In Python, senza usare il tracebackmodulo, c'è un modo per determinare il nome di una funzione all'interno di quella funzione?

Supponiamo di avere un modulo con una barra delle funzioni. Durante l'esecuzione foo.bar(), c'è un modo per bar di conoscere il nome della barra? O meglio ancora, foo.baril nome?

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

6
Non capisco perché la risposta accettata non sia quella di Andreas Young (o di qualsiasi altra che mostri come farlo). Invece, la risposta accettata è "non puoi farlo", il che sembra sbagliato; l'unico requisito dell'OP non era l'utilizzo traceback. Nemmeno i tempi di risposte e commenti sembrano sostenerlo.
sancho.s ReinstateMonicaCellio il

Risposte:


198

Python non ha una funzione per accedere alla funzione o al suo nome all'interno della funzione stessa. È stato proposto ma respinto. Se non vuoi giocare tu stesso con la pila, dovresti usare "bar"o in bar.__name__base al contesto.

L'avviso di rifiuto dato è:

Questo PEP è stato respinto. Non è chiaro come dovrebbe essere implementato o quale dovrebbe essere la semantica precisa nei casi limite e non ci sono abbastanza casi d'uso importanti dati. la risposta è stata al massimo tiepida.


17
inspect.currentframe()è uno di questi.
Yuval,

41
Combinando l'approccio di @ CamHart con quelli di @ Yuval si evitano metodi "nascosti" e potenzialmente deprecati nella risposta di @ RoshOxymoron, nonché l'indicizzazione numerica nello stack per la risposta di @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
piani cottura

2
è possibile riassumere perché è stato respinto?
Charlie Parker,

1
perché questa è la risposta scelta? La domanda non riguarda l'accesso alla funzione corrente o al modulo stesso, ma solo il nome. E le funzionalità di stacktrace / debug hanno già queste informazioni.
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
Questo è fantastico perché puoi anche fare [1][3]per ottenere il nome del chiamante.
Kos,

58
Si potrebbe anche usare: print(inspect.currentframe().f_code.co_name)o per ottenere il nome del chiamante: print(inspect.currentframe().f_back.f_code.co_name). Penso che dovrebbe essere più veloce dal momento che non si recupera un elenco di tutti i frame dello stack inspect.stack().
Michael,

7
inspect.currentframe().f_back.f_code.co_namenon funziona con un metodo decorato mentre inspect.stack()[0][3]...
Dustin Wyatt,

4
Nota: inspect.stack () può comportare un sovraccarico di prestazioni elevate, quindi usalo con parsimonia! Per completare il mio braccio ci sono voluti 240 ms (per qualche motivo)
Gardarh

Mi sembra che qualcosa di presente nel meccanismo di ricorsione di Python potrebbe essere impiegato per farlo in modo più efficiente
Tom Russell,

160

Esistono alcuni modi per ottenere lo stesso risultato:

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)

Si noti che le inspect.stackchiamate sono migliaia di volte più lente delle alternative:

$ 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()sembra un buon compromesso tra i tempi di esecuzione e l'uso di membri privati
FabienAndre,

1
@mbdevpl I miei numeri sono 1,25 ms, 1,24 ms, 0,5 us, 0,16us normale (non pitonico :)) secondi di conseguenza (win7x64, python3.5.1)
Antony Hatchkins,

Solo così nessuno pensa che @ mbdevpl sia pazzo :) - Ho inviato una modifica per l'output della terza esecuzione, dal momento che non aveva alcun senso. Non ho idea se il risultato avrebbe dovuto essere 0.100 useco in 0.199 usecentrambi i casi: centinaia di volte più veloce delle opzioni 1 e 2, paragonabile all'opzione 4 (sebbene Antony Hatchkins abbia trovato l'opzione 3 tre volte più veloce dell'opzione 4).
Dwanderson,

Io uso sys._getframe().f_code.co_namesopra inspect.currentframe().f_code.co_namesemplicemente perché ho già importato il sysmodulo. È una decisione ragionevole? (considerando che le velocità sembrano abbastanza simili)
PatrickT

47

Puoi ottenere il nome che è stato definito usando l'approccio mostrato da @Andreas Jung , ma potrebbe non essere il nome con cui è stata chiamata la funzione:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Se questa distinzione sia importante per te o no, non posso dire.


3
Stessa situazione con .func_name. Vale la pena ricordare che i nomi di classe e di funzione in Python sono una cosa e le variabili che si riferiscono ad esse sono un'altra.
Kos,

A volte potresti voler Foo2()stampare Foo. Ad esempio: Foo2 = function_dict['Foo']; Foo2(). In questo caso, Foo2 è un puntatore a funzione forse per un parser da riga di comando.
Harvey,

Che tipo di implicazione sulla velocità ha questo?
Robert C. Barth,

2
Implicazione della velocità rispetto a cosa? C'è una situazione in cui dovresti avere queste informazioni in una situazione difficile in tempo reale o qualcosa del genere?
bgporter,

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

Volevo qualcosa di molto simile perché volevo inserire il nome della funzione in una stringa di registro che andava in diverse posizioni nel mio codice. Probabilmente non è il modo migliore per farlo, ma ecco un modo per ottenere il nome della funzione corrente.


2
Totalmente funzionante, solo usando sys, non è necessario caricare più moduli, ma non è così facile ricordarselo: V
m3nda

@ erm3nda Vedi la mia risposta.
nerdfever.com

1
@ nerdfever.com Il mio problema non è quello di creare una funzione, è di non ricordare cosa mettere dentro quella funzione. Non è facile da ricordare, quindi avrò sempre bisogno di vedere qualche nota per costruirlo di nuovo. Cercherò di tenere a mente fper frame e coper il codice. Non lo uso tanto, tanto meglio se lo salvo in qualche frammento :-)
m3nda,

24

Tengo questa utile utility nelle vicinanze:

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

Uso:

myself()

1
Come si farebbe con l'alternativa proposta qui? "myself = lambda: sys._getframe (). f_code.co_name" non funziona (l'output è "<lambda>"; penso perché il risultato è determinato al momento della definizione, non successivamente al momento della chiamata. Hmm.
NYCeyes

2
@ prismalytics.io: se mi chiami (me stesso ()) e non usi solo il suo valore (me stesso), otterrai ciò che stai cercando.
ijw,

NYCeyes aveva ragione, il nome è stato risolto all'interno della lambda e quindi il risultato è <lambda>. I metodi sys._getframe () e inspect.currentframe () DEVONO essere eseguiti direttamente all'interno della funzione di cui si desidera ottenere il nome. Il metodo inspect.stack () funziona perché puoi specificare l'indice 1, facendo inspect.stack () [0] [3] produce anche <lambda>.
kikones34

20

Immagino inspectsia il modo migliore per farlo. Per esempio:

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

17

Ho trovato un wrapper che scriverà il nome della funzione

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

Questo stamperà

my_funky_name

STUB


1
In qualità di decoratore, mi chiedo se c'è un modo per accedere a func .__ name__ nel contesto di my_funky_name (in modo da poterne recuperare il valore e usarlo all'interno di my_funky_name)
cowbert,

Il modo per farlo all'interno della funzione my_funky_name è my_funky_name.__name__. È possibile passare il nome funzione .__ nella funzione come nuovo parametro. func (* args, ** kwargs, my_name = func .__ name__). Per ottenere il nome del tuo decoratore all'interno della tua funzione, penso che richiederebbe l'uso di inspect. Ma ottenere il nome della funzione che controlla la mia funzione all'interno della mia funzione di corsa ... beh, sembra solo l'inizio di un bellissimo meme :)
cad106uk,

15

Questo è in realtà derivato dalle altre risposte alla domanda.

Ecco la mia opinione:

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

Il probabile vantaggio di questa versione rispetto all'utilizzo di inspect.stack () è che dovrebbe essere migliaia di volte più veloce [vedi il post e i tempi di Alex Melihoff sull'uso di sys._getframe () rispetto all'utilizzo di inspect.stack ()].


12

print(inspect.stack()[0].function) sembra funzionare anche (Python 3.5).


11

Ecco un approccio a prova di futuro.

La combinazione dei suggerimenti di @ CamHart e @ Yuval con la risposta accettata di @ RoshOxymoron ha il vantaggio di evitare:

  • _hidden e metodi potenzialmente deprecati
  • indicizzazione nello stack (che potrebbe essere riordinato in pitoni futuri)

Quindi penso che questo funzioni bene con le future versioni di Python (testate su 2.7.3 e 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())

Test:

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

Produzione:

test_class_func_name
test_func_name


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

Nell'IDE il codice viene generato

ciao, sono pazzo, papà lo è

ciao, sono bar, papà è pazzo

ciao, sono bar, papà lo è


8

Puoi usare un decoratore:

def my_function(name=None):
    return name

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

>>> get_function_name(my_function)
'my_function'

Come risponde alla domanda del poster? Puoi espanderlo per includere un esempio di come il nome della funzione è noto all'interno della funzione?
parvus

@parvus: la mia risposta così com'è è un esempio che dimostra una risposta alla domanda di OP
Douglas Denhartog

Ok, my_function è la funzione dell'utente casuale dell'OP. Dai la colpa alla mia mancanza di comprensione dei decoratori. Dove il @? Come funzionerà per le funzioni di cui non vuoi adattare gli argomenti? Come capisco la tua soluzione: quando voglio conoscere il nome della funzione, devo aggiungerlo con @get_function_name e aggiungere l'argomento name, sperando che non sia già lì per un altro scopo. Probabilmente mi manca qualcosa, mi dispiace per quello.
parvus

Senza iniziare il mio corso Python all'interno di un commento: 1. le funzioni sono oggetti; 2. è possibile allegare un attributo name alla funzione, stampare / registrare il nome o fare un numero qualsiasi di cose con il "nome" all'interno del decoratore; 3. i decoratori possono essere collegati in più modi (ad es. @ O nel mio esempio); 4. i decoratori possono usare @wraps e / o essere classi stesse; 5. Potrei continuare, ma buona programmazione!
Douglas Denhartog,

Questo sembra solo un modo contorto per arrivare __name__all'attributo di una funzione. L'utilizzo richiede di conoscere la cosa che stai cercando di ottenere, il che non mi sembra molto utile in casi semplici in cui le funzioni non sono definite al volo.
Avi

3

Faccio il mio approccio usato per chiamare super con sicurezza all'interno di uno scenario di ereditarietà multipla (ho messo tutto il codice)

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

utilizzo del campione:

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

testandolo:

a = C()
a.test()

produzione:

called from C
called from A
called from B

All'interno di ogni metodo decorato @with_name hai accesso a self .__ fname__ come nome della funzione corrente.


3

Di recente ho provato a utilizzare le risposte di cui sopra per accedere al docstring di una funzione dal contesto di quella funzione, ma poiché le domande precedenti restituivano solo la stringa del nome, non funzionava.

Fortunatamente ho trovato una soluzione semplice. Se come me, vuoi fare riferimento alla funzione anziché semplicemente ottenere la stringa che rappresenta il nome, puoi applicare eval () alla stringa del nome della funzione.

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

3

Suggerisco di non fare affidamento sugli elementi dello stack. Se qualcuno usa il tuo codice in contesti diversi (ad esempio interprete python) il tuo stack cambierà e spezzerà il tuo indice ([0] [3]).

Ti suggerisco una cosa del genere:

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

Questo è abbastanza facile da realizzare con un decoratore.

>>> 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')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.