web-dev-qa-db-fra.com

Python création de fonction dynamique avec des noms personnalisés

Toutes mes excuses si cette question a déjà été soulevée et répondue. Ce que je dois faire est très simple dans son concept, mais malheureusement je n'ai pas pu trouver de réponse en ligne.

J'ai besoin de créer des fonctions dynamiques dans Python (Python2.7) avec des noms personnalisés au moment de l'exécution. Le corps de chaque fonction doit également être construit au moment de l'exécution, mais il est (presque) le même pour tous les fonctions.

Je commence par une liste de noms.

func_names = ["func1", "func2", "func3"]

Notez que la liste func_name peut contenir une liste de noms arbitraires, donc les noms ne seront PAS simplement func1, func2, func3, ....

Je veux que le résultat soit:

    def func1(*args):
        ...

    def func2(*args):
        ...

    def func3(*args):
        ...

La raison pour laquelle je dois le faire est que chaque nom de fonction correspond à un cas de test qui est ensuite appelé depuis le monde extérieur.

mise à jour: Il n'y a pas d'entrée utilisateur. J'attache deux extrémités d'un module beaucoup plus gros. Une extrémité détermine ce que sont les cas de test et entre autres, remplit une liste des noms des cas de test. L'autre extrémité est les fonctions elles-mêmes, qui doivent avoir un mappage 1: 1 avec le nom du scénario de test. J'ai donc le nom des cas de test, je sais ce que je veux faire avec chaque cas de test, j'ai juste besoin de créer les fonctions qui ont le nom des cas de test. Étant donné que le nom des cas de test est déterminé au moment de l'exécution, la création de fonction basée sur ces cas de test doit également l'être au moment de l'exécution.

mise à jour: Je peux également encapsuler ces fonctions nommées personnalisées dans une classe si cela rend les choses plus faciles.

Je peux coder en dur le contenu des fonctions (car elles sont presque les mêmes) dans une chaîne, ou je peux le baser sur une classe de base précédemment définie. Il suffit de savoir comment remplir les fonctions avec ce contenu.

Par exemple:

    func_content = """
                   for arg in args:
                       print arg
                   """

Merci d'avance,

Mahdi

40
mahdiolfat

Pour ce que vous décrivez, je ne pense pas que vous ayez besoin de descendre dans eval ou dans des macros - la création d'instances de fonction par fermeture devrait fonctionner très bien. Exemple:

def bindFunction1(name):
    def func1(*args):
        for arg in args:
            print arg
        return 42 # ...
    func1.__= name
    return func1

def bindFunction2(name):
    def func2(*args):
        for arg in args:
            print arg
        return 2142 # ...
    func2.__= name
    return func2

Cependant, vous souhaiterez probablement ajouter ces fonctions par leur nom à une certaine portée afin de pouvoir y accéder par leur nom.

>>> print bindFunction1('neat')
<function neat at 0x00000000629099E8>
>>> print bindFunction2('keen')
<function keen at 0x0000000072C93DD8>
48
Shane Holloway

S'étendant sur la réponse de Shane puisque je viens de trouver cette question lorsque je cherche une solution à un problème similaire. Faites attention à la portée des variables. Vous pouvez éviter les problèmes de portée en utilisant une fonction de générateur pour définir la portée. Voici un exemple qui définit des méthodes sur une classe:

class A(object):
    pass

def make_method(name):
    def _method(self):
        print("method {0} in {1}".format(name, self))
    return _method

for name in ('one', 'two', 'three'):
    _method = make_method(name)
    setattr(A, name, _method)

Utilisé:

In [4]: o = A()

In [5]: o.one()
method one in <__main__.A object at 0x1c0ac90>

In [6]: o1 = A()

In [7]: o1.one()
method one in <__main__.A object at 0x1c0ad10>

In [8]: o.two()
method two in <__main__.A object at 0x1c0ac90>

In [9]: o1.two()
method two in <__main__.A object at 0x1c0ad10>
16
Paul Whipp

Il y a probablement une sorte d'introspection pour faire ce genre de chose, mais je ne pense pas que ce serait l'approche pythonique du problème.

