209 votes

Nommage forcé des paramètres en Python

En Python, vous pouvez avoir une définition de fonction :

def info(object, spacing=10, collapse=1)

qui peut être appelé de l'une des manières suivantes :

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

grâce à Python qui autorise les arguments dans n'importe quel ordre, à condition qu'ils soient nommés.

Le problème que nous rencontrons est qu'au fur et à mesure que certaines de nos fonctions les plus importantes se développent, des personnes peuvent ajouter des paramètres entre spacing y collapse ce qui signifie que des valeurs erronées peuvent être attribuées à des paramètres qui ne sont pas nommés. En outre, il n'est pas toujours évident de savoir ce qu'il faut entrer. Nous cherchons un moyen de forcer les gens à nommer certains paramètres - pas seulement une norme de codage, mais idéalement un drapeau ou un plugin pydev ?

de sorte que dans les 4 exemples ci-dessus, seul le dernier passe le contrôle car tous les paramètres sont nommés.

Il est probable que nous ne l'activerons que pour certaines fonctions, mais toute suggestion sur la manière de mettre cela en œuvre - ou si c'est même possible - serait appréciée.

5voto

Anthony Sottile Points 3629

Les arguments python3 à mot-clé uniquement ( * ) peut être simulée en python2.x avec **kwargs

Considérons le code python3 suivant :

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

et son comportement :

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

Ceci peut être simulé à l'aide de ce qui suit, que j'ai pris la liberté d'intervertir. TypeError a KeyError dans le cas de "l'argument nommé requis", ce ne serait pas trop de travail de faire en sorte que ce soit le même type d'exception également

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

Et le comportement :

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

La recette fonctionne tout aussi bien avec python3.x, mais devrait être évitée si vous n'utilisez que python3.x.

2voto

Felix Kling Points 247451

Mise à jour :

J'ai réalisé que l'utilisation de **kwargs ne résoudrait pas le problème. Si vos programmeurs modifient les arguments des fonctions comme ils le souhaitent, on pourrait, par exemple, modifier la fonction comme suit :

def info(foo, **kwargs):

et l'ancien code serait à nouveau cassé (parce que maintenant chaque appel de fonction doit inclure le premier argument).

Tout se résume à ce que dit Bryan.


(...) il se peut que des personnes ajoutent des paramètres entre spacing y collapse (...)

En général, lorsque l'on modifie des fonctions, les nouveaux arguments doivent toujours être placés à la fin. Sinon, le code est cassé. Cela devrait être évident.
Si quelqu'un modifie la fonction de telle sorte que le code se brise, cette modification doit être rejetée.
(Comme le dit Bryan, c'est comme un contrat)

(...) Parfois, il n'est pas toujours évident de savoir ce qu'il faut faire.

En examinant la signature de la fonction (c'est-à-dire def info(object, spacing=10, collapse=1) ), on devrait immédiatement voir que chaque argument qui a un pas une valeur par défaut, est obligatoire.
Ce qu'il faut faire de l'argument, doit figurer dans la docstring.


Ancienne réponse (conservée par souci d'exhaustivité) :

Ce n'est probablement pas une bonne solution :

~~

Vous pouvez définir des fonctions de cette manière :

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargs est un dictionnaire qui contient n'importe quel argument de mot-clé. Vous pouvez vérifier si un argument obligatoire est présent et, si ce n'est pas le cas, lever une exception.

L'inconvénient est qu'il n'est peut-être plus évident de savoir quels arguments sont possibles, mais avec une documentation appropriée, cela devrait aller.

~~

1voto

Daniel Newby Points 1485

Comme l'indiquent les autres réponses, la modification des signatures de fonctions est une mauvaise idée. Il faut soit ajouter de nouveaux paramètres à la fin, soit corriger chaque appelant si des arguments sont insérés.

Si vous voulez quand même le faire, utilisez un décorateur de fonction et le inspect.getargspec fonction. Elle serait utilisée de la manière suivante :

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

Mise en œuvre de la require_named_args est laissée à l'appréciation du lecteur.

Je ne m'en préoccuperais pas. Ce sera lent à chaque fois que la fonction sera appelée, et vous obtiendrez de meilleurs résultats en écrivant le code avec plus de soin.

0voto

Noufal Ibrahim Points 32200

Vous pouvez déclarer vos fonctions comme recevant **args seulement. Cela permettrait d'utiliser des arguments de type mot-clé, mais il faudrait alors s'assurer que seuls des noms valides sont transmis.

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.

-1voto

shahjapan Points 4043
def cheeseshop(kind, *arguments, **keywords):

En python, l'utilisation de *args signifie que vous pouvez passer n-nombre d'arguments positionnels pour ce paramètre - qui seront accessibles comme un tuple à l'intérieur de la fonction.

Et si l'on utilise **kw, cela signifie que ses arguments sont des mots-clés, qui peuvent être accédés en tant que dict - vous pouvez passer n-nombre d'arguments kw, et si vous voulez restreindre l'utilisateur à entrer la séquence et les arguments dans l'ordre, alors n'utilisez pas * et ** - (c'est la façon pythonique de fournir des solutions génériques pour les grandes architectures...).

Si vous souhaitez restreindre votre fonction avec des valeurs par défaut, vous pouvez vérifier à l'intérieur de celle-ci

def info(object, spacing, collapse)
  spacing = 10 if spacing is None else spacing
  collapse = 1 if collapse is None else collapse

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