Only add to a dict if a condition is met
Question:
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?
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.
You can clear None after the assignment:
apple = 'green'
orange = None
dictparams = {
'apple': apple,
'orange': orange
}
for k in dictparams.keys():
if not dictparams[k]:
del dictparams[k]
params = urllib.urlencode(dictparams)
fruits = [("apple", get_apple()), ("orange", get_orange()), ...]
params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })
Another valid answer is that you can create you own dict-like container that doesn’t store None values.
class MyDict:
def __init__(self):
self.container = {}
def __getitem__(self, key):
return self.container[key]
def __setitem__(self, key, value):
if value != None:
self.container[key] = value
def __repr__(self):
return self.container.__repr__()
a = MyDict()
a['orange'] = 'orange';
a['lemon'] = None
print a
yields:
{'orange': 'orange'}
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
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)
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}
I really like the neat trick in the answer here: https://stackoverflow.com/a/50311983/3124256
But, it has some pitfalls:
- Duplicate
if
tests (repeated for key and value)
- Pesky
None: None
entry in the resulting dict
To avoid this, you can do the following:
apple = 23
orange = None
banana = None
a = {
'apple' if apple else None: apple,
'orange' if orange else None : orange,
'banana' if banana else None: banana,
None: None,
}
del a[None]
Expected Output : {'apple': 23}
Note: the None: None
entry ensures two things:
- The
None
key will always be present (del
won’t throw an error)
- The contents of ‘None values’ will never exist in the dict (in case you forget to
del
afterwards)
If you aren’t worried about these things, you can leave it out and wrap the del in a try...except
(or check if the None
key is present before del
ing). To address number 2 alternatively, you could also put the conditional check on the value (in addition to the key).
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 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'}
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 })
}
I find using a generator function to be easier to understand, and flexible enough. It also works with both Python 2 and 3.
def generate_request_items(apple, orange):
yield "apple", apple
if orange:
yield "orange", orange
# Add additional conditionals and yield statements here
apple = 'green'
orange = None
params = urllib.urlencode(dict(generate_request_items(apple, orange)))
You can add the data and then filter it via a condition afterwards:
data = {
"foo": None,
"bar": "1234",
"baz": None,
}
data = {k: v for k, v in data.items() if v is not None}
would result in:
data = {
"bar": "1234",
}
See also this question/answers for filter
/lambda
functions on a dictionary:
How to filter a dictionary according to an arbitrary condition function?
Building of the answer from kindall, I’m using this to filter out not just None
values, but anything that evaluates to False. This suits my scenario as I don’t want anything that is empty, False
or None
.
class DictNoNone(dict):
def __init__(self, iterable=(), **kwargs):
for k, v in iterable:
if v: self[k] = v
for k, v in kwargs.items():
if v: self[k] = v
def __setitem__(self, key, value):
if not value:
if key in self:
del self[key]
else:
dict.__setitem__(self, key, value)
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?
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.
You can clear None after the assignment:
apple = 'green'
orange = None
dictparams = {
'apple': apple,
'orange': orange
}
for k in dictparams.keys():
if not dictparams[k]:
del dictparams[k]
params = urllib.urlencode(dictparams)
fruits = [("apple", get_apple()), ("orange", get_orange()), ...]
params = urllib.urlencode({ fruit: val for fruit, val in fruits if val is not None })
Another valid answer is that you can create you own dict-like container that doesn’t store None values.
class MyDict:
def __init__(self):
self.container = {}
def __getitem__(self, key):
return self.container[key]
def __setitem__(self, key, value):
if value != None:
self.container[key] = value
def __repr__(self):
return self.container.__repr__()
a = MyDict()
a['orange'] = 'orange';
a['lemon'] = None
print a
yields:
{'orange': 'orange'}
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
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)
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}
I really like the neat trick in the answer here: https://stackoverflow.com/a/50311983/3124256
But, it has some pitfalls:
- Duplicate
if
tests (repeated for key and value) - Pesky
None: None
entry in the resultingdict
To avoid this, you can do the following:
apple = 23
orange = None
banana = None
a = {
'apple' if apple else None: apple,
'orange' if orange else None : orange,
'banana' if banana else None: banana,
None: None,
}
del a[None]
Expected Output : {'apple': 23}
Note: the None: None
entry ensures two things:
- The
None
key will always be present (del
won’t throw an error) - The contents of ‘None values’ will never exist in the dict (in case you forget to
del
afterwards)
If you aren’t worried about these things, you can leave it out and wrap the del in a try...except
(or check if the None
key is present before del
ing). To address number 2 alternatively, you could also put the conditional check on the value (in addition to the key).
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 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'}
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 })
}
I find using a generator function to be easier to understand, and flexible enough. It also works with both Python 2 and 3.
def generate_request_items(apple, orange):
yield "apple", apple
if orange:
yield "orange", orange
# Add additional conditionals and yield statements here
apple = 'green'
orange = None
params = urllib.urlencode(dict(generate_request_items(apple, orange)))
You can add the data and then filter it via a condition afterwards:
data = {
"foo": None,
"bar": "1234",
"baz": None,
}
data = {k: v for k, v in data.items() if v is not None}
would result in:
data = {
"bar": "1234",
}
See also this question/answers for filter
/lambda
functions on a dictionary:
How to filter a dictionary according to an arbitrary condition function?
Building of the answer from kindall, I’m using this to filter out not just None
values, but anything that evaluates to False. This suits my scenario as I don’t want anything that is empty, False
or None
.
class DictNoNone(dict):
def __init__(self, iterable=(), **kwargs):
for k, v in iterable:
if v: self[k] = v
for k, v in kwargs.items():
if v: self[k] = v
def __setitem__(self, key, value):
if not value:
if key in self:
del self[key]
else:
dict.__setitem__(self, key, value)