809 votes

Que signifie l'opérateur étoile et double étoile dans un appel de fonction ?

Que fait le * signifie en Python, par exemple dans un code tel que zip(*x) ou f(**k) ?

  1. Comment cela est-il géré en interne dans l'interpréteur ?
  2. Cela affecte-t-il les performances ? Est-il rapide ou lent ?
  3. Quand est-il utile et quand ne l'est-il pas ?
  4. Doit-on l'utiliser dans une déclaration de fonction ou dans un appel ?

0 votes

4 votes

Je pense que cela devrait être formulé comme la "*syntaxe d'appel de fonction". Ce ne sont pas des opérateurs, bien que cela puisse prêter à confusion car il y a est a * et ** qui n'ont rien à voir avec cette syntaxe.

1 votes

@Ian Bicking : vous avez tout à fait raison, * et ** dans la liste des arguments sont de la syntaxe pure (tokens).

1205voto

Lasse V. Karlsen Points 148037

L'étoile unique * décompose la séquence/collection en arguments positionnels, afin que vous puissiez faire ceci :

def sum(a, b):
    return a + b

values = (1, 2)

s = sum(*values)

Cela va décompresser le tuple de sorte qu'il s'exécute en tant que :

s = sum(1, 2)

L'étoile double ** fait la même chose, mais en utilisant un dictionnaire et donc des arguments nommés :

values = { 'a': 1, 'b': 2 }
s = sum(**values)

Vous pouvez également combiner :

def sum(a, b, c, d):
    return a + b + c + d

values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
s = sum(*values1, **values2)

s'exécutera comme :

s = sum(1, 2, c=10, d=15)

Voir également la section 4.7.4 - Dépaquetage des listes d'arguments de la documentation Python.


De plus, vous pouvez définir des fonctions pour prendre *x et **y arguments, cela permet à une fonction d'accepter un nombre quelconque d'arguments positionnels et/ou nommés qui ne sont pas spécifiquement nommés dans la déclaration.

Exemple :

def sum(*values):
    s = 0
    for v in values:
        s = s + v
    return s

s = sum(1, 2, 3, 4, 5)

ou avec ** :

def get_a(**values):
    return values['a']

s = get_a(a=1, b=2)      # returns 1

cela peut vous permettre de spécifier un grand nombre de paramètres facultatifs sans avoir à les déclarer.

Et encore une fois, vous pouvez combiner :

def sum(*values, **options):
    s = 0
    for i in values:
        s = s + i
    if "neg" in options:
        if options["neg"]:
            s = -s
    return s

s = sum(1, 2, 3, 4, 5)            # returns 15
s = sum(1, 2, 3, 4, 5, neg=True)  # returns -15
s = sum(1, 2, 3, 4, 5, neg=False) # returns 15

10 votes

Pourquoi en auriez-vous besoin, la fonction ne pourrait-elle pas simplement itérer sur la liste fournie sans qu'elle soit développée ?

38 votes

Bien sûr, mais il faudrait alors l'appeler : s = sum((1, 2, 3, 4, 5)) ou s = sum([1, 2, 3, 4, 5]) le *values donne l'impression que l'appel prend un certain nombre d'arguments, mais ceux-ci sont regroupés dans une collection pour le code de la fonction.

0 votes

Je ne pense pas. Remplacer if neg: avec if options['neg']: ou utiliser if options.get('neg', False) ,

51voto

Ned Batchelder Points 128913

