【问题标题】:Why doesn't a python dict.update() return the object?为什么 python dict.update() 不返回对象?
【发布时间】:2010-11-29 23:51:22
【问题描述】:

我正在努力:

award_dict = {
    "url" : "http://facebook.com",
    "imageurl" : "http://farm4.static.flickr.com/3431/3939267074_feb9eb19b1_o.png",
    "count" : 1,
}

def award(name, count, points, desc_string, my_size, parent) :
    if my_size > count :
        a = {
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }
        a.update(award_dict)
        return self.add_award(a, siteAlias, alias).award

但如果觉得功能真的很麻烦,我宁愿这样做:

        return self.add_award({
            "name" : name,
            "description" : desc_string % count,
            "points" : points,
            "parent_award" : parent,
        }.update(award_dict), siteAlias, alias).award

为什么更新不返回对象以便您可以链接?

JQuery 这样做是为了进行链接。为什么在 python 中不能接受?

【问题讨论】:

  • * TL;DR newdict = dict(dict001, **dict002)
  • @dreftymac,但这在理解中不起作用。
  • @alancalvitti 是的,这确实是一个需要指出的有效警告。
  • @dreftymac,另一个警告是 dict002 不能有任何整数键(或任何其他非字符串键)

标签: python dictionary language-design language-features


【解决方案1】:

不是不能接受,而是dicts 没有这样实现。

如果您查看 Django 的 ORM,就会发现它大量使用了链接。它并不气馁,您甚至可以从 dict 继承并仅覆盖 update 进行更新和 return self,如果您真的想要的话。

class myDict(dict):
    def update(self, *args):
        dict.update(self, *args)
        return self

【讨论】:

  • 谢谢,这可以修补 dict,我只是想知道为什么 dict() 本身不允许这个功能(因为它就像你演示的那样简单)。 Django 会像这样给字典打补丁吗?
【解决方案2】:

按照惯例,Python 的 API 区分过程和函数。函数根据其参数(包括任何目标对象)计算新值;过程修改对象并且不返回任何内容(即它们返回无)。所以程序有副作用,函数没有。 update 是一个过程,因此它不返回值。

这样做的动机是,否则可能会产生不良副作用。考虑

bar = foo.reverse()

如果 reverse(原地反转列表)也会返回列表,用户可能会认为 reverse 返回分配给 bar 的新列表,而不会注意到 foo 也被修改。通过使 reverse 返回 None,他们立即认识到 bar 不是反转的结果,并且会更接近 reverse 的效果。

【讨论】:

  • 谢谢。为什么不反向也提供不就地执行的选项?表现?做reverse(foo) 感觉很奇怪。
  • 添加一个选项是不合适的:它会根据参数改变方法的性质。但是,方法确实应该具有固定的返回类型(不幸的是,在某些情况下这条规则被打破了)。创建还原副本很容易:只需创建一个副本(使用bar=foo[:]),然后还原该副本。
  • 我认为原因是明确的。在bar = foo.reverse() 中,你可以认为foo 没有被修改。为避免混淆,您同时拥有foo.reverse()bar = reversed(foo)
  • 根据参数改变参数的性质有什么问题?
【解决方案3】:

Python 主要实现了 command-query separation 的实用风格: mutators 返回 None (具有实用性引发的异常,例如 pop;-) 所以它们不可能与访问器混淆(同样,赋值不是表达式,语句-表达式分离是存在的,等等)。

这并不意味着当你真正想要的时候没有很多方法可以合并,例如,dict(a, **award_dict) 会创建一个新的字典,就像你希望.update 返回的那个 - 那为什么如果你真的觉得它很重要,就不要使用它吗?

编辑:顺便说一句,在您的具体情况下,不需要一路创建a

dict(name=name, description=desc % count, points=points, parent_award=parent,
     **award_dict)

创建一个与您的a.update(award_dict) 具有完全相同语义的单个字典(包括,在发生冲突的情况下,award_dict 中的条目会覆盖您明确给出的条目;以获得其他语义,即有明确的条目“赢得”此类冲突,将 award_dict 作为唯一的位置参数传递,关键字之前传递,并且失去 ** 形式 -- @987654332 @ 等)。

【讨论】:

  • 好吧,在我必须制作之后,这将创建另一个字典。我想创建一个字典,然后添加一堆其他值,然后将它交给一个函数。
  • @Paul,这正是你正在做的——有两个语句(比你想要的嵌套方式更具可读性),你“觉得真的很麻烦”。编辑我的答案以显示如何避免完全创建a,顺便说一句,
  • 原始解决方案不可靠。如果 Award_dict 包含已指定的键,则会为重复的关键字参数抛出 SyntaxError。 jamylak 的解决方案 dict(itertools.chain(d1.iteritems(), .. d.iteritems())) 不仅适用于字典具有重复键的情况,而且还可以轻松地允许您稍后将多个字典与字典合并链优先于最终值。
  • 另外,如果 Award_dict 中的键不是字符串,解释器将抛出 TypeError
  • dict(old_dict, old_key=new_value) 不会为关键字抛出多个值并返回新的字典。
