【问题标题】:dict.get() method returns a pointerdict.get() 方法返回一个指针
【发布时间】:2011-11-01 12:07:42
【问题描述】:

假设我有这个代码:

my_dict = {}
default_value = {'surname': '', 'age': 0}

# get info about john, or a default dict
item = my_dict.get('john', default_value)

# edit the data
item[surname] = 'smith'
item[age] = 68

my_dict['john'] = item

如果我们现在检查 default_value 的值,问题就很清楚了:

>>> default_value
{'age': 68, 'surname': 'smith'}

很明显,my_dict.get() 并没有返回 default_value 的 value,而是一个指向它的指针 (?)。

可以通过将代码更改为:

item = my_dict.get('john', {'surname': '', 'age': 0})

但这似乎不是一个好方法。有什么想法吗,cmets?

【问题讨论】:

    标签: python dictionary pass-by-reference


    【解决方案1】:

    因为my_dict.get('john', default_value.copy()) 每次 get 被调用时都会创建一个默认字典的副本(即使存在并返回 'john'),使用这个 try/except 更快也很好选项:

    try:
        return my_dict['john']
    except KeyError:
        return {'surname': '', 'age': 0}
    

    或者,您也可以使用defaultdict

    import collections
    
    def default_factory():
        return {'surname': '', 'age': 0}
    
    my_dict = collections.defaultdict(default_factory)
    

    【讨论】:

      【解决方案2】:
      item = my_dict.get('john', default_value.copy())
      

      总是在 Python 中传递引用。

      这对于 strinttuple 等不可变对象无关紧要,因为您无法更改它们,只能将名称指向不同的对象,但对于可变对象如listsetdict。您需要习惯这一点并始终牢记这一点。

      编辑: Zach Bloom 和 Jonathan Sternberg 都指出了可以用来避免在每次查找时调用 copy 的方法。您应该使用 defaultdict 方法,类似于 Jonathan 的第一种方法,或者:

      def my_dict_get(key):
          try:
              item = my_dict[key]
          except KeyError:
              item = default_value.copy()
      

      如果my_dict 中几乎总是存在密钥,这将比if 快,如果dict 很大。您不必将其包装在一个函数中,但您可能不希望每次访问 my_dict 时都需要这四行。

      请参阅 Jonathan 的回答,了解带有小 dict 的时间。 get 方法在我测试的所有尺寸上都表现不佳,但try 方法在大尺寸下表现更好。

      【讨论】:

      • 这是python的一个非常重要的原则——所有值都是通过引用传递的。这些引用的可变性是一个完全不同的问题(尽管它经常以这种方式绊倒人们)。
      • 我确信我以前读过它,但是当你长时间不使用一种语言时,你往往会忘记一些事情。谢谢你说清楚。
      • 为什么您的答案与问题中提供的答案有任何不同。问题似乎更多是关于找到一种优雅的方式来返回字典的新实例,但只在需要时创建它。
      • 问题是关于每次他需要执行get 时不要输入{'surname': '', 'age': 0},以及如何在Python 中传递参数,而不是每次都创建它。但是,我已经指出了其他提到该问题的答案,并添加了一个替代方案。
      • @agf 查看我对速度计时的回答。使用 try/except 会比他想要的慢得多。
      【解决方案3】:

      不要使用get。你可以这样做:

      item = my_dict.get('john', default_value.copy())
      

      但这需要复制字典即使字典条目存在。相反,请考虑仅检查该值是否存在。

      item = my_dict['john'] if 'john' in my_dict else default_value.copy()
      

      唯一的问题是它会为 'john' 执行两次查找,而不仅仅是一次。如果您愿意使用额外的一行(并且 None 不是您可以从字典中获得的可能值),您可以这样做:

      item = my_dict.get('john')
      if item is None:
          item = default_value.copy()
      

      编辑:我想我会与 timeit 进行一些速度比较。 default_value 和 my_dict 是全局变量。如果钥匙在那里,如果有遗漏,我都做了。

      使用异常:

      def my_dict_get():
          try:
              item = my_dict['key']
          except KeyError:
              item = default_value.copy()
      
      # key present: 0.4179
      # key absent: 3.3799
      

      使用 get 并检查它是否为 None。

      def my_dict_get():
          item = my_dict.get('key')
          if item is None:
              item = default_value.copy()
      
      # key present: 0.57189
      # key absent: 0.96691
      

      用特殊的 if/else 语法检查它的存在

      def my_dict_get():
          item = my_dict['key'] if 'key' in my_dict else default_value.copy()
      
      # key present: 0.39721
      # key absent: 0.43474
      

      天真地复制字典。

      def my_dict_get():
          item = my_dict.get('key', default_value.copy())
      
      # key present: 0.52303 (this may be lower than it should be as the dictionary I used was one element)
      # key absent: 0.66045
      

      在大多数情况下,除了使用异常之外的所有内容都非常相似。出于某种原因,特殊的 if/else 语法似乎时间最短(不知道为什么)。

      【讨论】:

      • 这是一个很好的观点,我会在我的答案中添加一个注释。用'john' in my_dict 代替my_dict.has_key('john')my_dict.get('john') 代替my_dict.get('john', None) 怎么样?
      • 比起has_key,我更喜欢in的使用。我忘记了它的存在。我不知道 my_dict.get('john') 默认返回 null(我以为是 IndexError)。
      • 或者使用这个: from collections import defaultdict mydict = defaultdict(default_value.copy) 然后当你做 mydict[key-thats-not-here] 时,你传递给构造函数的函数会被调用。
      【解决方案4】:

      要意识到的主要一点是,Python 中的 一切 都是通过引用传递的。 C 风格语言中的变量名通常是对象形状的内存区域的简写,分配给该变量会复制另一个对象形状的区域……在 Python 中,变量只是字典中的键(@ 987654322@),而赋值行为只是存储了一个新的引用。 (从技术上讲,everything 是一个指针,但这是一个实现细节。

      这有很多含义,主要的是永远不会有一个对象的隐式副本,因为你将它传递给一个函数,分配它等等。获取副本的唯一方法是显式这样做. python stdlib 提供了一个 copy 模块,其中包含一些东西,包括一个 copy()deepcopy() 函数,用于当您想要显式复制某些内容时。此外,某些类型公开了自己的 .copy() 函数,但这不是标准,也不是一致实现的。其他不可变的方法有时会提供.replace() 方法,该方法会生成变异副本。


      就您的代码而言,传入原始实例显然不起作用,并且提前制作副本(当您可能不需要时)是浪费。所以最简单的解决方案可能是......

      item = my_dict.get('john')
      if item is None:
          item = default_dict.copy()
      

      如果.get() 支持传入默认值构造函数,这在这种情况下会很有用,但这可能会为边界情况过度设计基类。

      【讨论】:

        【解决方案5】:

        在 Python 中,dict 既是对象(因此它们总是作为引用传递)又是可变的(意味着它们可以在不重新创建的情况下更改)。

        您可以在每次使用时复制您的字典:

        my_dict.get('john', default_value.copy())
        

        你也可以使用defaultdict集合:

        from collections import defaultdict
        
        def factory():
          return {'surname': '', 'age': 0}
        
        my_dict = defaultdict(factory)
        
        my_dict['john']
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-09-14
          • 1970-01-01
          • 1970-01-01
          • 2021-09-21
          • 1970-01-01
          • 1970-01-01
          • 2011-02-11
          • 2012-08-18
          相关资源
          最近更新 更多