【问题标题】:Dictionaries and default values字典和默认值
【发布时间】:2012-02-20 09:42:06
【问题描述】:

假设 connectionDetails 是一个 Python 字典,那么像这样重构代码的最好、最优雅、最“pythonic”的方式是什么?

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

【问题讨论】:

    标签: python dictionary coding-style


    【解决方案1】:

    像这样:

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

    【讨论】:

    • 请注意,第二个参数是一个值,而不是一个键。
    • +1 以提高可读性,但 if/else 更快。这可能会也可能不会起作用。
    • @Tim,你能提供一个关于为什么if/else 更快的参考吗?
    • @Tim:我认为使用高级语言的优点之一是解释器能够“看到”函数内部并对其进行优化——用户不会尽可能多地处理微优化。这不就是 JIT 编译之类的东西吗?
    • @nishantjr:Python(至少是 CPython,最常见的变体)没有 JIT 编译。 PyPy 确实可以更快地解决这个问题,但我还没有安装它,因为到目前为止标准 Python 对我的目的来说一直足够快。一般来说,这在现实生活中不太重要 - 如果您需要进行时间紧迫的数字运算,Python 可能不是首选语言...
    【解决方案2】:

    您也可以像这样使用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"
    

    【讨论】:

    • 我来这里是为了解决一些与 OP 的问题不同的问题,而您的解决方案正好解决了它。
    • 我会 +1,但遗憾的是它不适合 get 或类似方法。
    • 这个答案对我来说很有用,可以确保字典的添加包括默认键。我的实现有点太长了,无法在 StackOverflow 答案中描述,所以我在这里写了。 persagen.com/2020/03/05/…
    【解决方案3】:

    虽然.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
    

    【讨论】:

    • 我仍然不明白 为什么 if/then 会更快。这两种情况都需要字典查找,除非get() 的调用速度如此慢得多,否则还有什么原因导致速度变慢?
    • @Jens:函数调用很昂贵。
    • 在人口众多的字典中,这应该不是什么大问题,对吗?这意味着如果实际查找成本很高,则函数调用并不重要。它可能只在玩具示例中很重要。
    • @zehelvion:无论字典大小如何,字典查找都是O(1),因此函数调用开销是相关的。
    • 如果调用函数的开销会让你决定不使用 get,那就太奇怪了。使用您的团队成员最能阅读的内容。
    【解决方案4】:

    对于多个不同的默认值试试这个:

    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
    

    【讨论】:

    • 这是一个很好的惯用解决方案,但有一个陷阱。如果 connectionDetails 提供 None 或 emptyString 作为键值对中的值之一,则可能会导致意外结果。 defaults 字典可能会无意中删除其中一个值。 (另见stackoverflow.com/questions/6354436
    【解决方案5】:

    这不完全是问的问题,但在 python 字典中有一个方法:dict.setdefault

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

    但是,如果尚未定义键 host,则此方法将 connectionDetails['host'] 的值设置为 someDefaultValue,这与问题所要求的不同。

    【讨论】:

    • 请注意 setdefault() 返回值,所以这也适用:host = connectionDetails.setdefault('host', someDefaultValue)。请注意,如果之前没有密钥,它会将connectionDetails['host'] 设置为默认值。
    【解决方案6】:

    (这是一个迟到的答案)

    另一种方法是继承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'
    

    【讨论】:

      【解决方案7】:

      测试@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
      

      【讨论】:

        【解决方案8】:

        您可以为此使用lamba 函数作为单行。创建一个新对象connectionDetails2,它像函数一样被访问...

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

        现在使用

        connectionDetails2(k)
        

        而不是

        connectionDetails[k]
        

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

        【讨论】:

        • 我支持你,但你的解决方案的问题是 dicts 与 [] 一起使用,但 lambdas 与() 一起使用
        猜你喜欢
        • 2011-06-29
        • 1970-01-01
        • 2019-08-17
        • 2021-07-27
        • 2015-05-07
        • 2011-05-04
        • 2023-03-26
        • 2011-12-14
        • 1970-01-01
        相关资源
        最近更新 更多