在字典的字面值中是否可以有可选键?

5

字典字面量中是否有可能拥有“可选”键,而不是在if语句中添加它们?

像这样:

a = True
b = False
c = True
d = False

obj = {
    "do_a": "args for a" if a,
    "do_b": "args for b" if b,
    "do_c": "args for c" if c,
    "do_d": "args for d" if d,
}

#expect:
obj == {
    "do_a": "args for a",
    "do_c": "args for c",
}

为了更好的理解,请允许我加入一些背景信息:

我知道如何处理逻辑 :) 我只是想避免使用if语句,因为我的对象是一个大型的数据块,代表着声明式逻辑,所以移动东西就有点像"意大利面条式编码",而这并不是本来就应该是过程化的。

我希望对象的值看起来像是一个查询。

实际上,它是一个elasticsearch查询,所以它会像这样:

{
    "query": {
        "bool": {
            "must": [
                 <FILTER A>,
                 <FILTER B>,  # would like to make this filter optional
                 <FILTER C>,
                 {
                     "more nested stuff" : [ ... ]
                 }
             ],
             "other options": [ ... ]
        },
        "other options": [ ... ]
    },
    "other options": [ ... ]
}

我的目标可能有些可疑,就是让它看起来像一个查询,你可以查看并理解其形状,而不必跟踪if语句。 也就是说,没有"filters": [f for f in filters if f.enabled], 因为这样你就必须去寻找过滤器,而这里所有的过滤器都是可选常量。


zip是你的好朋友。 - dawg
你是否考虑将 <FILTER X> 设计成一种只在需要时才计算的生成器? - dawg
@coldspeed:作者添加了相关的上下文信息,你为什么要删除它?已回滚... - dawg
@dawg:因为它看起来像是他们的“上下文”改变了他们的问题的性质,而且也不是很相关。 - cs95
4个回答

10
我认为答案是否定的,正如其他答案所述,但这是我迄今为止得到的最接近的答案... 尽管有点让人感到令人憎恶的'wtf'。
a = True
b = False
c = True
d = False

obj = {
    **({"do_a": "args for a"} if a else {}),
    **({"do_b": "args for b"} if b else {}),
    **({"do_c": "args for c"} if c else {}),
    **({"do_d": "args for d"} if d else {}),
}

#expect:
assert(obj == {
        "do_a": "args for a",
        "do_c": "args for c",
    })

如果您想将可选性放在某个函数中:

def maybe(dictionary, condition, default=None):
    return dictionary if condition else default or {}

obj = {
    **maybe({"do_a": "args for a"}, a),
    **maybe({"do_b": "args for b"}, b),
    **maybe({"do_c": "args for c"}, c),
    **maybe({"do_d": "args for d"}, d),
}

这种代码的问题在于条件和结果之间越来越远(想象一下,最终我们将大型字典传递给maybe中的第一个参数)。

5

首先定义您的键、参数和布尔变量列表:

keys = ["do_a", "do_b", ...]
args = ["args for a", "args for b", ...]
mask = [a, b, ...]

现在,使用mask列表来确定插入哪些键,构建obj对象:
obj = {k : a for k, a, m in zip(keys, args, mask) if m}

或者说,

obj = {}
for k, a, m in zip(keys, args, mask):
    if m:
        obj[k] = a

5

在字面值中,您不能使用“可选值”。字面值中表达式的结果 始终 插入到结果中。

然而我认为明确的 if 语句可能更好地符合需要:

a = True
b = False
c = True
d = False

obj = {}
if a: obj["do_a"] = "args for a"
if b: obj["do_b"] = "args for b"
if c: obj["do_c"] = "args for c"
if d: obj["do_d"] = "args for d"

如果你真的不喜欢使用 if,这里提供一些替代方案:

  • You could also use a different value in case the argument is "false"-ey and then filter the dictionary:

    _to_remove = object()
    
    obj = {
        "do_a": "args for a" if a else _to_remove,
        "do_b": "args for b" if b else _to_remove,
        "do_c": "args for c" if c else _to_remove,
        "do_d": "args for d" if d else _to_remove,
    }
    
    obj = {key: value for key, value in obj.items() if value is not _to_remove}
    
  • Or use itertools.compress and the dict builtin:

    key_value_pairs = [
        ("do_a", "args for a"), 
        ("do_b", "args for b"), 
        ("do_c", "args for c"), 
        ("do_d", "args for d")
    ]
    
    from itertools import compress
    
    obj = dict(compress(key_value_pairs, [a, b, c, d]))
    

构建一个字典,然后再删除那些键似乎有点过度,而且当需要删除的键比保留的键多时,情况可能会更糟。 - cs95
1
同意 - 但我认为这并不是过度的,因为在某个时刻所有的键、值和条件都必须存在。它们是在列表还是字典中并没有太大的区别。最终你总是会得到一个包含所需项的字典,你需要一些循环来实现这一点(或者重复的if语句)。如果这真的是瓶颈(无论是速度还是内存),那么字典比类似的列表/元组分配了稍微多一点的空间,并且它执行更多的if语句可能会有所影响。但请注意,这仅影响我的第二种方法,我真的会选择第一种方法。 - MSeifert
1
我意识到我在提问时要求布尔值作为答案,所以我想你的回答是目前唯一符合标准的候选者,因为其他人返回建议而不是陈述事物是否可能 - 你的回答以“否”开头。 - JamEnergy

2

这里提供一种不同的方法。根据您的定义:

a = True
b = False
c = True
d = False

然后,您可以将字面值构造为三个成员元组:

li=[
    ("do_a", "args for a",a),
    ("do_b", "args for b",b),
    ("do_c", "args for c",c),
    ("do_d", "args for d",d)
]

这相当于使用三个字面列表的zip,但对于人眼来说,使用更短的列表更容易理解意图。
然后可以有条件地构建您的字典,如下所示:
>>> dict([(k,v) for k,v,f in li if f])
{'do_c': 'args for c', 'do_a': 'args for a'}

在这篇文章中进行了澄清后,您可以将函数作为字典值使用,并在创建字典时(或更新字典时)直接调用该函数:

def filter_a():
    # some expensive function...
    return "<FILTER A>"

def filter_b():
    return "<FILTER B>"  

def filter_c():
    return "<FILTER C>"    

def filter_d():
    return "<FILTER D>" 

li=[
    ("do_a", filter_a, a),
    ("do_b", filter_b, b),
    ("do_c", filter_c, c),
    ("do_d", filter_d, d)
]

然后只有相关的过滤器函数被调用,如构造函数所示:

>>> dict((k,v()) for k,v,f in li if f)
{'do_c': '<FILTER C>', 'do_a': '<FILTER A>'}

那么B和D永远不会被调用。

更好的方法是,编写逻辑使得过滤器X成为生成器的一种形式,并且只在需要时返回数据。


我喜欢生成器的想法,我要看看是否有一种方法可以组合这样的东西,以便结果的语法不会掩盖我的对象的“形状”。 同时也在思考是否可以使用双星号策略来创建字典……就像{**({things} if X else {})}这样。 - JamEnergy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接