105 votes

Comment vérifier si le paramètre de la fonction optionnelle est défini

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 ?

64voto

ecatmur Points 64173

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.

28voto

Stefano Points 5188

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.

16voto

Ellioh Points 2277

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)

5voto

Jesse B Miller Points 67

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é.

5voto

c z Points 1188

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.

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