web-dev-qa-db-fra.com

Comment obtenir la fonction courante dans une variable?

Comment puis-je obtenir une variable contenant la fonction en cours d'exécution dans Python? Je ne veux pas le nom de la fonction. Je sais que je peux utiliser inspect.stack pour obtenir le nom de la fonction actuelle. Je veux l'objet appelable réel. Cela peut-il être fait sans utiliser inspect.stack pour récupérer le nom de la fonction, puis evaling le nom pour obtenir l'objet appelable?

Edit: J'ai une raison de le faire, mais ce n'est même pas très bon. J'utilise plac pour analyser les arguments de la ligne de commande. Vous l'utilisez en faisant plac.call(main), qui génère un objet ArgumentParser à partir de la signature de fonction de "main". Dans "main", s'il y a un problème avec les arguments, je souhaite quitter avec un message d'erreur qui inclut le texte d'aide de l'objet ArgumentParser, ce qui signifie que je dois accéder directement à cet objet en appelant plac.parser_from(main).print_help(). Ce serait bien de pouvoir dire plutôt: plac.parser_from(get_current_function()).print_help(), de sorte que je ne compte pas sur la fonction nommée "main". Pour le moment, mon implémentation de "get_current_function" serait:

import inspect    
def get_current_function():
    return eval(inspect.stack()[1][3])

Mais cette implémentation repose sur le fait que la fonction a un nom, ce qui, je suppose, n’est pas trop onéreux. Je ne ferai jamais plac.call(lambda ...).

À long terme, il serait peut-être plus utile de demander à l'auteur de plac d'implémenter une méthode print_help pour imprimer le texte d'aide de la fonction la plus récemment appelée à l'aide de plac, ou quelque chose de similaire.

29
Ryan Thompson

Le cadre de pile nous indique dans quel objet de code nous nous trouvons. Si nous pouvons trouver un objet de fonction qui fait référence à cet objet de code dans son attribut __code__, nous avons trouvé la fonction.

Heureusement, nous pouvons demander au ramasse-miettes quels objets contiennent une référence à notre objet de code et les tamiser, plutôt que de devoir parcourir tous les objets actifs du monde Python. Il n'y a généralement qu'une poignée de références à un objet de code.

Désormais, les fonctions peuvent partager des objets de code et le faire dans le cas où vous renvoyez une fonction de une fonction, c'est-à-dire une fermeture. Lorsqu'il y a plus d'une fonction utilisant un objet de code donné, nous ne pouvons pas dire de quelle fonction il s'agit, nous retournons donc None.

import inspect, gc

def giveupthefunc():
    frame = inspect.currentframe(1)
    code  = frame.f_code
    globs = frame.f_globals
    functype = type(lambda: 0)
    funcs = []
    for func in gc.get_referrers(code):
        if type(func) is functype:
            if getattr(func, "__code__", None) is code:
                if getattr(func, "__globals__", None) is globs:
                    funcs.append(func)
                    if len(funcs) > 1:
                        return None
    return funcs[0] if funcs else None

Quelques cas de test:

def foo():
    return giveupthefunc()

zed = lambda: giveupthefunc()

bar, foo = foo, None

print bar()
print zed()

Je ne suis pas sûr des caractéristiques de performance de cette opération, mais je pense que cela devrait convenir à votre cas d'utilisation.

26
kindall

C'est ce que vous avez demandé, aussi proche que possible. Testé en versions python 2.4, 2.6, 3.0.

#!/usr/bin/python
def getfunc():
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    caller = caller.f_back
    from pprint import pprint
    func = caller.f_locals.get(
            func_name, caller.f_globals.get(
                func_name
        )
    )

    return func

def main():
    def inner1():
        def inner2():
            print("Current function is %s" % getfunc())
        print("Current function is %s" % getfunc())
        inner2()
    print("Current function is %s" % getfunc())
    inner1()


#entry point: parse arguments and call main()
if __== "__main__":
    main()

Sortie:

Current function is <function main at 0x2aec09fe2ed8>
Current function is <function inner1 at 0x2aec09fe2f50>
Current function is <function inner2 at 0x2aec0a0635f0>
14
bukzor

J'ai récemment passé beaucoup de temps à essayer de faire quelque chose comme ça et j'ai fini par m'en aller. Il y a beaucoup de cas de coin. 

Si vous souhaitez uniquement le niveau le plus bas de la pile d'appels, vous pouvez simplement référencer le nom utilisé dans l'instruction def. Cela sera lié à la fonction que vous voulez par la fermeture lexicale.

