Déterminer le nom de la fonction à partir de cette fonction (sans utiliser traceback)

479

En Python, sans utiliser le tracebackmodule, existe-t-il un moyen de déterminer le nom d'une fonction à partir de cette fonction?

Disons que j'ai un module foo avec une barre de fonctions. Lors de l'exécution foo.bar(), y a-t-il un moyen pour bar de connaître le nom de la barre? Ou mieux encore, son foo.barnom?

#foo.py  
def bar():
    print "my name is", __myname__ # <== how do I calculate this at runtime?
Rob
la source
6
Je ne comprends pas pourquoi la réponse acceptée n'est pas celle d'Andreas Young (ou toute autre qui montre comment le faire). Au lieu de cela, la réponse acceptée est «vous ne pouvez pas faire ça», ce qui semble faux; la seule exigence du PO n'était pas utilisée traceback. Même le temps des réponses et des commentaires ne semble pas le soutenir.
sancho.s ReinstateMonicaCellio

Réponses:

198

Python n'a pas de fonctionnalité pour accéder à la fonction ou à son nom dans la fonction elle-même. Il a été proposé mais rejeté. Si vous ne voulez pas jouer avec la pile vous-même, vous devez utiliser "bar"ou en bar.__name__fonction du contexte.

L'avis de rejet donné est:

Ce PEP est rejeté. Il n'est pas clair comment cela devrait être implémenté ou quelle devrait être la sémantique précise dans les cas limites, et il n'y a pas assez de cas d'utilisation importants donnés. la réponse a été au mieux tiède.

Rosh Oxymoron
la source
17
inspect.currentframe()est une telle façon.
Yuval
41
La combinaison de l'approche de @ CamHart avec celle de @ Yuval évite les méthodes "cachées" et potentiellement obsolètes dans la réponse de @ RoshOxymoron ainsi que l'indexation numérique dans la pile de @ neuro / @ AndreasJung:print(inspect.currentframe().f_code.co_name)
plaques
2
est-il possible de résumer pourquoi il a été rejeté?
Charlie Parker
1
pourquoi est-ce la réponse choisie? La question ne concerne pas l'accès à la fonction actuelle ou au module lui-même, juste le nom. Et les fonctionnalités de stacktrace / débogage ont déjà ces informations.
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
Andreas Jung
la source
47
C'est très bien car vous pouvez également faire [1][3]pour obtenir le nom de l'appelant.
Kos
58
Vous pouvez également utiliser: print(inspect.currentframe().f_code.co_name)ou pour obtenir le nom de l'appelant: print(inspect.currentframe().f_back.f_code.co_name). Je pense que cela devrait être plus rapide car vous ne récupérez pas une liste de tous les cadres de pile comme le inspect.stack()fait.
Michael
7
inspect.currentframe().f_back.f_code.co_namene fonctionne pas avec une méthode décorée alors inspect.stack()[0][3]que ...
Dustin Wyatt
4
Veuillez noter: inspect.stack () peut entraîner des frais généraux élevés, donc utilisez-les avec parcimonie! Sur ma boîte de bras, il a fallu 240 ms pour terminer (pour une raison quelconque)
gardarh
Il me semble que quelque chose de présent dans la machinerie de récursion Python pourrait être utilisé pour le faire plus efficacement
Tom Russell
160

Il existe plusieurs façons d'obtenir le même résultat:

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)

Notez que les inspect.stackappels sont des milliers de fois plus lents que les alternatives:

$ 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
la source
5
inspect.currentframe()semble un bon compromis entre le temps d'exécution et l'utilisation de membres privés
FabienAndre
1
@mbdevpl Mes nombres sont de 1,25 ms, 1,24 ms, 0,5us, 0,16us normal (non pythonique :)) secondes en conséquence (win7x64, python3.5.1)
Antony Hatchkins
Juste pour que personne ne pense que @ mbdevpl est fou :) - J'ai soumis une modification pour la sortie de la troisième manche, car cela n'avait aucun sens. Aucune idée si le résultat aurait dû être 0.100 usecou 0.199 usecmais de toute façon - des centaines de fois plus rapide que les options 1 et 2, comparable à l'option 4 (bien qu'Antony Hatchkins ait trouvé l'option 3 trois fois plus rapide que l'option 4).
dwanderson
J'utilise sys._getframe().f_code.co_nameover inspect.currentframe().f_code.co_namesimplement parce que j'ai déjà importé le sysmodule. Est-ce une décision raisonnable? (considérant que les vitesses semblent assez similaires)
PatrickT
47

