Only add to a dict if a condition is met
Solution 1
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.
Solution 2
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
Solution 3
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)
Solution 4
I did this. Hope this help.
apple = 23
orange = 10
a = {
'apple' : apple,
'orange' if orange else None : orange
}
Expected output : {'orange': 10, 'apple': 23}
Although, if orange = None
, then there will be a single entry for None:None
. For example consider this :
apple = 23
orange = None
a = {
'apple' : apple,
'orange' if orange else None : orange
}
Expected Output : {None: None, 'apple': 23}
Solution 5
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 })
}
Related videos on Youtube
user1814016
Updated on April 06, 2022Comments
-
user1814016 about 2 years
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 thanNone
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?
-
jpm over 11 yearsIf there's an acceptable default value, you could use the
'orange': orange if orange else default
syntax.
-
-
Ryan Artecona over 11 yearsequivalently,
d = {k:v for k,v in d.items() if v}
-
PhilipGarnero almost 9 yearsThis will also clear values evaluated to False. You should do
if dictparams[k] is None
instead. -
Leonardo Ruiz almost 8 yearsThank you. I was able to figure it out using this in conjuntion with this answer stackoverflow.com/a/2588648/2860243
-
RCross almost 8 yearsOh, very neat. I like this answer the best!
-
CharlesB almost 6 years
d = {k:v for k,v in d.items() if v is not None}
, then -
DylanYoung about 5 yearsA new update method might not do it alone. CPython bypasses special methods when doing a dict-to-dict update (which it determines based on the memory structure of the object). You may need to avoid directly subclassing dict (you can set
__class__
to dict instead to pass isinstance checks). It's possible that this doesn't apply in this case (I was doing the inverse, transforming keys and values when extracted rather than when input), but I'm leaving this comment just in case it's helpful -
DylanYoung about 5 yearsAgreed, except for all that extra work you're doing by updating multiple times in a loop: get rid of the for loop and do this:
params.update({key: val for key, val in kwargs if val is not None})
-
DylanYoung about 5 yearsThis is a neat trick. Then you only have one key to clear at the end:
None
. I'd suggest only doing the condition on the key (if you're worried about the value being there, just addNone: None
as the last line in the dict declaration), then afterwards dodel a[None]
. -
Oli about 5 yearsThis works for adding new values. You need to override init and process filter down kwargs values for None too if you want the constructor to work too.
-
DylanYoung over 4 yearsSo we need a
getter
for each variable. Why not just do:fruits={"apple", "orange"}; d=vars(); params = urllib.urlencode({ fruit: val for fruit, val in d.items() if fruit in fruits and val is not None })
-
Bart about 4 yearsFor Python 3.5 and up: since PEP-0448 was implemented (proposed 29-Jun-2013), stackoverflow.com/a/55341342/563970 should be the answer
-
Martijn Pieters about 4 years@Bart: that's very much a stylistic choice. For just one key, using
**({key: value} if test else {})
is really not more readable. -
Nikhil Wagh about 4 yearsI won't suggest this, because it depends on the ordering in which you write the keys. It is prone to bugs.
-
Bart about 4 yearsSure it is a stylistic choice, and for a single option it may be overkill. I've been using it to add
{key: value}
pairs to a nested dict where both key and value were derived (composed) from other keys and values. Doing this theif something: ...
way would definitely decrease readability in my case (due to the nesting which would then have to be applied twice or more). YMMV on a case-by-case basis here. -
Cristóbal Felipe Fica Urzúa about 4 yearspretty elegant, I only added a default get value def get(self, key, default_value=None): return self.container.get(key, default_value)
-
raullalves about 4 yearsThis is the best answer. Just add
a.pop(None)
and it`s perfect -
Fernando Martínez almost 4 yearsThis is a bad practice. If the language doesn't support, better not to add extra operations to by pass this, (like a.pop, del a[None] and similars).
-
Dave Gregory almost 4 yearsQuick illustration: In my case today, my conditional dict key is right in the middle of a big structure of nested dict and list literals (a mongoDB aggregation pipeline). It's REALLY helpful to have the conditional in the dict's place within the structure (although tomorrow I might decide that it looks too much like an injection vulnerability!).
-
Martijn Pieters over 3 yearsYou need to explicitly test for
is not None
, as stated in the question: I only want to be added if a value other thanNone
. Try withorange = ""
ororange = 0
, which are values other thanNone
. -
Martijn Pieters over 3 yearsOther than that: resist the urge to use tricks. This code requires an additional statement (
a.pop(None)
orif None in a: del a[None]
), and requires an explanation in a comment for future developers that have to maintain your code. -
Martijn Pieters over 3 yearsResist the urge to use 'clever tricks'. You'll not thank yourself later when you rename the
'apple'
key to'pear'
and miss the first line, and so introduced a weird bug. Readability counts! -
Ilya Kharlamov over 3 years@MartijnPieters Did I mention that it's a hack? It should be treated as a hack.
-
Patrick Mutuku about 3 yearsRemove curly bracket at the expression at the top
-
ciurlaro about 2 yearsThis is simply beautiful
-
Nahuel Greco about 2 yearsA variation: `d = { 'apple': apple, **(orange and {"orange" : orange} or {}) }```