Python合并字典的高效优雅方案:从Python 3.5到旧版本的最佳实践
本文深入探讨Python中合并两个字典的多种方法,重点介绍Python 3.5引入的**{**x, **y}解包语法,以及适用于Python 2和早期Python 3版本的优雅替代方案。通过对比items()、构造函数及itertools.chain等方法的性能与兼容性,帮助开发者根据版本需求选择最高效且Pythonic的实现方式。
前言
字典(dict)是Python中极为强大且常用的数据类型。本文将详细解析合并两个字典的多种实现方式,分享实用技巧供读者参考。
一行代码合并两个字典
假设存在两个字典变量x和y,我们需要生成一个新的字典z,且在此过程中不修改原始的x和y。例如:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
期望生成的新结果z中,若存在相同的键(key),则y中的值将覆盖x中的值。期望的最终结果为:
>>> z
{'a': 1, 'b': 3, 'c': 4}
在PEP 448规范中,引入了新的语法支持,该语法自Python 3.5版本起可用。合并代码如下:
z = {**x, **y}
这确实是一行代码的简洁写法。鉴于目前仍有不少用户在使用Python 2,或者处于Python 3.0至3.4版本之间,对于这些用户而言,存在一种较为优雅但需要两行代码的替代方案。
z = x.copy() z.update(y)
上述方法中,y的值会覆盖x中对应的内容,因此最终结果中b的值为3。
如何在未使用Python 3.5的情况下实现一行代码合并?
如果您尚未升级至Python 3.5,或者需要编写向后兼容的代码,同时希望保持单行表达式的简洁性,最高效的方法是将逻辑封装在一个函数中:
def merge_two_dicts(x, y): """Given two dicts, merge them into a new dict as a shallow copy.""" z = x.copy() z.update(y) return z
随后,只需一行代码即可调用该函数完成合并:
z = merge_two_dicts(x, y)
您还可以定义一个函数来合并多个字典,示例如下:
def merge_dicts(*dict_args):
"""
Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
使用方式如下:
z = merge_dicts(a, b, c, d, e, f, g)
在所有上述方法中,处理相同键(key)时,均遵循“后者优先”的原则,即后面的字典值会覆盖前面的值。
以下展示一些不够优雅的实现方式:
利用items()方法
部分开发者可能会采用这种方法:
z = dict(x.items() + y.items())
这种做法实际上是在内存中创建了两个列表,进而创建第三个列表,拷贝完成后再生成新字典,最后删除前三个列表。此过程耗费性能,且在Python 3中无法直接执行,因为items()返回的是视图对象而非列表。
>>> c = dict(a.items() + b.items()) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'
您必须显式将其转换为列表,即z = dict(list(x.items()) + list(y.items())) ,这严重浪费了性能。此外,依赖items()返回的列表进行并集操作的方法在Python 3中同样会失败。更关键的是,并集操作导致重复键在取值时具有不确定性。因此,如果您对字典合并的优先级有明确要求,这种方法完全不可取。
>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
这里有一个示例,其中y本应具有更高优先级,但由于集合顺序的任意性,x的值被意外保留:
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
利用构造函数
也有人会使用以下方式:
z = dict(x, **y)
这种方式虽然比前述两步法高效,但可读性较差,不够Pythonic。特别是在键(key)非字符串类型时,Python 3中运行会失败。
>>> c = dict(a, **b) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: keyword arguments must be strings
Python创始人Guido van Rossum指出,宣告dict({}, {1:3})为非法操作,因为这属于滥用机制。尽管这种方法颇具黑客风格,但过于投机取巧,不推荐在正式代码中使用。
一些性能稍逊但相对优雅的方法
以下方法虽然性能不如最优解,但远优于items()方法,且支持优先级控制。
{k: v for d in dicts for k, v in d.items()}
在Python 2.6中可以使用以下方式:
dict((k, v) for d in dicts for k, v in d.items())
itertools.chain可以将键值对的迭代器按正确顺序链接:
import itertools z = dict(itertools.chain(x.iteritems(), y.iteritems()))
性能测试
以下测试在Ubuntu 14.04系统下的Python 2.7(系统默认Python)环境中完成:
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(),y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934
在Python 3.5环境下的测试结果:
>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287
总结
以上就是本文的全部内容,希望这些内容能对您的学习或工作有所帮助。如有任何疑问,欢迎留言交流,感谢大家的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













