有比dict.get(<thing>, dict.get(<other>))更好的方法吗?涉及it技术。

4

在Python中,

>>> x = {'spam': 'a lot'}
>>> x.get('eggs', x.get('spam'))
'a lot'

但是使用 .get() 这种组合的方式似乎有些别扭。

有没有更好的方法来表达“这可能是两个键中的一个,我不在意哪一个,只想获取值”的意思呢?


我建议使用defaultdict。这种模式有一些奇怪的副作用,而且这是一个我从未考虑过使用的模式。 - David Ehrmann
@DavidEhrmann:如何使用defaultdict来解决这个问题? OP的版本会带来什么奇怪的副作用? - John Y
@JohnY 这感觉像是一个默认模式(虽然可能不是),因此使用defaultdict。副作用是,由于它感觉像那样,更改x["spam"]会改变get的行为。 - David Ehrmann
2
@DavidEhrmann:嗯,OP真正想做的是(1)尝试'eggs',如果不行,则尝试'spam',如果还不行,则获取None;或(2)获取'eggs'或'spam'中的任意一个,无论哪个失败,都获取None。你会使用哪个工厂函数来获得这些行为的defaultdict - John Y
如果你不介意在返回值的同时将 d['spam'] 的值写入 d['eggs'],你可以像这样做:d = defaultdict(partial(d.get, 'spam'))... - Corley Brigman
5个回答

4
我能想到的最“正确”的解决方案也是最丑陋的。这很糟糕。我的两个标准:
- 避免`0或None`的情况。 - 如果一个键存在,则短路。
尽管它很丑陋,但实际上是执行最快的方案。
x['eggs'] if 'eggs' in x else x.get('spam')

timeit结果:

>>> op       = lambda: x.get('eggs', x.get('spam'))
>>> aaron    = lambda: x.get('eggs') or x.get('spam')
>>> ndpu     = lambda: filter(None, map(x.get, ['eggs', 'spam']))[0]
>>> mhlester = lambda: x['eggs'] if 'eggs' in x else x.get('spam')
>>>
>>> timeit(op, number=100000)
0.04057041245972073
>>> timeit(aaron, number=100000)
0.04477326960257777
>>> timeit(ndpu, number=100000)
0.13210876799140614
>>> timeit(mhlester, number=100000)
0.03425499248118058

1
我喜欢 Python 三元表达式。除了它与其他语言不同之外,我从来没有理解为什么它得到的尊重如此之少。 - user890167
除非你的版本是唯一一个可以引发异常的。 - John Y
编辑过的。它比之前的“get”慢了50%,但仍然是最快的。 - mhlester
速度不应该是决定因素,除非必须如此。 - gabe
同意。尽管开销更高,但我仍然最喜欢 OP 的。 - mhlester
我认为你的时间假设了最坏的情况,可能在最好的情况下表现不佳。 - Russia Must Remove Putin

1

我认为我会倾向于这样做,因为它可能更易读:

>>> x = {'spam': 'a lot'}
>>> results = x.get('eggs') or x.get('spam')
>>> results
'a lot'

你的代码看起来更加高效,但它必须评估两个函数,因此使用or进行短路处理实际上更好。 如果x['eggs']返回一个空值,这可能不是你想要的结果,请注意行为(但你说你不介意得到任何一个值)。
>>> results = 0 or 'a lot'
>>> results
'a lot'
>>> results = '' or 'a lot'
>>> results
'a lot'

意外的边缘情况可能类似于这样:
>>> results = '' or None
>>> print(results)
None

1
但是如果 x['eggs']0 呢?我想要的是 0,而不是 None - mhlester
1
就效率而言,你的实际上更高效,因为它可以短路。函数在调用之前需要评估所有参数,因此在你的代码中两个 get 都保证会被调用。 - mhlester
@mhlester:或者更糟糕的是,你可能得到了'很多' - John Y
对于 x['eggs'] = 0,True 可能会产生意想不到的结果,我会做个记录。 - Russia Must Remove Putin
像 @mhlester 说的那样,实际上更快,而且更容易阅读。 - David Ehrmann

1
如果你只是想要一行代码,我认为已经发布的三元运算符可以满足你的需求:
result = myDict['eggs'] if 'eggs' in myDict else myDict.get('spam')

如果你只是想要易读的答案,我认为你应该使用 if 语句。
result = myDict.get('eggs', 'fail')
if result == 'fail':
    result = myDict.get('spam')

这样分割有什么不利之处吗?


1
不需要使用myDict.get('eggs'),因为如果你在代码的那个部分,'eggs'已经被确认在myDict中。这个答案类似于mhlester的答案,但是你还建议了一个更长的形式,在myDict['eggs']实际上等于'fail'的情况下是不正确的。此外,is不是字符串相等的适当测试。两个非常长的字符串可以相等(使用==),但不是相同的(使用is)。 - John Y
哦,没错。我太习惯于使用“my_value不是None”的方式了,所以反过来也使用了同样的方式。哎呀。但是希望大家能理解,“fail”的值最好不要是“eggs”中的任何一个值——在此情况下,不应该使用类似“eggs”的值作为标记。 - user890167

1
我喜欢Stick的答案,但如果你要做超过两次的类似操作(遵循“三次原则”),我建议将其包装在一个类中。 另外:速度不应该是首要考虑因素,除非实际上需要。追求简单和易读性。
class MyDict(dict):
    def get(self, first_key, second_key, default=None):
        if first_key in self:
            return self[first_key]
        elif second_key in self:
            return self[second_key]
        else:
            return default

x = MyDict({"spam":"a lot"})
print x.get("eggs","spam")
# "a lot"    

2
我本来想忽略这个答案,但是重新阅读原帖的问题后,我发现他实际上并没有要求最短或最快的解决方案,只是要一个“更好”的解决方案。如果需要重复使用,那么这绝对是更好的选择。你甚至可以通过与map/filter或列表推导相结合(如ndpu的答案中所示)来扩展到任意数量的键检查。 - John Y
我一直在尝试这个答案的变体,我真的非常喜欢它。根据 OP 被分配的工作,这可能有点过度,但实现非常简单。 - user890167

-1
使用 mapfilter
>>> x = {'spam': 'a lot'}
>>> filter(None, map(x.get, ('eggs', 'spam'))) # [0]
['a lot']

使用列表推导式:

>>> [x.get(k) for k in ('eggs', 'spam') if k in x]
['a lot']

在需要检查两个以上键时可能会很有用:

>>> filter(None, map(x.get, ('eggs', 'foo', 'bar', 'spam')))
['a lot']

你为什么在第一个例子中“注释掉”了[0],而在第二个例子中根本没有包括它?除了这些问题之外,我认为这个答案实际上是有用的,以防有许多可能的键需要检查。 - John Y
1
为了完整起见,值得注意的是,这也可以使用列表推导式来完成,而不是使用mapfilter - John Y
@JohnY 结果可能为空列表,因此我认为最好不要在此方法中使用 [0] 以增强通用性... 如果需要的话,仍然需要使用 if - ndpu
@JohnY 如果你只需要一个值,那就无所谓了,请查看问题。如果你需要多个值 - 它已经可以工作了。 - ndpu
我检查了这个问题。如果你读了 OP 的代码,它所做的就是获取一个确切的值。如果字典中没有任何一项键,则该值为 None - John Y
显示剩余2条评论

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