113 votes

N'ajouter à un dict que si une condition est remplie

J'utilise urllib.urlencode pour construire les paramètres web POST, cependant il y a quelques valeurs que je veux seulement ajouter si une valeur autre que None existe pour eux.

apple = 'green'
orange = 'orange'
params = urllib.urlencode({
    'apple': apple,
    'orange': orange
})

Cela fonctionne bien, mais si je fais le orange facultative, comment puis-je empêcher qu'elle soit ajoutée aux paramètres ? Quelque chose comme ceci (pseudocode) :

apple = 'green'
orange = None
params = urllib.urlencode({
    'apple': apple,
    if orange: 'orange': orange
})

J'espère que c'était suffisamment clair. Quelqu'un sait-il comment résoudre ce problème ?

114voto

Martijn Pieters Points 271458

Vous devrez ajouter la clé séparément, après la création de la clé initiale. dict :

params = {'apple': apple}
if orange is not None:
    params['orange'] = orange
params = urllib.urlencode(params)

Python n'a pas de syntaxe pour définir une clé comme conditionnelle ; vous pourriez utiliser une compréhension de type dict si vous aviez déjà tout dans une séquence :

params = urllib.urlencode({k: v for k, v in (('orange', orange), ('apple', apple)) if v is not None})

mais ce n'est pas très lisible.

Si vous utilisez Python 3.9 ou une version plus récente, vous pouvez utiliser la fonction nouveau soutien aux opérateurs de fusion de dictées et une expression conditionnelle :

params = urllib.urlencode(
    {'apple': apple} | 
    ({'orange': orange} if orange is not None else {})
)

mais je trouve que la lisibilité en souffre, et donc j'utiliserais probablement toujours une version séparée de if expression :

params = {'apple': apple}
if orange is not None:
    params |= {'orange': orange}
params = urllib.urlencode(params)

Une autre option consiste à utiliser déballage du dictionnaire mais pour une seule touche, ce n'est pas plus lisible :

params = urllib.urlencode({
    'apple': apple,
    **({'orange': orange} if orange is not None else {})
})

Personnellement, je ne l'utiliserais jamais, c'est trop bricolé et loin d'être aussi explicite et clair comme l'utilisation d'un if déclaration. Comme le Zen du python États : La lisibilité compte.

39voto

kindall Points 60645

Pour faire suite à la réponse de sqreept, voici une sous-classe de dict qui se comporte comme souhaité :

class DictNoNone(dict):
    def __setitem__(self, key, value):
        if key in self or value is not None:
            dict.__setitem__(self, key, value)

d = DictNoNone()
d["foo"] = None
assert "foo" not in d

Cela permettra aux valeurs des clés existantes d'être modifié a None mais en attribuant None à une clé qui n'existe pas est un échec. Si vous vouliez attribuer un élément à None a supprimer pour le retirer du dictionnaire s'il existe déjà, vous pourriez faire ceci :

def __setitem__(self, key, value):
    if value is None:
        if key in self:
            del self[key]
    else:
        dict.__setitem__(self, key, value)

Valeurs de None peut entrer si vous les faites passer pendant la construction. Si vous voulez éviter cela, ajoutez un __init__ pour les filtrer :

def __init__(self, iterable=(), **kwargs):
    for k, v in iterable:
        if v is not None: self[k] = v
    for k, v in kwargs.iteritems():
        if v is not None: self[k] = v

Vous pouvez également le rendre générique en l'écrivant de manière à ce que vous puissiez passer la condition souhaitée lors de la création du dictionnaire :

class DictConditional(dict):
    def __init__(self, cond=lambda x: x is not None):
        self.cond = cond
    def __setitem__(self, key, value):
        if key in self or self.cond(value):
            dict.__setitem__(self, key, value)

d = DictConditional(lambda x: x != 0)
d["foo"] = 0   # should not create key
assert "foo" not in d

17voto

Vieille question mais voici une alternative utilisant le fait que la mise à jour d'un dict avec un dict vide ne fait rien.

def urlencode_func(apple, orange=None):
    kwargs = locals().items()
    params = dict()
    for key, value in kwargs:
        params.update({} if value is None else {key: value})
    return urllib.urlencode(params)

6voto

Nikhil Wagh Points 376

J'ai fait ça. J'espère que cela vous aidera.

apple = 23
orange = 10
a = {
    'apple' : apple,
    'orange' if orange else None : orange
}

Résultat attendu : {'orange': 10, 'apple': 23}

Bien que, si orange = None alors il y aura une seule entrée pour None:None . Par exemple, considérez ceci :

apple = 23
orange = None
a = {
    'apple' : apple,
    'orange' if orange else None : orange
}

Résultats attendus : {None: None, 'apple': 23}

5voto

kmui2 Points 1001

Une technique que je suggère est d'utiliser le opération de déballage de dictionnaire pour ça.

apple = 'green'
orange = None
params = urllib.urlencode({
    'apple': apple,
    **({ 'orange': orange } if orange else {})
})

Explication

Fondamentalement, si orange es None alors le dictionnaire ci-dessus se simplifie en

{
    'apple': apple,
    **({})
}

# which results in just
{
    'apple': apple,
} 

Le contraire va avec si orange n'est pas None :

{
    'apple': apple,
    **({ "orange": orange })
}

# which results in just
{
    'apple': apple,
    'orange': orange
} 

La lisibilité est un inconvénient pour l'ajout conditionnel de clés en ligne. Il est possible de créer une fonction qui pourrait aider à résoudre le problème de lisibilité.

from typing import Callable

def cond_pairs(
        cond: bool, pairs: Callable[[], dict],
) -> dict:
    return pairs() if cond else {}

{
    'apple': apple,
    **cond_pairs(orange, lambda: { 'orange': orange })
}

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