为什么python dict.update()不返回对象?


139

我正在尝试:

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中不可接受?


14
* TL; DRnewdict = dict(dict001, **dict002)
dreftymac

2
@dreftymac,虽然在理解上不起作用。
alancalvitti

@alancalvitti是的,这确实是一个有效的警告。
dreftymac

Answers:


219

Python大多实现了务实的命令查询分离风格:mutator返回None(带有务实的异常,例如pop;-),因此它们不可能与访问器混淆(同样,赋值不是表达式,语句-表达式分离,依此类推)。

这并不意味着没有很多方法可以在您真正想要的时候将它们合并,例如,dict(a, **award_dict)做出一个新的字典,就像您希望.update返回的字典一样。所以,如果您真的觉得很重要,那就为什么不使用THAT ?

编辑:顺便说一句,在您的特定情况下,无需a按照以下方式进行创建:

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

创建一个具有与您的语义完全相同的语义的dict a.update(award_dict)(包括在发生冲突的情况下,in中的条目award_dict会覆盖您明确提供的条目的事实;要获取其他语义,即,使显式条目“赢得”此类冲突,award_dict作为唯一的位置 arg 传递,关键字“>” 之前传递,并丧失**形式- dict(award_dict, name=name等等)。


好吧,这将在我不得不写一个字典之后创建另一个字典。我想创建一个字典,然后添加一堆其他值,然后将其提供给函数。
Paul Tarjan

@Paul,这正是您正在做的-用两条语句(比您想要的嵌套方式更具可读性)对您来说“感觉真的很麻烦”。编辑我的答案以显示如何避免a完全创建,顺便说一句,
Alex Martelli 2009年

1
原始解决方案并不可靠。如果award_dict包含已指定的键,则对于重复的关键字参数将引发SyntaxError。jamylak的解决方案dict(itertools.chain(d1.iteritems(),.. d <n> .iteritems()))不仅适用于字典具有重复键的情况,而且还可以轻松地使您稍后在字典中合并多个字典链以最终值为准。
马特

2
另外,如果award_dict中的键不是字符串,则解释器将在TypeError
2015年

3
dict(old_dict, old_key=new_value)不会为关键字抛出多个值并返回新的字典。
Charmy

35

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

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

bar = foo.reverse()

如果reverse(也将反向替换列表)也返回列表,则用户可能会认为reverse返回一个新列表,该列表已分配给bar,而永远不会注意到foo也被修改了。通过使反向返回为“无”,他们可以立即认识到bar不是反向的结果,并且看起来更接近反向的效果。


1
谢谢。为什么不撤消也可以选择不就地执行?性能?这样做reverse(foo)感觉怪怪的。
Paul Tarjan

添加选项将是不合适的:它将根据参数改变方法的性质。但是,方法实际上应该具有固定的返回类型(不幸的是,有一些违反此规则的情况)。创建还原后的副本很容易:只需制作一个副本(使用bar=foo[:]),然后还原该副本即可。
Martin v。Löwis09年

3
我认为原因是明确的。在中bar = foo.reverse(),您可能会认为foo未修改。为避免混淆,请同时使用foo.reverse()bar = reversed(foo)
罗伯托·邦瓦莱特

根据参数更改参数的性质有什么问题?
Julien


15
>>> 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}

10
-1 lambda不应该使用这样的,而是使用常规功能def而不是
jamylak

8
甚至不需要lambda,只需使用a.update(b) or a
Pycz

10

没有足够的声誉来评论顶部答案

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

之所以**kwargs只能使用解决方案,是因为要合并的字典仅具有string类型的键

>>> 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}

5

不是说它不被接受,而是不是那样dicts实现的。

如果您查看Django的ORM,它将充分利用链接。不劝阻它,您甚至可以继承dict并仅重写update以执行update和return self,如果您确实需要的话。

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

谢谢,这可以修补dict,我只是想知道为什么dict()本身不允许此功能(因为它很容易演示)。Django是否会像这样修补字典?
Paul Tarjan

2

我会尽可能接近您建议的解决方案

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

1

对于那些迟到的人,我已经安排了一些时间安排(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)

就地操作的时序有些棘手,因此需要在额外的复制操作中进行修改(第一个时序仅供参考):

%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)

0
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

0

刚在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}
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.