Existe-t-il un moyen simple de vérifier si la valeur d'un paramètre facultatif provient de sa valeur par défaut ou si l'utilisateur l'a définie explicitement à l'appel de la fonction?
Pas vraiment. La méthode standard consiste à utiliser une valeur par défaut que l'utilisateur ne devrait pas transmettre, par exemple. une instance object
:
DEFAULT = object()
def foo(param=DEFAULT):
if param is DEFAULT:
...
Habituellement, vous pouvez simplement utiliser None
comme valeur par défaut, si cela n’a pas de sens en tant que valeur que l’utilisateur souhaite transmettre.
L'alternative consiste à utiliser kwargs
:
def foo(**kwargs):
if 'param' in kwargs:
param = kwargs['param']
else:
...
Cependant, ceci est trop détaillé et rend votre fonction plus difficile à utiliser car sa documentation n'inclura pas automatiquement le paramètre param
.
Le décorateur de fonction suivant, explicit_checker
, crée un ensemble de noms de paramètres de tous les paramètres donnés explicitement. Il ajoute le résultat en tant que 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 même deviner la valeur par défaut s'il essayait. Je peux donc être sûr que lorsque je vois cette valeur pour arg
, elle n'a pas été transmise.
Je suis d'accord avec le commentaire de la volatilité. Mais vous pouvez vérifier de la manière suivante:
def function(arg1,...,**optional):
if 'optional_arg' in optional:
# user has set 'optional_arg'
else:
# user has not set 'optional_arg'
optional['optional_arg'] = optional_arg_default_value # set default
Vous pouvez le vérifier à partir de foo.__defaults__
et foo.__kwdefaults__
voir un exemple simple ci-dessous
def foo(a, b, c=123, d=456, *, e=789, f=100):
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
print(a, b, c, d, e, f)
#and these variables are also accessible out of function body
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
foo.__kwdefaults__['e'] = 100500
foo(1, 2)
#(123, 456)
#{'f': 100, 'e': 100500}
#1 2 123 456 100500 100
puis en utilisant l'opérateur =
et is
vous pouvez les comparer
et dans certains cas, le code ci-dessous est suffisant
Par exemple, vous devez éviter de modifier la valeur par défaut, vous pouvez alors vérifier l’égalité, puis vous en sortir
def update_and_show(data=Example):
if data is Example:
data = copy.deepcopy(data)
update_inplace(data) #some operation
print(data)
Beaucoup de réponses contiennent de petits morceaux d'informations complètes, alors j'aimerais les rassembler avec mes motifs préférés.
mutable
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 fois lorsque la fonction est définie (pour en savoir plus à la fin de la réponse)
Cela signifie que vous pouvez facilement comparer une valeur modifiable par défaut en utilisant is
pour voir si elle a été transmise en tant qu'argument ou laissée par défaut, comme dans les exemples suivants en tant que 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')
Maintenant, il est un peu moins élégant si votre valeur par défaut est supposée être une valeur immutable
(et rappelez-vous que même les chaînes sont immuables!) Car vous ne pouvez pas exploiter le truc tel quel, mais vous pouvez toujours faire quelque chose comme: :
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)
Cela semble particulièrement amusant si votre valeur par défaut est None
, mais None
est immuable alors ...
Default
pour des valeurs par défaut immuablesou, comme dans la suggestion de @ c-z, si python docs ne suffisent pas :-), vous pouvez ajouter un objet entre eux pour rendre l'API plus explicite sans lire les documents:
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))
à présent:
>>> 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 bien aussi pour Default(None)
.
Évidemment, les schémas ci-dessus semblent plus laids qu’ils ne le devraient à cause de tous les print
qui ne sont là que pour montrer comment ils fonctionnent. Sinon, je les trouve assez concis et répétables.
Vous pouvez écrire à un décorateur pour ajouter le motif __call__
suggéré par @dmg d'une manière plus simple, mais cela obligera tout de même à utiliser des astuces étranges dans la définition de la fonction elle-même - il vous faudra alors séparer value
et value_default
si votre code doit les distinguer, je ne vois donc pas beaucoup d'avantages et je n'écrirai pas l'exemple :-)
Un peu plus sur # 1 python gotcha! , abusé pour votre propre plaisir ci-dessus. Vous pouvez voir ce qui se passe en raison de l'é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 (votre commande par défaut est donc immuable :-)).
Rappelez-vous que dans Python, il y a 3 mutables types intégrés : set
, list
, dict
, mais tout le reste - même des chaînes ! - est immuable.
J'ai vu ce modèle à quelques reprises (par exemple, bibliothèque unittest
, py-flags
, jinja
):
class Default:
def __repr__( self ):
return "DEFAULT"
DEFAULT = Default()
... ou l'équivalent one-liner ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Contrairement à DEFAULT = object()
, cela facilite la vérification de type et fournit des informations lorsque des erreurs se produisent - la représentation sous forme de chaîne ("DEFAULT"
) ou le nom de classe ("Default"
) sont fréquemment utilisés dans les messages d'erreur.
Une approche un peu bizarre serait:
class CheckerFunction(object):
def __init__(self, function, **defaults):
self.function = function
self.defaults = defaults
def __call__(self, **kwargs):
for key in self.defaults:
if(key in kwargs):
if(kwargs[key] == self.defaults[key]):
print 'passed default'
else:
print 'passed different'
else:
print 'not passed'
kwargs[key] = self.defaults[key]
return self.function(**kwargs)
def f(a):
print a
check_f = CheckerFunction(f, a='z')
check_f(a='z')
check_f(a='b')
check_f()
Quelles sorties:
passed default
z
passed different
b
not passed
z
Maintenant, comme je l'ai dit, c'est assez bizarre, mais ça fait le travail. Cependant, ceci est assez illisible et similaire à la suggestion de ecatmur / ne sera pas automatiquement documentée.