仅在满足条件时才添加到字典

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 )
)

说明

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


    '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 键是否存在于 deling 之前)。要解决第 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",因为orangeNone

'apple': 'green'

【讨论】:

以上是关于仅在满足条件时才添加到字典的主要内容,如果未能解决你的问题,请参考以下文章

仅在满足某些条件时才调用 Safari Content Blocker 扩展?

如何编写查询以仅在满足条件时才显示值?

仅在满足条件时如何启动调试器

仅当每个元素都满足条件时才选择组

即使条件不满足,处理库 CSV 也会继续添加行

仅在满足条件时如何应用edgeIgnoringSafeArea?