Existe-t-il un moyen simple en Python de vérifier si la valeur d'un paramètre facultatif provient de sa valeur par défaut, ou parce que l'utilisateur l'a définie explicitement lors de l'appel de fonction?
python
function
optional-parameters
Matthias
la source
la source
None
comme valeur par défaut et vérifiez cela. Si vous pouviez vraiment configurer ce test, vous excluriez également toute possibilité pour l'utilisateur de transmettre explicitement la valeur qui invoque le comportement par défaut.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
utilisateur l'appelle avecx=My(b=False)
. Une méthode de classe pourrait s'appeler avec desx=My(_p=self, c=True)
fonctions if pourraient détecter que b n'est pas explicitement défini et que les variables non définies doivent être transmises depuis le niveau supérieur. Mais si elles ne peuvent pas, les appels récursifs doivent passer toutes les variables explicitement:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
etx=My(a=True)
. Votre scénario consiste à attribuer aux paramètres facultatifs une valeur autre que leur valeur par défaut.Réponses:
Beaucoup de réponses contiennent de petites informations complètes, je voudrais donc les rassembler avec mes motifs préférés.
la valeur par défaut est un
mutable
typeSi 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 (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 si elle a été passée en 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')
Arguments par défaut immuables
Maintenant, c'est un peu moins élégant si on s'attend à ce que votre valeur par défaut soit une
immutable
valeur (et rappelez-vous que même les chaînes sont immuables!) Parce que vous ne pouvez pas exploiter l'astuce telle quelle, mais il y a toujours quelque chose que vous pouvez faire, en exploitant toujours mutable type; en gros, vous mettez un "faux" par défaut mutable dans la signature de la fonction, et la valeur par défaut "réelle" 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)
Cela semble particulièrement amusant si votre valeur par défaut est
None
, mais qu'elleNone
est immuable, donc ... vous devez toujours utiliser explicitement un mutable comme paramètre par défaut de la fonction et basculer sur None dans le code.Utilisation d'une
Default
classe pour les valeurs par défaut immuablesou, similaire à la suggestion @cz, si les documents python ne suffisent pas :-), vous pouvez ajouter un objet entre les deux pour rendre l'API plus explicite (sans lire les documents); l'instance de classe used_proxy_ Default est mutable et contiendra la valeur par défaut réelle que vous souhaitez 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 bien aussi pour
Default(None)
.Autres modèles
De toute évidence, les modèles ci-dessus semblent plus laids qu'ils ne le devraient à cause de tous ceux
print
qui ne sont là que pour montrer comment ils fonctionnent. Sinon, je les trouve assez laconiques et répétables.Vous pouvez écrire un décorateur pour ajouter le
__call__
modèle suggéré par @dmg de manière plus simple, mais cela obligera toujours à utiliser des astuces étranges dans la définition de la fonction elle-même - vous devrez vous séparervalue
etvalue_default
si votre code a besoin de les distinguer, alors Je ne vois pas beaucoup d'avantage et je n'écrirai pas l'exemple :-)Types mutables comme valeurs par défaut dans Python
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 (donc fondamentalement votre valeur par défaut est immuable :-)).Rappelez - vous qu'en Python il y a seulement 3 mutable types intégrés :
set
,list
,dict
; tout le reste - même les cordes! - est immuable.la source
1
, qui devrait être immuable ...def f(value={})
.1
; désolé si ce n'est pas clair dans l'explication, mais le but de cette partie de la réponse est de pouvoir avoir une valeur par défaut immuable (1
). Si vous vérifiez l'exemple, vous verrez qu'il dit:,print('default'); value = 1
nonvalue={}
Pas vraiment. La méthode standard consiste à utiliser une valeur par défaut que l'utilisateur ne devrait pas transmettre, par exemple une
object
instance: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 voudrait transmettre.L'alternative est d'utiliser
kwargs
:def foo(**kwargs): if 'param' in kwargs: param = kwargs['param'] else: ...
Cependant, cela est trop détaillé et rend votre fonction plus difficile à utiliser car sa documentation n'inclura pas automatiquement le
param
paramètre.la source
Ellipsis
singleton par défaut, qui a été explicitement conçu pour être utilisé comme saut de cette valeur....
est un alias pourEllipsis
, donc les utilisateurs qui souhaitent utiliser des arguments de position peuvent simplement appeleryour_function(p1, ..., p3)
ce qui le rend évident et agréable à lire.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
C'est en fait faux, car vous pouvez définir la description d'une fonction et de ses paramètres à l'aide duinspect
module. Cela dépend de votre IDE si cela fonctionnera ou non.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. Faites juste'a' in explicit_params
pour vérifier si le paramètrea
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)
la source
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, donc je peux être très confiant que lorsque je vois cette valeur pour
arg
, elle n'a pas été transmise.la source
object()
place deuuid4()
- c'est toujours une instance unique , ce quiis
vérifieJe l' ai vu ce modèle à quelques reprises (bibliothèque par exemple
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... ou l'équivalent monoliner ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Contrairement à
DEFAULT = object()
cela, cela facilite la vérification de type et fournit des informations lorsque des erreurs se produisent - souvent la représentation sous forme de chaîne ("DEFAULT"
) ou le nom de classe ("Default"
) sont utilisés dans les messages d'erreur.la source
La réponse de @ Ellioh fonctionne en python 2. En python 3, le code suivant devrait fonctionner:
import inspect def explicit_checker(f): varnames = inspect.getfullargspec(f)[0] def wrapper(*a, **kw): kw['explicit_params'] = set(list(varnames[:len(a)]) + list(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
Cette méthode peut conserver les noms d'argument et les valeurs par défaut (au lieu de ** kwargs) avec une meilleure lisibilité.
la source
Vous pouvez le vérifier depuis
foo.__defaults__
etfoo.__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
=
etis
vous pouvez les compareret dans certains cas, le code ci-dessous suffit
Par exemple, vous devez éviter de changer la valeur par défaut, vous pouvez vérifier l'égalité et copier si c'est le cas
def update_and_show(data=Example): if data is Example: data = copy.deepcopy(data) update_inplace(data) #some operation print(data)
De plus, il est assez pratique d'utiliser
getcallargs
frominspect
car il renvoie des arguments réels avec lesquels la fonction sera appelée. Vous lui passez une fonction et des args et kwargs (inspect.getcallargs(func, /, *args, **kwds)
), cela retournera les arguments de la méthode réelle utilisés pour l'invocation, en tenant compte des valeurs par défaut et d'autres choses. Jetez un œil à un exemple ci-dessous.from inspect import getcallargs # we have a function with such signature def show_params(first, second, third=3): pass # if you wanted to invoke it with such params (you could get them from a decorator as example) args = [1, 2, 5] kwargs = {} print(getcallargs(show_params, *args, **kwargs)) #{'first': 1, 'second': 2, 'third': 5} # here we didn't specify value for d args = [1, 2, 3, 4] kwargs = {} # ---------------------------------------------------------- # but d has default value =7 def show_params1(first, *second, d = 7): pass print(getcallargs(show_params1, *args, **kwargs)) # it will consider b to be equal to default value 7 as it is in real method invocation # {'first': 1, 'second': (2, 3, 4), 'd': 7} # ---------------------------------------------------------- args = [1] kwargs = {"d": 4} def show_params2(first, d=3): pass print(getcallargs(show_params2, *args, **kwargs)) #{'first': 1, 'd': 4}
https://docs.python.org/3/library/inspect.html
la source
Je suis d'accord avec le commentaire de Volatility. 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
la source
def func(optional=value)
non**kwargs
**kwargs
est un peu différent. PS pas de problème à propos de -1 :) Et mon -1 pour vous était accidentel :)Ceci est une variation de la réponse de Stefano, mais je trouve un peu plus lisible:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
la source
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 mentionné, c'est assez bizarre, mais cela fait le travail. Cependant, ceci est tout à fait illisible et de la même manière que la suggestion d' ecatmur ne sera pas automatiquement documentée.
la source
check_f('z')
, qui est aussi, comme vous le dites, bizarre.