Une petite précision : il ne s'agit pas d'opérateurs. Les opérateurs sont utilisés dans les expressions pour créer de nouvelles valeurs à partir de valeurs existantes (1+2 devient 3, par exemple. Les * et ** font ici partie de la syntaxe des déclarations et des appels de fonctions.

7 votes

Notez que la documentation Python appelle * dans ce contexte un opérateur ; je suis d'accord, c'est un peu trompeur.

0 votes

Merci. J'ai cherché une exposition claire de ceci dans la documentation de référence de python et je ne l'ai toujours pas vue. Donc la règle pour les appels de fonction est en gros qu'un "*" ou "**" qui est au début d'une expression dans un appel de fonction provoque cette sorte d'expansion ?

32voto

sepp2k Points 157757

Dans un appel de fonction, l'étoile simple transforme une liste en arguments distincts (par ex. zip(*x) est la même chose que zip(x1,x2,x3) si x=[x1,x2,x3] ) et la double étoile transforme un dictionnaire en arguments de mots-clés distincts (par ex. f(**k) est la même chose que f(x=my_x, y=my_y) si k = {'x':my_x, 'y':my_y} .

Dans une définition de fonction, c'est l'inverse : l'étoile simple transforme un nombre arbitraire d'arguments en une liste, et le double départ transforme un nombre arbitraire d'arguments de mots-clés en un dictionnaire. Par exemple def foo(*x) signifie "foo prend un nombre arbitraire d'arguments et ils seront accessibles par la liste x (c'est-à-dire que si l'utilisateur appelle foo(1,2,3) , x sera [1,2,3] )" et def bar(**k) signifie "bar prend un nombre arbitraire d'arguments de mots-clés et ils seront accessibles par le dictionnaire k (c'est-à-dire que si l'utilisateur appelle bar(x=42, y=23) , k sera {'x': 42, 'y': 23} )".

3 votes

Un commentaire (très) tardif, mais je crois que de def foo(*x) *x donne un tuple, pas une liste.

25voto

Donald Miner Points 18116

Je trouve cela particulièrement utile lorsque vous voulez "stocker" un appel de fonction.

Par exemple, supposons que j'ai des tests unitaires pour une fonction "add" :

def add(a, b): return a + b
tests = { (1,4):5, (0, 0):0, (-1, 3):3 }
for test, result in tests.items():
    print 'test: adding', test, '==', result, '---', add(*test) == result

Il n'y a pas d'autre moyen d'appeler add, à part en faisant manuellement quelque chose comme add(test[0], test[1]) ce qui est laid. De plus, s'il y a un nombre variable de variables, le code pourrait devenir assez laid avec toutes les déclarations if dont vous auriez besoin.

Une autre utilité est la définition des objets Factory (objets qui créent des objets pour vous). Supposons que vous ayez une classe Factory, qui crée des objets Car et les renvoie. Vous pouvez faire en sorte que myFactory.make_car('red', 'bmw', '335ix') crée Car('red', 'bmw', '335ix') puis le renvoie.

def make_car(*args):
    return Car(*args)

C'est également utile lorsque vous voulez appeler le constructeur d'une superclasse.

6 votes

J'aime vos exemples. Mais, je pense que -1 + 3 == 2.

9 votes

J'ai intentionnellement mis quelque chose là-dedans qui échouerait :)

20voto

Mark Byers Points 318575

On l'appelle la syntaxe d'appel étendue. De la documentation :

Si la syntaxe *expression apparaît dans l'appel de fonction, l'expression doit être évaluée comme une séquence. Les éléments de cette séquence sont traités comme s'ils étaient des arguments positionnels supplémentaires ; s'il y a des arguments positionnels x1,..., xN, et que l'expression évalue une séquence y1, ..., yM, ceci est équivalent à un appel avec M+N arguments positionnels x1, ..., xN, y1, ..., yM.

et :

Si la syntaxe **expression apparaît dans l'appel de fonction, expression doit être évaluée à un mapping, dont le contenu est traité comme des arguments supplémentaires du mot-clé. Dans le cas où un mot-clé apparaît à la fois dans l'expression et comme argument explicite de mot-clé, une exception TypeError est levée.

3 votes

J'ajoute juste une note de bas de page à la réponse du manuel : avant l'arrivée du support syntaxique, la même fonctionnalité était réalisée avec la fonction intégrée apply() fonction

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X