Only add to a dict if a condition is met
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.
Pretty old question but here is an alternative using the fact that updating a dict with an empty dict does nothing.
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)
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
One technique I suggest is using the dictionary unpacking operatior for this.
apple = 'green'
orange = None
params = urllib.urlencode({
'apple': apple,
**({ 'orange': orange } if orange else {})
})
Explanation
Basically, if orange
is None
, then the above dictionary simplifies to
{
'apple': apple,
**({})
}
# which results in just
{
'apple': apple,
}
Opposite goes with if orange
is not None
:
{
'apple': apple,
**({ "orange": orange })
}
# which results in just
{
'apple': apple,
'orange': orange
}
Readablity is a downside for conditionally adding keys inline. It is possible to create a function that could help mediate the readability issue.
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 })
}