字典和默认值


213

假设connectionDetails是Python字典,那么像这样的重构代码的最佳,最优雅,最“ pythonic”的方法是什么?

if "host" in connectionDetails:
    host = connectionDetails["host"]
else:
    host = someDefaultValue

Answers:


311

像这样:

host = connectionDetails.get('host', someDefaultValue)

40
请注意,第二个参数是一个值,而不是键。
Marcin 2012年

7
+1为可读性,但if/else速度更快。这可能会或可能不会起作用。
蒂姆·皮茨克

7
@Tim,您能否提供为什么if/else更快的参考?
nishantjr 2014年

2
@Tim:我以为使用高级语言的优势之一是解释器将能够“看到”函数内部并对其进行优化-用户将无需像微优化那样多地进行处理。 。这不是JIT编译之类的功能吗?
nishantjr 2014年

3
@nishantjr:Python(至少是CPython,最常见的变体)没有JIT编译。PyPy确实可以更快地解决此问题,但是由于标准Python到目前为止对我而言一直足够快,因此我尚未安装。通常,在现实生活中不太重要-如果您需要进行时间紧迫的数字运算,Python可能不是首选语言……
Tim Pietzcker 2014年

99

您也可以这样使用defaultdict

from collections import defaultdict
a = defaultdict(lambda: "default", key="some_value")
a["blabla"] => "default"
a["key"] => "some_value"

您可以传递任何普通函数而不是lambda:

from collections import defaultdict
def a():
  return 4

b = defaultdict(a, key="some_value")
b['absent'] => 4
b['key'] => "some_value"

7
我来这里的目的不是OP的问题,您的解决方案可以解决这个问题。
2015年

我会对其+1,但遗憾的是它不适合get或类似方法。
2015年

对于确保添加到词典中的默认键来说,这个答案对我很有用。我的实现太长了,无法在StackOverflow答案中进行描述,因此我在此处进行了介绍。persagen.com/2020/03/05/...
维多利亚斯图尔特

24

虽然这.get()是一个很好的习惯用法,但是它比if/else(比try/except大多数情况下可以预期字典中键的存在要慢):

>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.07691968797894333
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.4583777282275605
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(1, 10)")
0.17784020746671558
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="a=d.get(2, 10)")
0.17952161730158878
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.10071221458065338
>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}", 
... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.06966537335119938

3
我仍然不明白为什么 if/then会更快。这两种情况都需要字典查找,除非的调用get()如此慢得多,还有什么占放缓?
詹斯

1
@Jens:函数调用很昂贵。
蒂姆·皮茨克

1
是的,在人口稠密的词典中哪个不应该是大问题,对吗?这意味着,如果实际查找成本很高,则函数调用不会有太大关系。这可能仅在玩具示例中很重要。
AturSams,2015年

2
@zehelvion:字典查找O(1)与字典大小无关,因此函数调用开销是相关的。
蒂姆·皮茨克

35
如果调用函数的开销使您决定不使用get,这是奇怪的。使用您的团队成员最能阅读的内容。
Jochen Bedersdorfer

19

对于多个不同的默认值,请尝试以下操作:

connectionDetails = { "host": "www.example.com" }
defaults = { "host": "127.0.0.1", "port": 8080 }

completeDetails = {}
completeDetails.update(defaults)
completeDetails.update(connectionDetails)
completeDetails["host"]  # ==> "www.example.com"
completeDetails["port"]  # ==> 8080

3
这是一个很好的惯用解决方案,但有一个陷阱。如果提供connectionDetails None或把EmptyString作为键值对中的值之一,则可能会导致意外结果。该defaults词典可能有它的价值的一个无意的空白。(见stackoverflow.com/questions/6354436
dreftymac

9

python词典中有一个方法可以做到这一点: dict.setdefault

connectionDetails.setdefault('host',someDefaultValue)
host = connectionDetails['host']

但是,与问题所要求的不同,此方法将if 的值设置connectionDetails['host']someDefaultValueif host尚未定义。


1
请注意,它会setdefault()返回值,因此也可以正常工作:host = connectionDetails.setdefault('host', someDefaultValue)。请注意,connectionDetails['host']如果之前没有该密钥,它将设置为默认值。
ash108 '16

7

(这是一个很晚的答案)

一种替代方法是对类进行子dict类化并实现__missing__()方法,如下所示:

class ConnectionDetails(dict):
    def __missing__(self, key):
        if key == 'host':
            return "localhost"
        raise KeyError(key)

例子:

>>> connection_details = ConnectionDetails(port=80)

>>> connection_details['host']
'localhost'

>>> connection_details['port']
80

>>> connection_details['password']
Traceback (most recent call last):
  File "python", line 1, in <module>
  File "python", line 6, in __missing__
KeyError: 'password'

4

测试@Tim Pietzcker对Python 3.3.5的PyPy(5.2.0-alpha0)情况的怀疑,我发现确实两者.get()if/ else方式的执行情况相似。实际上,在if / else情况下,如果条件和赋值涉及相同的键,则似乎只有一次查找(与最后一次有两次查找的情况比较)。

>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[1]\nexcept KeyError:\n a=10")
0.011889292989508249
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="try:\n a=d[2]\nexcept KeyError:\n a=10")
0.07310474599944428
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(1, 10)")
0.010391917996457778
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="a=d.get(2, 10)")
0.009348208011942916
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 1 in d:\n a=d[1]\nelse:\n a=10")
0.011475925013655797
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=10")
0.009605801998986863
>>>> timeit.timeit(setup="d={1:2, 3:4, 5:6, 7:8, 9:0}",
.... stmt="if 2 in d:\n a=d[2]\nelse:\n a=d[1]")
0.017342638995614834

1

您可以将lamba函数用作单线。制作一个connectionDetails2可以像函数一样访问的新对象 ...

connectionDetails2 = lambda k: connectionDetails[k] if k in connectionDetails.keys() else "DEFAULT"

现在使用

connectionDetails2(k)

代替

connectionDetails[k]

如果k在键中,则返回字典值,否则返回"DEFAULT"


我赞成您,但是您解决方案的问题是,字典适用于[],而lambdas适用于()
yukashima huksay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.