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

问题描述 投票:0回答:11

我有这个代码:

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

但是感觉代码比较繁琐。我宁愿能够写:

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

为什么

update
方法没有
return
原始字典,以便允许chaining,就像它在JQuery中的工作方式一样?为什么它在 python 中不被接受?


参见How do I merge two dictionaries in a single expression in Python? for workarounds.

python dictionary language-design language-features
11个回答
272
投票

Python 主要实现了一种带有实用色彩的 命令-查询分离:修改器返回

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
作为唯一的positionalarg,before关键字,并且失去
**
形式 -
dict(award_dict, name=name
等)。


45
投票

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

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

bar = foo.reverse()

如果 reverse(就地反转列表)也会返回列表,用户可能会认为 reverse 返回一个分配给 bar 的新列表,而不会注意到 foo 也被修改了。通过使 reverse return None,他们立即认识到 bar 不是反转的结果,并且会更仔细地观察 reverse 的效果是什么。


33
投票

这很简单:

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

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

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

26
投票

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

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

带有

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

18
投票
>>> 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}

6
投票

不是说它不可接受,而是

dicts
没有那样执行。

如果你看一下 Django 的 ORM,它会广泛使用链接。它并不气馁,你甚至可以从

dict
继承并且只覆盖
update
来做更新和
return self
,如果你真的想要它。

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

3
投票

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

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
投票
import itertools
dict_merge = lambda *args: dict(itertools.chain(*[d.iteritems() for d in args]))

1
投票

我自己在 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}

0
投票

通过连接项目列表进行合并:

d1 = {1: "one"}
d2 = {2: "two"}
dict(list(d1.items()) + list(d2.items()))
# {1: 'one', 2: 'two'}
© www.soinside.com 2019 - 2024. All rights reserved.