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
fruits = [("apple", get_apple()), ("orange", get_orange()), ...]
params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })
There is a counter-intuitive but reliable hack, to reuse the other prop name you want to exclude it.
{
'orange' if orange else 'apple': orange,
'apple': apple,
}
In this case, the latter 'apple' will override the previous 'apple' effectively removing it. Note that the conditional expressions should go above the real ones.
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.
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.
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
You can deal with all optional items using a single condition by using a dictionary comprehension:
apple = "red"
orange = None
dictparams = {
key: value for key, value in
{
"apple": apple,
"orange": orange
}.items()
if value is not None
}
The dictparams
result will not contain "orange"
in this case, because orange
is None
:
{'apple': 'green'}