Existe-t-il un moyen simple en Python de vérifier si la valeur d'un paramètre optionnel provient de sa valeur par défaut, ou parce que l'utilisateur l'a défini explicitement à l'appel de la fonction ?
Réponses
Trop de publicités?Pas vraiment. La méthode standard est d'utiliser une valeur par défaut que l'utilisateur n'est pas censé passer, par exemple un object
instance :
DEFAULT = object()
def foo(param=DEFAULT):
if param is DEFAULT:
...
En général, vous pouvez simplement utiliser None
comme valeur par défaut, si elle n'a pas de sens en tant que valeur que l'utilisateur voudrait passer.
L'alternative est d'utiliser kwargs
:
def foo(**kwargs):
if 'param' in kwargs:
param = kwargs['param']
else:
...
Cependant, cette méthode est trop verbeuse et rend votre fonction plus difficile à utiliser, car sa documentation n'inclura pas automatiquement l'adresse de l'utilisateur. param
paramètre.
Beaucoup de réponses contiennent de petits morceaux de l'information complète, alors j'aimerais rassembler tout cela avec mon ou mes motifs préférés.
La valeur par défaut est un mutable
type
Si la valeur par défaut est un objet mutable, vous avez de la chance : vous pouvez exploiter le fait que les arguments par défaut de Python sont évalués une seule fois lors de la définition de la fonction (plus d'informations à ce sujet à la fin de la réponse dans la dernière section).
Cela signifie que vous pouvez facilement comparer une valeur mutable par défaut en utilisant is
pour voir s'il a été passé comme argument ou laissé par défaut, comme dans les exemples suivants comme fonction ou méthode :
def f(value={}):
if value is f.__defaults__[0]:
print('default')
else:
print('passed in the call')
et
class A:
def f(self, value={}):
if value is self.f.__defaults__[0]:
print('default')
else:
print('passed in the call')
Arguments par défaut immuables
Maintenant, c'est un peu moins élégant si votre défaut est censé être un immutable
(et rappelez-vous que même les chaînes de caractères sont immuables !) parce que vous ne pouvez pas exploiter l'astuce telle qu'elle est, mais il y a encore quelque chose que vous pouvez faire, toujours en exploitant le type mutable ; en gros, vous mettez une valeur mutable "faux" défaut dans la signature de la fonction, et la "vraie" valeur par défaut souhaitée dans le corps de la fonction.
def f(value={}):
"""
my function
:param value: value for my function; default is 1
"""
if value is f.__defaults__[0]:
print('default')
value = 1
else:
print('passed in the call')
# whatever I want to do with the value
print(value)
C'est particulièrement drôle si votre vrai défaut est None
mais None
est immuable donc... vous devez toujours utiliser explicitement un mutable comme paramètre par défaut de la fonction, et passer à None dans le code.
Utilisation d'un Default
classe pour les valeurs par défaut immuables
ou, comme le suggère @c-z, si la documentation python ne suffit pas :-) vous pouvez ajouter un objet entre les deux pour rendre l'API plus explicite (sans lire la documentation) ; l'instance de la classe used_proxy_ Default est mutable, et contiendra la vraie valeur par défaut que vous voulez utiliser.
class Default:
def __repr__(self):
return "Default Value: {} ({})".format(self.value, type(self.value))
def __init__(self, value):
self.value = value
def f(default=Default(1)):
if default is f.__defaults__[0]:
print('default')
print(default)
default = default.value
else:
print('passed in the call')
print("argument is: {}".format(default))
maintenant :
>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1
>>> f(2)
passed in the call
argument is: 2
Ce qui précède fonctionne également très bien pour Default(None)
.
Autres motifs
Il est évident que les modèles ci-dessus sont plus laids qu'ils ne devraient l'être en raison de tous les éléments suivants print
qui ne sont là que pour montrer comment ils fonctionnent. Sinon, je les trouve suffisamment laconiques et reproductibles.
Vous pourriez écrire un décorateur pour ajouter le __call__
suggéré par @dmg d'une manière plus rationnelle, mais cela obligera toujours à utiliser des astuces bizarres dans la définition de la fonction elle-même - vous devrez séparer les fonctions value
y value_default
si votre code doit les distinguer, je n'y vois pas beaucoup d'avantages et je n'écrirai pas l'exemple :-)
Types mutables comme valeurs par défaut en Python
Un peu plus sur #1 python gotcha ! abusés pour votre propre plaisir ci-dessus. Vous pouvez voir ce qui se passe en raison de la évaluation à la définition en faisant :
def testme(default=[]):
print(id(default))
Vous pouvez exécuter testme()
autant de fois que vous le souhaitez, vous verrez toujours une référence à la même instance par défaut (donc, fondamentalement, votre défaut est immuable :-) ).
Rappelez-vous qu'en Python il n'y a que 3 mutables types intégrés : set
, list
, dict
; tout le reste - même les chaînes de caractères ! - est immuable.
Le décorateur de fonction suivant, explicit_checker
fait un ensemble de noms de paramètres de tous les paramètres donnés explicitement. Elle ajoute le résultat comme paramètre supplémentaire ( explicit_params
) à la fonction. Il suffit de faire 'a' in explicit_params
pour vérifier si le paramètre a
est donné explicitement.
def explicit_checker(f):
varnames = f.func_code.co_varnames
def wrapper(*a, **kw):
kw['explicit_params'] = set(list(varnames[:len(a)]) + kw.keys())
return f(*a, **kw)
return wrapper
@explicit_checker
def my_function(a, b=0, c=1, explicit_params=None):
print a, b, c, explicit_params
if 'b' in explicit_params:
pass # Do whatever you want
my_function(1)
my_function(1, 0)
my_function(1, c=1)
J'utilise parfois une chaîne universellement unique (comme un UUID).
import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
if arg is DEFAULT:
# it was not passed in
else:
# it was passed in
De cette façon, aucun utilisateur ne pourrait deviner la valeur par défaut s'il essayait, et je peux donc être sûr que lorsque je vois cette valeur de arg
il n'a pas été adopté.
J'ai vu ce modèle à plusieurs reprises (par exemple, la bibliothèque unittest
, py-flags
, jinja
) :
class Default:
def __repr__( self ):
return "DEFAULT"
DEFAULT = Default()
...ou l'équivalent en une phrase... :
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Contrairement à DEFAULT = object()
cela facilite la vérification du type et fournit des informations lorsque des erreurs se produisent - fréquemment, la représentation de la chaîne de caractères ( "DEFAULT"
) ou le nom de la classe ( "Default"
) sont utilisés dans les messages d'erreur.
- Réponses précédentes
- Plus de réponses