Vous pouvez obtenir le nom avec lequel il a été défini en utilisant l'approche indiquée par @Andreas Jung , mais ce n'est peut-être pas le nom avec lequel la fonction a été appelée:

import inspect

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

Foo2 = Foo

>>> Foo()
Foo

>>> Foo2()
Foo

Que cette distinction soit importante pour vous ou non, je ne peux pas le dire.

bgporter
la source
3
Même situation qu'avec .func_name. Il faut se rappeler que les noms de classe et les noms de fonction en Python sont une chose et que les variables qui s'y réfèrent en sont une autre.
Kos
Parfois, vous souhaiterez peut- Foo2()être imprimer Foo. Par exemple: Foo2 = function_dict['Foo']; Foo2(). Dans ce cas, Foo2 est un pointeur de fonction pour peut-être un analyseur de ligne de commande.
Harvey
Quel type d'implication de vitesse cela a-t-il?
Robert C.Barth
2
Implication de la vitesse par rapport à quoi? Y a-t-il une situation où vous auriez besoin d'avoir ces informations dans une situation difficile en temps réel ou quelque chose?
bgporter
44
functionNameAsString = sys._getframe().f_code.co_name

Je voulais une chose très similaire parce que je voulais mettre le nom de la fonction dans une chaîne de journal qui allait à plusieurs endroits dans mon code. Ce n'est probablement pas la meilleure façon de le faire, mais voici un moyen d'obtenir le nom de la fonction actuelle.

Ron Davis
la source
2
Fonctionnant totalement, en utilisant simplement sys, pas besoin de charger plus de modules, mais pas si facile de s'en souvenir: V
m3nda
@ erm3nda Voir ma réponse.
nerdfever.com
1
@ nerdfever.com Mon problème n'est pas de créer une fonction, c'est de ne pas me rappeler quoi mettre à l'intérieur de cette fonction. Ce n'est pas facile à retenir, donc j'aurai toujours besoin de voir une note pour le reconstruire. Je vais essayer de garder à l'esprit le fcadre et le cocode. Je ne l'utilise pas tellement, tant mieux si je l'enregistre dans un extrait :-)
m3nda
24

Je garde cet utilitaire à portée de main:

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

Usage:

myself()
xxyzzy
la source
1
Comment cela se ferait-il avec l'alternative proposée ici? "moi = lambda: sys._getframe (). f_code.co_name" ne fonctionne pas (la sortie est "<lambda>"; je pense que parce que le résultat est déterminé au moment de la définition, pas plus tard au moment de l'appel. Hmm.
NYCeyes
2
@ prismalytics.io: Si vous vous appelez (moi-même ()) et n'utilisez pas seulement sa valeur (moi-même), vous obtiendrez ce que vous cherchez.
ijw
NYCeyes avait raison, le nom est résolu à l'intérieur du lambda et donc le résultat est <lambda>. Les méthodes sys._getframe () et inspect.currentframe () DOIVENT être exécutées directement à l'intérieur de la fonction dont vous souhaitez obtenir le nom. La méthode inspect.stack () fonctionne parce que vous pouvez spécifier l'index 1, faire inspect.stack () [0] [3] donne également <lambda>.
kikones34
20

Je suppose que inspectc'est la meilleure façon de le faire. Par exemple:

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

J'ai trouvé un wrapper qui écrira le nom de la fonction

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

Cela imprimera

my_funky_name

TALON

cad106uk
la source
1
En tant que décorateur noob, je me demande s'il existe un moyen d'accéder à func .__ name__ dans le contexte de my_funky_name (afin que je puisse récupérer sa valeur et l'utiliser dans my_funky_name)
cowbert
La façon de le faire dans la fonction my_funky_name est my_funky_name.__name__. Vous pouvez passer le nom func .____ dans la fonction en tant que nouveau paramètre. func (* args, ** kwargs, my_name = func .__ name__). Pour obtenir le nom de votre décorateur à l'intérieur de votre fonction, je pense que cela nécessiterait d'inspecter. Mais obtenir le nom de la fonction contrôlant ma fonction au sein de ma fonction de course ... eh bien, ça sonne juste comme le début d'un beau mème :)
cad106uk
15

Ceci est en fait dérivé des autres réponses à la question.

Voici mon point de vue:

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

L'avantage probable de cette version par rapport à l'inspection.stack () est qu'elle devrait être des milliers de fois plus rapide [voir la publication d'Alex Melihoff et les délais concernant l'utilisation de sys._getframe () par rapport à l'utilisation d'inspect.stack ()].

Gino
la source
12

print(inspect.stack()[0].function) semble fonctionner aussi (Python 3.5).

Pierre Voisin
la source
11

Voici une approche évolutive.

La combinaison des suggestions de @ CamHart et @ Yuval avec la réponse acceptée de @ RoshOxymoron a l'avantage d'éviter:

  • _hidden et méthodes potentiellement obsolètes
  • indexation dans la pile (qui pourrait être réorganisée dans les futurs pythons)

Je pense donc que cela fonctionne bien avec les futures versions de python (testées sur 2.7.3 et 3.3.2):

from __future__ import print_function
import inspect

def bar():
    print("my name is '{}'".format(inspect.currentframe().f_code.co_name))
plaques de cuisson
la source
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())