【解决方案4】:
>>> dict_merge = lambda a,b: a.update(b) or a
>>> dict_merge({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

请注意,除了返回合并的字典外,它还修改了第一个参数。所以 dict_merge(a,b) 会修改 a。

或者,当然,你也可以内联:

>>> (lambda a,b: a.update(b) or a)({'a':1, 'b':3},{'c':5})
{'a': 1, 'c': 5, 'b': 3}

【讨论】:

  • -1 lambda 不应该这样使用,而是使用常规函数def 代替
  • 甚至不需要 lambda,只需使用 a.update(b) or a
【解决方案5】:
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

【讨论】:

    【解决方案6】:

    在最佳答案上留下的评论没有足够的声誉

    @beardc 这似乎不是 CPython 的东西。 PyPy 给我“TypeError:关键字必须是字符串”

    **kwargs 的解决方案之所以有效,是因为要合并的字典只有字符串类型的键

    >>> dict({1:2}, **{3:4})
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: keyword arguments must be strings
    

    >>> dict({1:2}, **{'3':4})
    {1: 2, '3': 4}
    

    【讨论】:

    • 在一个衬垫中使用的巧妙解决方案。
    【解决方案7】:

    尽可能接近您提出的解决方案

    from collections import ChainMap
    
    return self.add_award(ChainMap(award_dict, {
        "name" : name,
        "description" : desc_string % count,
        "points" : points,
        "parent_award" : parent,
    }), siteAlias, alias).award
    

    【讨论】:

      【解决方案8】:

      我自己只是在 Python 3.4 中尝试过这个(所以无法使用花哨的 {**dict_1, **dict_2} 语法)。

      我希望能够在字典中使用非字符串键并提供任意数量的字典。

      另外,我想制作一本新字典,所以我选择不使用 collections.ChainMap(这也是我最初不想使用 dict.update 的原因。

      这是我最后写的:

      def merge_dicts(*dicts):
          all_keys  = set(k for d in dicts for k in d.keys())
          chain_map = ChainMap(*reversed(dicts))
          return {k: chain_map[k] for k in all_keys}
      
      merge_maps({'1': 1}, {'2': 2, '3': 3}, {'1': 4, '3': 5})
      # {'1': 4, '3': 5, '2': 2}
      

      【讨论】:

        【解决方案9】:

        这很简单:

        (lambda d: d.update(dict2) or d)(d1)
        

        或者,如果不修改字典很重要:

        (lambda d: d.update(dict2) or d)(d1.copy())
        

        【讨论】:

        • 为什么?我有一个在使用return d1.update(d2) 后返回 None 的类。让我们考虑d2 来自类似self.one_function() 的东西。这种 lambda 方法背后的理论是什么?感谢您的回答。
        【解决方案10】:

        对于那些迟到的人,我已经安排了一些时间(Py 3.7),表明基于.update() 的方法在保留输入时看起来更快(~5%),并且明显更快(~30%)就地更新时。

        像往常一样,所有的基准测试都应该持保留态度。

        def join2(dict1, dict2, inplace=False):
            result = dict1 if inplace else dict1.copy()
            result.update(dict2)
            return result
        
        
        def join(*items):
            iter_items = iter(items)
            result = next(iter_items).copy()
            for item in iter_items:
                result.update(item)
            return result
        
        
        def update_or(dict1, dict2):
            return dict1.update(dict2) or dict1
        
        
        d1 = {i: str(i) for i in range(1000000)}
        d2 = {str(i): i for i in range(1000000)}
        
        %timeit join2(d1, d2)
        # 258 ms ± 1.47 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %timeit join(d1, d2)
        # 262 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %timeit dict(d1, **d2)
        # 267 ms ± 2.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %timeit {**d1, **d2}
        # 267 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        

        in-place 操作的时间有点棘手,因此需要在额外的复制操作中进行修改(第一个时间仅供参考):

        %timeit dd = d1.copy()
        # 44.9 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
        
        %timeit dd = d1.copy(); join2(dd, d2)
        # 296 ms ± 2.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %timeit dd = d1.copy(); join2(dd, d2, True)
        # 234 ms ± 1.02 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        %timeit dd = d1.copy(); update_or(dd, d2)
        # 235 ms ± 1.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        

        【讨论】:

          猜你喜欢
          • 2022-08-16
          • 2013-08-20
          • 1970-01-01
          • 2011-06-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多