【问题标题】: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 语法。

标签: python variables dictionary urllib


【解决方案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 日),stackoverflow.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

【讨论】:

  • 谢谢。我能够将它与这个答案结合使用stackoverflow.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://stackoverflow.com/a/50311983/3124256

    但是,它有一些陷阱:

    1. 重复的if 测试(重复键和值)
    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. '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'}
      

      【讨论】:

        猜你喜欢
        • 2021-08-25
        • 1970-01-01
        • 2018-01-22
        • 1970-01-01
        • 1970-01-01
        • 2011-05-13
        • 2013-06-11
        • 1970-01-01
        相关资源
        最近更新 更多