Passer des arguments sous forme d'un dictionnaire ou de paramètres nommés ?

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour,

Je travaille avec Python3.5 et j’ai une fonction qui fait deux appels à une API :

1
2
3
4
5
class Simulation:
    def step(self, steps, parameters=None):
        if parameters is not None:
            requests.post(self.endpoint("configure"), json=parameters)
        requests.post(self.endpoint("step"), json={"steps": steps})

Le nombre de paramètres est variable :

1
2
3
4
sim = Simulation()
sim.step(1, {"param1": 1})
sim.step(1, {"param2": 1})
sim.step(1, {"param1": 2, "param3": 2})

La question que je me pose est si l’interface suivante, avec des paramètres nommés plutôt qu’un dictionnaire, est préférable (plus pratique, plus idiomatique, etc.) :

1
2
3
4
5
6
7
8
9
class Simulation:
    def step(self, steps, **kwargs):
        requests.post(self.endpoint("configure"), json=kwargs)
        requests.post(self.endpoint("step"), json={"steps": steps})

sim = Simulation()
sim.step(1, param1=1)
sim.step(1, param2=1)
sim.step(1, param1=2, param3=2)

Merci.

Édité par Vayel

+0 -1

Cette réponse a aidé l’auteur du sujet

Salut,

Déjà, on sait qu’avec les opérateurs splat les deux versions sont plus ou moins équivalentes, il n’y en a pas une qui offre plus de possibilités que l’autre. Dans le cas où les clefs sont toutes des chaînes de caractères, bien sûr.

Partant de là, j’opterais plutôt pour la seconde solution, plus concise et plus claire à mes yeux. Le problème est si tu as des arguments dont le nom n’est pas un identifiant Python valide (là ça deviendrait un peu bizarre), ou si tu as des conflits avec les noms d’autres paramètres (steps).

Au passage, vu que l’appel à configure ne semble pas indispensable, autant ne pas le faire (dans un cas comme dans l’autre) si le dictionnaire des paramètres est vide.

Édité par entwanne

Salut ! J’aime bien ta première version car tout est clair, tu passes un dico à step directement repris dans la fonction requests.post.

1
2
3
4
5
class Simulation:
    def step(self, steps, parameters=None):
        if parameters:
            requests.post(self.endpoint("configure"), json=parameters)
        requests.post(self.endpoint("step"), json={"steps": steps})

Je ne sais pas combien d’arguments prends ton appel vers configure mais intuitivement on peut imaginer l’appel à la fonction step de deux manières:

Avec ta première méthode.
1
2
3
4
5
6
options = {
    'param': 1,
    'param2': 2,
    'param3': 3
}
simul.step('steps12', options)
Avec ta seconde méthodes
1
2
3
4
param = 1
param2 = 2
param3 = 3
simul.step('steps12', param=param, param2=param2, param3=param3) #voir plus...

Petit exemple qui peut répondre à ta question (pris dans le code du module requests), je t’ai sélectionné une ligne.

+0 -0
Auteur du sujet

@entwanne : la première me semble plus sécurisante. Je n’ai pas l’impression qu’il vaille le coup de s’exposer aux limitations que tu mentionnes pour économiser les caractères dict():

1
sim.step(1, dict(param1=2, param3=2))
+0 -0
Avec ta seconde méthodes
1
2
3
4
param = 1
param2 = 2
param3 = 3
simul.step('steps12', param=param, param2=param2, param3=param3) #voir plus...
grolip

Pas vraiment un argument :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
options = {
    'param': 1,
    'param2': 2,
    'param3': 3,
}
simul.step('steps12', **options)

# et si les options sont paramétrées juste avant l'appel de la fonction :

simul.step('steps12', **{
    'param': 1,
    'param2': 2,
    'param3': 3
})

Autrement dit, ça ne change pas grand chose niveau syntaxe. Personnellement, j’aurai tendance à passer un dict si ce sont de réels données qui sont passés. Si ce sont des paramètres, je tendrai plutôt vers la version foo(**kwargs) qui facilite l’appel quand on veut juste modifier quelques paramètres.

Dans la pratique cette question relève du pinaillage, et je tirerais la solution à pile ou face. **kwargs. Voilà.

Le plus important c’est juste de rester consistant dans le projet.

Édité par nohar

I was a llama before it was cool

+1 -0

Pourquoi pas un mixte des deux alors ?

1
2
3
4
5
6
7
def step(self, steps, parameters={}, **kwargs):
    params = {}
    params.update(parameters)
    params.update(kwargs)
    if params:
        requests.post(self.endpoint("configure"), json=params)
    requests.post(self.endpoint("step"), json={"step": steps})

Comme ça plus de problème, sauf peut-être l’ordre des updates… :D

+0 -3

Outre le fait que mixer les deux n’apporte rien à part de la confusion, j’attire l’attention des potentiels lecteurs sur le fait qu’il est très souvent une mauvaise idée d’assigner un mutable comme valeur par défaut à un argument d’une fonction. Cet argument n’est évalué qu’une fois et est donc partagé par tous les appels de la fonction utilisant ce défaut, ce qui peut conduire à des effets de bords non désirés si on le modifie dans le corps de la fonction :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def add_beer(bar={}):
    if 'beer' not in bar:
        bar['beer'] = 1
    else:
        bar['beer'] += 1
    return bar

print(add_beer())  # {'beer': 1}
print(add_beer())  # {'beer': 2}
print(add_beer() is add_beer())  # True (et on a 4 bières!)

Édité par adri1

I don’t mind that you think slowly, but I do mind that you are publishing faster. – W. Pauli

+0 -0

Dans l’exemple le paramètre par défaut n’est jamais modifié, le fait que ce soit un dictionnaire vide sert à gagner du temps et de la clarté.

Je n’ai rien inventé, la bibliothèque tkinter offre aussi ce choix de paramètre à l’utilisateur, sans confusion me semble-t-il.

Aussi je ne pense pas que ce soit une mauvaise idée de définir un mutable en paramètre par défaut, juste qu’il faut savoir de quoi il en retourne. La plupart du temps, avec un mutable vide, ça sert à clarifier le type de donnée attendu, et dans de rare cas, l’effet de bord dont tu parles peut être désirable.

+2 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte