Appeler une fonction avec une liste d'arguments en python

150

J'essaie d'appeler une fonction dans une autre fonction en python, mais je ne trouve pas la bonne syntaxe. Ce que je veux faire est quelque chose comme ceci:

def wrapper(func, args):
    func(args)

def func1(x):
    print(x)

def func2(x, y, z):
    return x+y+z

wrapper(func1, [x])
wrapper(func2, [x, y, z])

Dans ce cas, le premier appel fonctionnera et le second ne fonctionnera pas. Ce que je veux modifier, c'est la fonction wrapper et non les fonctions appelées.

SurDin
la source

Réponses:

268

Pour développer un peu les autres réponses:

Dans la ligne:

def wrapper(func, *args):

Le * à côté de argssignifie "prendre le reste des paramètres donnés et les mettre dans une liste appelée args".

Dans la ligne:

    func(*args)

Le * à côté d' argsici signifie "prendre cette liste appelée args et la 'dérouler' dans le reste des paramètres.

Vous pouvez donc faire ce qui suit:

def wrapper1(func, *args): # with star
    func(*args)

def wrapper2(func, args): # without star
    func(*args)

def func2(x, y, z):
    print x+y+z

wrapper1(func2, 1, 2, 3)
wrapper2(func2, [1, 2, 3])

Dans wrapper2, la liste est passée explicitement, mais dans les deux wrappers argscontient la liste [1,2,3].

Itsadok
la source
26
Une chose que je n'ai pas trouvée mentionnée très souvent est comment appeler une fonction avec * args, si vous avez une liste ou un tuple que vous voulez passer. Pour cela, vous devez l'appeler comme ceci: wrapper1 (func2, * mylist)
Ali
* args in def wrapper(func, *args)est ce qui method(params object[] args)est en C #.
Jim Aho
1
Notez qu'il *argsdoit s'agir du dernier argument de la définition de fonction.
Jim Aho
2
The * next to args means "take the rest of the parameters given and put them in a list called args".Cela les met dans un tuple, pas dans une liste.
Tomasz Nocoń
20

La façon la plus simple d'encapsuler une fonction

    func(*args, **kwargs)

... est d'écrire manuellement un wrapper qui appellerait func () à l' intérieur de lui-même:

    def wrapper(*args, **kwargs):
        # do something before
        try:
            return func(*a, **kwargs)
        finally:
            # do something after

En Python, la fonction est un objet, vous pouvez donc passer son nom comme argument d'une autre fonction et le renvoyer. Vous pouvez également écrire un générateur de wrapper pour n'importe quelle fonction anyFunc () :

    def wrapperGenerator(anyFunc, *args, **kwargs):
        def wrapper(*args, **kwargs):
            try:
                # do something before
                return anyFunc(*args, **kwargs)
            finally:
                #do something after
        return wrapper

Veuillez également noter qu'en Python, lorsque vous ne connaissez pas ou ne voulez pas nommer tous les arguments d'une fonction, vous pouvez vous référer à un tuple d'arguments, qui est indiqué par son nom, précédé d'un astérisque entre parenthèses après le nom de la fonction:

    *args

Par exemple, vous pouvez définir une fonction qui prendrait n'importe quel nombre d'arguments:

    def testFunc(*args):
        print args    # prints the tuple of arguments

Python permet une manipulation encore plus poussée des arguments de fonction. Vous pouvez autoriser une fonction à prendre des arguments de mot-clé. Dans le corps de la fonction, les arguments de mot-clé sont conservés dans un dictionnaire. Dans les parenthèses après le nom de la fonction, ce dictionnaire est indiqué par deux astérisques suivis du nom du dictionnaire:

    **kwargs

Un exemple similaire qui imprime le dictionnaire des arguments de mot-clé:

    def testFunc(**kwargs):
        print kwargs    # prints the dictionary of keyword arguments
Alex
la source
11

Vous pouvez utiliser la syntaxe * args et ** kwargs pour les arguments de longueur variable.

Que signifient * args et ** kwargs?

Et du tutoriel officiel python

http://docs.python.org/dev/tutorial/controlflow.html#more-on-defining-functions

JimB
la source
Donc j'en ai besoin pour obtenir à la fois * args et ** kwargs et l'appeler avec eux?
SurDin
1
Non, vous pouvez utiliser l'un ou l'autre, mais ils sont souvent associés. Dans votre cas, vous n'avez besoin que de * args.
JimB
Ok, ça marche, mais ça ne me laisse toujours pas passer une liste d'arguments, et je dois les passer séparément. Cela ne me dérange pas beaucoup, dans ma situation actuelle, mais il serait toujours bon de savoir comment faire cela. (J'ai besoin de faire wrapper (func2, x, y, z) et pas wrapper (func2, [x, y, z]))
SurDin
Si ce dernier est ce que vous voulez, utilisez la forme * args lorsque le wrapper appelle func2, mais pas dans 'def wrapper'.
Alex Martelli
10

La réponse littérale à votre question (faire exactement ce que vous avez demandé, en changeant uniquement le wrapper, pas les fonctions ou les appels de fonction) est simplement de modifier la ligne

func(args)

lire

func(*args)

Cela indique à Python de prendre la liste donnée (dans ce cas, args) et de transmettre son contenu à la fonction en tant qu'arguments positionnels.

Cette astuce fonctionne des deux "côtés" de l'appel de fonction, donc une fonction définie comme ceci:

def func2(*args):
    return sum(args)

serait capable d'accepter autant d'arguments de position que vous lui lancez, et de les placer tous dans une liste appelée args.

J'espère que cela aidera à clarifier un peu les choses. Notez que tout cela est également possible avec les arguments dicts / mots-clés, en utilisant **au lieu de *.

Alan Rowarth
la source
8

Vous devez utiliser la décompression des arguments.

def wrapper(func, *args):
    func(*args)

def func1(x):
    print(x)

def func2(x, y, z):
    print x+y+z

wrapper(func1, 1)
wrapper(func2, 1, 2, 3)
Joril
la source
0

Un petit ajout aux réponses précédentes, car je n'ai pas trouvé de solution à un problème, ce qui ne vaut pas la peine d'ouvrir une nouvelle question, mais m'a conduit ici.

Voici un petit extrait de code, qui combine lists, zip()et *args, pour fournir un wrapper qui peut traiter un nombre inconnu de fonctions avec un nombre inconnu d'arguments.

def f1(var1, var2, var3):
    print(var1+var2+var3)

def f2(var1, var2):
    print(var1*var2)

def f3():
    print('f3, empty')

def wrapper(a,b, func_list, arg_list):
    print(a)
    for f,var in zip(func_list,arg_list):
        f(*var)
    print(b)

f_list = [f1, f2, f3]
a_list = [[1,2,3], [4,5], []]

wrapper('begin', 'end', f_list, a_list)

Gardez à l'esprit que zip()cela ne fournit pas de vérification de sécurité pour les listes de longueur inégale, voir les itérateurs de zip affirmant une longueur égale en python .

P. Siehr
la source