Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Only add to a dict if a condition is met

I am using urllib.urlencode to build web POST parameters, however there are a few values I only want to be added if a value other than None exists for them.

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

That works fine, however if I make the orange variable optional, how can I prevent it from being added to the parameters? Something like this (pseudocode):

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

I hope this was clear enough, does anyone know how to solve this?

like image 268
user1814016 Avatar asked Jan 10 '13 17:01

user1814016


People also ask

Should I use dict () or {}?

With CPython 2.7, using dict() to create dictionaries takes up to 6 times longer and involves more memory allocation operations than the literal syntax. Use {} to create dictionaries, especially if you are pre-populating them, unless the literal syntax does not work for your case.

Can you append to a dict?

To append an element to an existing dictionary, you have to use the dictionary name followed by square brackets with the key name and assign a value to it.

Can you explain the dict get () function?

Python Dictionary get() Method The get() method returns the value of the item with the specified key.

How do you add key-value pairs to an existing dictionary?

In Python, we can add multiple key-value pairs to an existing dictionary. This is achieved by using the update() method. This method takes an argument of type dict or any iterable that has the length of two - like ((key1, value1),) , and updates the dictionary with new key-value pairs.


2 Answers

You'll have to add the key separately, after the creating the initial dict:

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

Python has no syntax to define a key as conditional; you could use a dict comprehension if you already had everything in a sequence:

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

but that's not very readable.

If you are using Python 3.9 or newer, you could use the new dict merging operator support and a conditional expression:

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

but I find readability suffers, and so would probably still use a separate if expression:

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

Another option is to use dictionary unpacking, but for a single key that's not all that more readable:

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

I personally would never use this, it's too hacky and is not nearly as explicit and clear as using a separate if statement. As the Zen of Python states: Readability counts.

like image 101
Martijn Pieters Avatar answered Sep 28 '22 00:09

Martijn Pieters


To piggyback on sqreept's answer, here's a subclass of dict that behaves as desired:

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 

This will allow values of existing keys to be changed to None, but assigning None to a key that does not exist is a no-op. If you wanted setting an item to None to remove it from the dictionary if it already exists, you could do this:

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

Values of None can get in if you pass them in during construction. If you want to avoid that, add an __init__ method to filter them out:

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 

You could also make it generic by writing it so you can pass in the desired condition when creating the dictionary:

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 
like image 22
kindall Avatar answered Sep 28 '22 00:09

kindall