仅在满足条件时才添加到字典
Posted
技术标签:
【中文标题】仅在满足条件时才添加到字典【英文标题】:Only add to a dict if a condition is met 【发布时间】:2012-12-25 04:35:55 【问题描述】:我正在使用urllib.urlencode
来构建网络 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
)
我希望这已经足够清楚了,有人知道如何解决这个问题吗?
【问题讨论】:
如果有可接受的默认值,您可以使用'orange': orange if orange else default
语法。
【参考方案1】:
在创建初始 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)
但这不是很可读。
如果您使用的是 Python 3.9 或更新版本,则可以使用 new dict merging operator support 和条件表达式:
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)
另一种选择是使用dictionary unpacking,但对于单个键,它的可读性并不那么好:
params = urllib.urlencode(
'apple': apple,
**('orange': orange if orange is not None else )
)
我个人永远不会使用它,它太 hacky 并且不像使用单独的 if
语句那样明确和清晰。正如Zen of Python 所说:可读性很重要。
【讨论】:
对于 Python 3.5 及更高版本:由于实现了 PEP-0448(建议 2013 年 6 月 29 日),***.com/a/55341342/563970 应该是答案 @Bart:这在很大程度上是一种风格选择。只需要一键,使用**(key: value if test else )
确实不可读。
当然,这是一种风格选择,对于单一的选择,它可能是矫枉过正。我一直在使用它将 key: value
对添加到嵌套字典中,其中键和值都是从其他键和值派生(组合)的。在我的情况下,以if something: ...
方式执行此操作肯定会降低 的可读性(由于嵌套必须应用两次或更多次)。 YMMV 在此处逐案处理。
快速说明:在我今天的例子中,我的条件 dict 键正好位于嵌套 dict 和列表文字(一个 mongoDB 聚合管道)的大结构的中间。将条件放在结构中的 dict 位置真的很有帮助(尽管明天我可能会认为它看起来太像注入漏洞了!)。【参考方案2】:
为了搭载 sqreept 的回答,这里有一个 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
【讨论】:
谢谢。我能够将它与这个答案结合使用***.com/a/2588648/2860243 新的更新方法可能无法单独完成。 CPython 在执行 dict-to-dict 更新时绕过特殊方法(它根据对象的内存结构确定)。您可能需要避免直接对 dict 进行子类化(您可以将__class__
设置为 dict 而不是通过 isinstance 检查)。这可能不适用于这种情况(我正在做相反的事情,在提取而不是输入时转换键和值),但我留下这个评论以防万一它有帮助
这适用于添加新值。如果您希望构造函数也可以工作,您还需要覆盖 init 并处理过滤掉 kwargs 的 None 值。【参考方案3】:
相当老的问题,但这里有一个替代方法,即使用空 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)
【讨论】:
哦,非常整洁。我最喜欢这个答案! 同意,除了你通过在循环中多次更新所做的所有额外工作:摆脱 for 循环并执行此操作:params.update(key: val for key, val in kwargs if val is not None)
【参考方案4】:
我做到了。希望对您有所帮助。
apple = 23
orange = 10
a =
'apple' : apple,
'orange' if orange else None : orange
预期输出:'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
【讨论】:
这是一个巧妙的技巧。那么你最后只需要清除一个键:None
。我建议只对键做条件(如果你担心值在那里,只需在 dict 声明的最后一行添加None: None
),然后再做del a[None]
。
这是最好的答案。只需添加a.pop(None)
即可完美
这是一种不好的做法。如果语言不支持,最好不要添加额外的操作来绕过这个,(如 a.pop、del a[None] 和类似的)。
您需要显式测试is not None
,如问题中所述:我只想添加None
以外的值。尝试使用orange = ""
或orange = 0
,它们是None
以外的值。
除此之外:克制使用技巧的冲动。此代码需要附加声明(a.pop(None)
或 if None in a: del a[None]
),并且需要在注释中为需要维护代码的未来开发人员提供解释。【参考方案5】:
我建议的一种技术是为此使用dictionary unpacking operatior。
apple = 'green'
orange = None
params = urllib.urlencode(
'apple': apple,
**( 'orange': orange if orange else )
)
说明
基本上,如果orange
是None
,那么上面的字典就简化为
'apple': apple,
**()
# which results in just
'apple': apple,
如果orange
不是None
,则相反:
'apple': apple,
**( "orange": orange )
# which results in just
'apple': apple,
'orange': orange
可读性是有条件地添加内联键的缺点。可以创建一个函数来帮助解决可读性问题。
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 )
【讨论】:
去掉顶部表达式的大括号【参考方案6】:你可以在赋值后清除None:
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)
【讨论】:
等价于d = k:v for k,v in d.items() if v
这也将清除评估为 False 的值。你应该改用if dictparams[k] is None
。
d = k:v for k,v in d.items() if v is not None
,然后【参考方案7】:
另一个有效的答案是,您可以创建自己的不存储 None 值的类 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
产量:
'orange': 'orange'
【讨论】:
相当优雅,我只加了一个默认的get value def get(self, key, default_value=None): return self.container.get(key, default_value)【参考方案8】:我发现使用generator function 更容易理解,也足够灵活。它也适用于 Python 2 和 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)))
【讨论】:
【参考方案9】:我真的很喜欢这里答案中的巧妙技巧:https://***.com/a/50311983/3124256
但是,它有一些陷阱:
-
重复的
if
测试(重复键和值)
讨厌的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
条目确保了两件事:
None
键将始终存在(del
不会引发错误)
'None values' 的内容永远不会存在于 dict 中(以防您之后忘记del
)
如果您不担心这些事情,您可以将其排除在外,并将 del 包裹在 try...except
中(或检查 None
键是否存在于 del
ing 之前)。要解决第 2 个问题,您还可以对值(除了键)进行条件检查。
【讨论】:
【参考方案10】:fruits = [("apple", get_apple()), ("orange", get_orange()), ...]
params = urllib.urlencode( fruit: val for fruit, val in fruits if val is not None )
【讨论】:
所以我们需要为每个变量提供一个getter
。为什么不这样做: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 )
【参考方案11】:
有一个违反直觉但可靠的技巧,可以重用您要排除它的其他道具名称。
'orange' if orange else 'apple': orange,
'apple': apple,
在这种情况下,后一个“苹果”将覆盖前一个“苹果”,有效地将其删除。请注意,条件表达式应高于真实表达式。
【讨论】:
我不建议这样做,因为这取决于您编写密钥的顺序。它很容易出现错误。 克制使用“巧妙技巧”的冲动。稍后当您将'apple'
键重命名为 'pear'
并错过第一行时,您不会感谢自己,因此引入了一个奇怪的错误。可读性很重要!
@MartijnPieters 我有没有提到这是一个黑客?它应该被视为黑客攻击。【参考方案12】:
您可以使用字典推导式使用单个条件处理所有可选项目:
apple = "red"
orange = None
dictparams =
key: value for key, value in
"apple": apple,
"orange": orange
.items()
if value is not None
在这种情况下dictparams
结果将不包含"orange"
,因为orange
是None
:
'apple': 'green'
【讨论】:
以上是关于仅在满足条件时才添加到字典的主要内容,如果未能解决你的问题,请参考以下文章