Only add to a dict if a condition is met

我使用 urllib.urlencode来构建 web POST 参数,但是有一些值我只想要添加,如果一个值以外的 None为他们存在。

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

这很好,但是如果我使 orange变量成为可选的,我怎样才能防止它被添加到参数中呢?类似这样的东西(伪代码) :

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

我希望这已经足够清楚了,有人知道怎么解决这个问题吗?

95555 次浏览

在创建初始 dict之后,您必须单独添加密钥:

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

Python 没有将键定义为条件的语法; 如果您已经具备了序列中的所有内容,那么可以使用 dict 理解:

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

但是读起来不是很清楚。

If you are using Python 3.9 or newer, you could use the 新的授权合并操作符支持 and a conditional expression:

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

但我发现可读性受到影响,因此可能仍会使用单独的 if表达式:

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

另一种选择是使用 解开字典,但是对于一个不那么可读的单键:

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

我个人永远不会使用这个,它太粗糙,几乎没有显式和 clear使用单独的 if语句。正如 Zen of Python所说: 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 })

另一个有效的答案是,您可以创建自己的不存储 Nothing 值的类 dict 容器。

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'}

为了回答这个问题,下面是 dict的一个子类,它的行为符合要求:

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

这将允许现有键的值为 变了None,但是将 None分配给不存在的键是不可操作的。如果您希望将一个项目从字典中的 None设置为 拿开(如果它已经存在) ,您可以这样做:

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

如果在构造过程中传递 None 可以的值,它们就会进入。如果您想避免这种情况,可以添加一个 __init__方法来过滤它们:

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

你也可以通过编写它来使它变得通用,这样你就可以在创建字典时 请进达到你想要的条件:

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

这是一个很老的问题,但是这里有一个替代方案,它使用一个空的 dict 来更新 dict,但是什么也不做。

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)

是我干的,希望这能帮上忙。

apple = 23
orange = 10
a = {
'apple' : apple,
'orange' if orange else None : orange
}

Expected output : {'orange': 10, 'apple': 23}

尽管如此,如果是 orange = None,那么 None:None将只有一个条目:

apple = 23
orange = None
a = {
'apple' : apple,
'orange' if orange else None : orange
}

预期产出: {None: None, 'apple': 23}

我真的很喜欢这里的答案: https://stackoverflow.com/a/50311983/3124256

但它也有一些缺陷:

  1. Duplicate if tests (repeated for key and value)
  2. 讨厌的 None: None条目在结果 dict

为了避免这种情况,您可以采取以下措施:

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]

预期产出: {'apple': 23}

注意: None: None条目确保了两件事:

  1. None键将始终存在(del不会抛出错误)
  2. “无值”的内容将永远不会存在于结果中(以防您以后忘记了 del)

如果您不担心这些事情,您可以省略它并将 del 包装在 try...except中(或者在 deling 之前检查是否存在 None键)。要解决数字2的问题,您也可以将条件检查放在值上(除了键之外)。

有一个反直观但可靠的黑客,重用其他道具名称,你想排除它。

{
'orange' if orange else 'apple': orange,
'apple': apple,
}

在这种情况下,后一个“苹果”将覆盖前一个“苹果”,有效地删除它。注意,条件表达式应该高于实数表达式。

通过使用字典理解,您可以使用单个条件处理所有可选项:

apple = "red"
orange = None
dictparams = {
key: value for key, value in
{
"apple": apple,
"orange": orange
}.items()
if value is not None
}

在这种情况下,dictparams结果将不包含 "orange",因为 orangeNone:

{'apple': 'red'}

我建议的一种技术是使用 字典解包算子字典解包算子

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

解释

基本上,如果 orangeNone,那么上面的字典简化为

{
'apple': apple,
**({})
}


# which results in just
{
'apple': apple,
}

如果 orange不是 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 })
}

我发现使用 发电机功能发电机功能更容易理解,也足够灵活。它还可以用于 Python2和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}

会导致:

data = {
"bar": "1234",
}

参见字典中 filter/lambda函数的这个问题/答案:

如何根据任意条件函数对字典进行过滤?

构建来自 仁慈的答案,我使用它来过滤掉的不仅仅是 None值,还有任何计算结果为 False 的值。这适合我的场景,因为我不希望任何东西是空的,FalseNone

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)