Tester:

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

Production:

test_class_func_name
test_func_name
nordborn
la source
11

Je ne sais pas pourquoi les gens compliquent les choses:

import sys 
print("%s/%s" %(sys._getframe().f_code.co_filename, sys._getframe().f_code.co_name))
karthik r
la source
peut-être parce que vous utilisez une fonction privée du module sys, en dehors de celui-ci. En général, c'est considéré comme une mauvaise pratique, n'est-ce pas?
tikej
@tikej, l'utilisation est conforme aux meilleures pratiques énoncées ici: 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()

Dans IDE, les sorties de code

bonjour, je suis foo, papa est

bonjour, je suis bar, papa est foo

bonjour, je suis bar, papa est

Lee
la source
8

Vous pouvez utiliser un décorateur:

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
la source
Comment cela répond-il à la question de l'affiche? Pouvez-vous développer cela pour inclure un exemple de la façon dont le nom de la fonction est connu à l'intérieur de la fonction?
parvus
@parvus: ma réponse tout comme un exemple qui démontre une réponse à la question de l' OP
Douglas Denhartog
Ok, ma_fonction est la fonction d'utilisateur aléatoire de l'OP. C'est la faute de ma méconnaissance des décorateurs. Où le @? Comment cela fonctionnera-t-il pour les fonctions dont vous ne souhaitez pas adapter les arguments? Comment je comprends votre solution: quand je veux connaître le nom de la fonction, je dois l'ajouter avec @get_function_name, et ajouter l'argument nom, en espérant qu'il n'est pas déjà là pour un autre but. Il me manque probablement quelque chose, désolé pour cela.
parvus
Sans commencer mon propre cours de python dans un commentaire: 1. les fonctions sont des objets; 2. vous pouvez attacher un attribut de nom à la fonction, imprimer / enregistrer le nom ou faire un certain nombre de choses avec le "nom" à l'intérieur du décorateur; 3. les décorateurs peuvent être attachés de plusieurs façons (par exemple @ ou dans mon exemple); 4. les décorateurs peuvent utiliser @wraps et / ou être eux-mêmes des classes; 5. Je pourrais continuer, mais bonne programmation!
Douglas Denhartog
Cela ressemble à un moyen compliqué d'accéder à l' __name__attribut d'une fonction. L'utilisation nécessite de connaître la chose que vous essayez d'obtenir, ce qui ne me semble pas très utile dans les cas simples où les fonctions ne sont pas définies à la volée.
Avi
3

Je fais ma propre approche utilisée pour appeler super avec sécurité dans un scénario d'héritage multiple (je mets tout le code)

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

exemple d'utilisation:

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

le tester:

a = C()
a.test()

production:

called from C
called from A
called from B

À l'intérieur de chaque méthode décorée @with_name, vous avez accès à self .__ fname__ comme nom de fonction actuel.

Mel Viso Martinez
la source
3

J'ai récemment essayé d'utiliser les réponses ci-dessus pour accéder à la docstring d'une fonction à partir du contexte de cette fonction, mais comme les questions ci-dessus ne renvoyaient que la chaîne de nom, cela n'a pas fonctionné.

Heureusement, j'ai trouvé une solution simple. Si comme moi, vous voulez vous référer à la fonction plutôt que simplement obtenir la chaîne représentant le nom, vous pouvez appliquer eval () à la chaîne du nom de la fonction.

import sys
def foo():
    """foo docstring"""
    print(eval(sys._getframe().f_code.co_name).__doc__)
John Forbes
la source
3

Je suggère de ne pas compter sur des éléments de pile. Si quelqu'un utilise votre code dans différents contextes (interprète python par exemple), votre pile changera et cassera votre index ([0] [3]).

Je vous suggère quelque chose comme ça:

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
la source
0

C'est assez facile à réaliser avec un décorateur.

>>> 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
la source