Je pense que vous devriez profiter de la nature des fonctions dans python en tant que citoyens de premier niveau. Utilisez les fermetures comme Shane Holloway l'a souligné, pour personnaliser le contenu de la fonction comme vous le souhaitez. Ensuite, pour la liaison de nom dynamique, utilisez un dictionnaire dont les clés sont les noms définis dynamiquement, et les valeurs seront les fonctions elles-mêmes.

def function_builder(args):
    def function(more_args):
       #do stuff based on the values of args
    return function

my_dynamic_functions = {}
my_dynamic_functions[dynamic_name] = function_builder(some_dynamic_args)

#then use it somewhere else
my_dynamic_functions[dynamic_name](the_args)

J'espère que cela a du sens pour votre cas d'utilisation.

6
Facundo Olano

Vous voudrez peut-être utiliser eval ; vous allez construire la chaîne contenant la définition Python de chaque fonction (c'est-à-dire une chaîne multiligne commençant par def func1 ....) et vous allez alors eval it.

Mais je générerais un nom unique pour chacune de ces fonctions (par exemple genfun345). N'utilisez pas d'entrée utilisateur non contrôlée pour de tels noms. Parce que si le nom est le même qu'un nom connu en Python, vous allez entrer dans une catastrophe difficile à déboguer.

Faites-vous confiance aux entrées à partir desquelles ces fonctions sont créées? Vous souciez-vous des logiciels malveillants ou des abus?

Lisez à propos de macros hygiéniques sur wikipedia.

2

Si je comprends bien vos besoins, il semble que vous souhaitiez simplement attribuer dynamiquement des fonctions existantes à des noms nouveaux ou alternatifs - auquel cas quelque chose dans le sens suivant devrait faire le travail:

import sys

testcases = []
def testcase(f):
    """ testcase function decorator """
    testcases.append(f)
    return f

@testcase
def testcase0(*args):
    print 'testcase0 called, args:', args

@testcase
def testcase1(*args):
    print 'testcase1 called, args:', args

@testcase
def testcase2(*args):
    print 'testcase2 called, args:', args

def assign_function_names(func_names, namespace=None):
    if namespace is None:
        namespace = sys._getframe(1).f_globals  # default to caller's globals
    for name, func in Zip(func_names, testcases):
        func.__= name  # optional
        namespace[name] = func

assign_function_names(["funcA", "funcB", "funcC"])

funcA(1, 2, 3)
funcB(4, 5)
funcC(42)
1
martineau

Pour vraiment créer des fonctions dynamiquement, vous pouvez utiliser makefun , je l'ai développé juste pour ça. Il prend en charge trois façons de définir la signature à générer:

  • une représentation sous forme de chaîne, par exemple 'foo(a, b=1)'
  • un objet Signature, fabriqué à la main ou dérivé en utilisant inspect.signature sur une autre fonction
  • une fonction de référence. Dans ce cas, la signature exposée sera identique à la signature de cette fonction.

De plus, vous pouvez lui indiquer d'injecter la référence de la fonction créée comme premier argument dans votre implémentation, afin d'avoir des modifications de comportement mineures selon d'où vient l'appel (de quelle façade). Par exemple:

# generic core implementation
def generic_impl(f, *args, **kwargs):
    print("This is generic impl called by %s" % f.__name__)
    # here you could use f.__in a if statement to determine what to do
    if f.__== "func1":
        print("called from func1 !")
    return args, kwargs

my_module = getmodule(generic_impl)

# generate 3 facade functions with various signatures
for f_name, f_params in [("func1", "b, *, a"),
                         ("func2", "*args, **kwargs"),
                         ("func3", "c, *, a, d=None")]:
    # the signature to generate
    f_sig = "%s(%s)" % (f_name, f_params)

    # create the function dynamically
    f = create_function(f_sig, generic_impl, inject_as_first_arg=True)

    # assign the symbol somewhere (local context, module...)
    setattr(my_module, f_name, f)

# grab each function and use it
func1 = getattr(my_module, 'func1')
assert func1(25, a=12) == ((), dict(b=25, a=12))

func2 = getattr(my_module, 'func2')
assert func2(25, a=12) == ((25,), dict(a=12))

func3 = getattr(my_module, 'func3')
assert func3(25, a=12) == ((), dict(c=25, a=12, d=None))

Comme vous pouvez le voir dans le documentation , les arguments sont toujours redirigés vers le kwargs sauf lorsque cela n'est absolument pas possible (signatures var-positionnelles comme dans func2).

0
smarie