Par exemple:

def recursive(*args, **kwargs):
    me = recursive

me fera maintenant référence à la fonction en question, quelle que soit l'étendue depuis laquelle la fonction est appelée, à condition qu'elle ne soit pas redéfinie dans l'étendue où la définition est utilisée. Y a-t-il une raison pour que cela ne fonctionne pas?

Pour obtenir une fonction qui s'exécute plus haut dans la pile d'appels, je ne pouvais penser à rien qui puisse être fait de manière fiable.

12
aaronasterling

Voici une autre possibilité: un décorateur qui passe implicitement une référence à la fonction appelée en tant que premier argument (similaire à self dans les méthodes d'instance liée). Vous devez décorer chaque fonction pour laquelle vous souhaitez recevoir une telle référence, mais "explicite vaut mieux qu'implicite" comme on dit. 

Bien sûr, il présente tous les inconvénients des décorateurs: un autre appel de fonction dégrade légèrement les performances et la signature de la fonction encapsulée n'est plus visible.

import functools

def gottahavethatfunc(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(func, *args, **kwargs)

    return wrapper

Le scénario de test montre que la fonction décorée obtient toujours la référence à elle-même même si vous modifiez le nom auquel la fonction est liée. En effet, vous ne modifiez que la liaison de la fonction wrapper. Il illustre également son utilisation avec un lambda.

@gottahavethatfunc
def quux(me):
    return me

zoom = gottahavethatfunc(lambda me: me)

baz, quux = quux, None

print baz()
print zoom()

Lors de l'utilisation de ce décorateur avec une méthode d'instance ou de classe, la méthode doit accepter la référence de fonction comme premier argument et le self traditionnel comme second.

class Demo(object):

    @gottahavethatfunc
    def method(me, self):
        return me

print Demo().method()

Le décorateur s'appuie sur une fermeture pour conserver la référence à la fonction encapsulée dans l'emballage. La création directe de la fermeture peut en réalité être plus propre et ne pas entraîner la surcharge de l'appel de fonction supplémentaire:

def my_func():
    def my_func():
        return my_func
    return my_func
my_func = my_func()

Dans la fonction interne, le nom my_func fait toujours référence à cette fonction; sa valeur ne repose pas sur un nom global pouvant être modifié. Ensuite, nous "élevons" cette fonction dans l'espace de noms global, en remplaçant la référence à la fonction externe. Fonctionne aussi en classe:

class K(object):
    def my_method():
        def my_method(self):
             return my_method
        return my_method
    my_method = my_method()
9
kindall

Je viens de définir au début de chaque fonction un "mot clé" qui n'est qu'une référence au nom réel de la fonction. Je fais juste cela pour n'importe quelle fonction, si elle en a besoin ou non:

def test():
    this=test
    if not hasattr(this,'cnt'):
        this.cnt=0
    else:
        this.cnt+=1
    print this.cnt
6
Florian

La pile d'appels ne conserve pas de référence à la fonction elle-même - Bien que le cadre en cours soit utilisé comme référence à l'objet code correspondant au code associé à une fonction donnée.

(Les fonctions sont des objets avec du code et des informations sur leur environnement, telles que des fermetures, des noms, un dictionnaire de globaux, une chaîne de documentation, des paramètres par défaut, etc.).

Par conséquent, si vous exécutez une fonction standard, vous feriez mieux d'utiliser son propre nom dans le dictionnaire globals pour s'appeler lui-même, comme cela a été souligné.

Si vous exécutez un code dynamique ou lambda dans lequel vous ne pouvez pas utiliser le nom de la fonction, la seule solution consiste à reconstruire un autre objet fonction qui réutilise l'objet code en cours et appelle cette nouvelle fonction à la place.

Vous perdrez quelques éléments, tels que les arguments par défaut, et il peut être difficile de le faire fonctionner avec des fermetures (bien que cela puisse être fait).

C'est exactement ce que j'ai écrit dans un article de blog - appeler des fonctions anonymes à partir d'eux-mêmes - j'espère que le code qu'il contient peut vous aider:

http://metapython.blogspot.com/2010/11/recursive-lambda-functions.html

Sur une note de côté: évitez d'utiliser o inspect.stack - il est trop lent, car il reconstruit beaucoup d'informations à chaque appel. préférez utiliser inspect.currentframe pour traiter les images de code à la place.

Cela peut sembler compliqué, mais le code lui-même est très court - je le colle ci-dessous. Le message ci-dessus contient plus d'informations sur son fonctionnement.

from inspect import currentframe
from types import FunctionType

lambda_cache = {}

def myself (*args, **kw):
    caller_frame = currentframe(1)
    code = caller_frame.f_code
    if not code in lambda_cache:
        lambda_cache[code] = FunctionType(code, caller_frame.f_globals)
    return lambda_cache[code](*args, **kw)

if __== "__main__":
    print "Factorial of 5", (lambda n: n * myself(n - 1) if n > 1 else 1)(5)

Si vous avez réellement besoin de la fonction d'origine elle-même, la fonction "moi-même" ci-dessus peut être utilisée pour rechercher sur certaines étendues (comme le dictionnaire global de la fonction appelante) un objet fonction dont l'objet code correspondrait à celui extrait du cadre, au lieu de créer une nouvelle fonction.

4
jsbueno

sys._getframe (0) .f_code renvoie exactement ce dont vous avez besoin: le codeobject en cours d'exécution. Avec un objet code, vous pouvez récupérer un nom avec codeobject . Co_name

2
muodov

OK, après avoir lu la question et commenté à nouveau, je pense que c'est un cas test décent:

def foo(n):
  """ print numbers from 0 to n """
  if n: foo(n-1)
  print n

g = foo    # assign name 'g' to function object
foo = None # clobber name 'foo' which refers to function object
g(10)      # dies with TypeError because function object tries to call NoneType

J'ai essayé de le résoudre en utilisant un décorateur pour masquer temporairement l'espace de noms global et en réaffectant l'objet fonction au nom d'origine de la fonction:

def selfbind(f):
   """ Ensures that f's original function name is always defined as f when f is executed """
   oname = f.__name__
   def g(*args, **kwargs):

      # Clobber global namespace
      had_key = None
      if globals().has_key(oname):
         had_key = True
         key = globals()[oname]
      globals()[oname] = g

      # Run function in modified environment
      result = f(*args, **kwargs)

      # Restore global namespace
      if had_key: 
         globals()[oname] = key
      else:
         del globals()[oname]

      return result

   return g

@selfbind
def foo(n):
   if n: foo(n-1)
   print n

g = foo   # assign name 'g' to function object
foo = 2   # calling 'foo' now fails since foo is an int
g(10)     # print from 0..10, even though foo is now an int
print foo # prints 2 (the new value of Foo)

Je suis sûr de ne pas avoir réfléchi à tous les cas d'utilisation. Le plus gros problème que je vois est l'objet de la fonction qui modifie intentionnellement son propre nom (une opération qui serait écrasée par le décorateur), mais cela devrait être correct tant que la fonction récursive ne redéfinit pas son propre nom au milieu. de récursif.

Je ne suis toujours pas sûr d'avoir besoin de faire cela, mais y penser était intéressant.

1
Triptych

Voici une variante (Python 3.5.1) de la réponse get_referrers (), qui tente de distinguer les fermetures utilisant le même objet code:

import functools
import gc
import inspect

def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        set(inspect.getclosurevars(referer).nonlocals.items()) <=
        set(frame.f_locals.items())][0]

def f1(x):
    def f2(y):
        print(get_func())
        return x + y
    return f2

f_var1 = f1(1)
f_var1(3)
# <function f1.<locals>.f2 at 0x0000017235CB2C80>
# 4

f_var2 = f1(2)
f_var2(3)
# <function f1.<locals>.f2 at 0x0000017235CB2BF8>
# 5

def f3():
    print(get_func())    

f3()
# <function f3 at 0x0000017235CB2B70>

def wrapper(func):
    functools.wraps(func)
    def wrapped(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapped

@wrapper
def f4():
    print(get_func())    

f4()
# <function f4 at 0x0000017235CB2A60>

f5 = lambda: get_func()    

print(f5())
# <function <lambda> at 0x0000017235CB2950>
1
someone

Correction de ma réponse précédente, car le contrôle de sous-compte fonctionne déjà avec "<=" appelé sur dict_items et les appels supplémentaires de set () entraînent des problèmes, s'il existe des valeurs de dict qui sont elles-mêmes définies:

import gc
import inspect


def get_func():
    frame = inspect.currentframe().f_back
    code = frame.f_code
    return [
        referer
        for referer in gc.get_referrers(code)
        if getattr(referer, "__code__", None) is code and
        inspect.getclosurevars(referer).nonlocals.items() <=
        frame.f_locals.items()][0]
0
someone