這樣合併Python字典,可以讓程式的執行效率提高4倍
摘要:在Python中,合併字典有多種方式,通過內建函式、運算子、自定義函式等,都可以完成合並字典的功能,但這些方式,哪些效率低,哪些效率高呢?本文將對這些合併字典的方式進行逐個深度詳解,最後會比較這些方式,看看到底誰是效率之王!
現在提出一個問題:如何用一行程式碼合併兩個Python字典,並返回合併結果。可能很多同學最先想到的是下面的程式碼:
x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}
x.update(y)
# {'a': 1, 'b': 10, 'c': 11}
print(x)
這段程式碼沒有任何問題,通過update方法可以將x和y合併,但問題是update方法修改了x的值,將合併的結果賦給了x,而不是新生成一個變數。這樣並沒有通過一行程式碼合併兩個字典,而且還修改了變數x的值。
當然,我們可以做一些改變,例如,先定義一個空的字典z,然後分別將x和y與z合併,程式碼如下:
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}
z = {}
z.update(x)
z.update(y)
# {'a': 1, 'b': 10, 'c': 11}
print(x)
這段程式碼完美地將x和y合併,而且並未改變x和y的值,不過程式碼量比較多,仍然未使用一行程式碼合併兩個字典。
1. Python 3.9的解決方案
如果讀者使用Python 3.9,那簡直太幸運了,因為Python 3.9可以直接通過“|”運算符合並兩個字典,簡直乾淨利索,程式碼如下:
z = x | y
print(z)
不過遺憾的是,“|”運算子只能合併字典,不能合併列表。
2. Python 3.5及以上版本的解決方案
如果讀者使用的不是Python 3.9,但卻是Python3.5或以上版本,如Python3.7、Python3.8等,可以採用雙星(**)運算符合並兩個字典,程式碼如下:
x = {'a': 1, 'b': 2}
y = { 'b': 10, 'c': 11}
z = {**x, **y}
print(z)
這裡的“**”表示將字典拆成key-value對形式傳入,那麼{**x, **y}就表示先將x和y拆成獨立的key-value對,然後再將這些key-value對覆蓋新字典。
除了**
外,還有可以處理列表的*
,例如,下面的程式碼可以合併兩個列表。合併原理與**
類似。
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
xx = [1,2,3]
yy = [4,5,6]
# 合併列表
zz = {*xx,*yy}
print(zz)
3. Python 3.4或一下版本的解決方案
如果讀者使用的是Python3.4或更低的Python版本,如Python2.7,那麼如果想用一行程式碼解決問題,就要自己編寫函數了。基本的解決思路是先將x整個複製一份,變成z,然後再使用update函式合併z與y。實現程式碼如下:
def merge_two_dicts(x, y):
z = x.copy() # 複製x到z
z.update(y) # 將z與y合併,z已經改變
return z
現在就可以使用下面的一行程式碼合併x和y了,而且x和y都沒有改變。
print(merge_two_dicts(x,y))
如果還想合併不定數量的字典,如3個字典、5個字典,可以使用下面的函式:
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
def merge_dicts(*dict_args):
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
new_dict = {'x':20,'y':30}
# {'a': 1, 'b': 10, 'c': 11, 'x': 20, 'y': 30}
print(merge_dicts(x,y,new))
4. 深度合併
前面給出的案例只能淺層合併,如果想深度合併,可以使用遞迴的方式。例如,要合併下面兩個字典:
xx = {'a':{1:{}}, 'b': {2:{}}}
yy = {'b':{10:{}}, 'c': {11:{}}}
這兩個字典的每一個key,也是一個字典,現在需要合併每一個key表示的字典,合併的結果希望是如下形式:
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}
要完成這個功能,需要使用deepcopy函式,程式碼如下:
from copy import deepcopy
def dict_of_dicts_merge(x, y):
z = {}
# 如果兩個要合併的字典結構不一致,無法合併,返回None
if not hasattr(x,'keys') or not hasattr(y,'keys'):
return
# 交集:b
overlapping_keys = x.keys() & y.keys()
for key in overlapping_keys:
z[key] = dict_of_dicts_merge(x[key], y[key])
for key in x.keys() - overlapping_keys:
z[key] = deepcopy(x[key])
for key in y.keys() - overlapping_keys:
z[key] = deepcopy(y[key])
return z
現在可以使用下面的程式碼合併xx和yy:
xx = {'a':{1:{}}, 'b': {2:{}}}
yy = {'b':{10:{}}, 'c': {11:{}}}
print(dict_of_dicts_merge(xx, yy))
如果要合併的兩個字典的key對應的value,有一個不是字典,那麼無法合併,在這種情況下,value就為None,例如,要合併下面兩個字典:
xx = {'a':{1:{}}, 'b': {2:{}}}
yy = {'b':20, 'c': {11:{}}}
print(dict_of_dicts_merge(xx, yy))
由於yy的b是20,而xx的b是一個字典,所以b無法合併,因此執行這段程式碼,會輸出如下結果:
{'b': None, 'a': {1: {}}, 'c': {11: {}}}
5. 其他合併字典的方式
除了前面介紹的幾種合併字典的方式,還可以用下面的2種合併方式:
(1)for in 表示式
在Python中有一種語法,可以利用for in表示式生成列表或字典,因此,可以利用這個功能,將要合併的兩個字典中的key和value單獨提取出來,然後逐個寫入新的字典,實現程式碼如下:
'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}
z = {k: v for d in (x, y) for k, v in d.items()}
# {'a': 1, 'b': 10, 'c': 11}
print(z)
要理解for in表示式是如何工作的,可以先提取下面的程式碼:
for d in(x,y) :
print(d)
這段程式碼其實是將x和y作為元組的元素,輸出的結果如下:
{'a': 1, 'b': 2}
{'b': 10, 'c': 11}
然後for k, v in d.items()就是對這個元組進行迭代,而k和v就是提取元組中每一個元素的key和value,然後再將k和v作為新字典的key和value插入。
(2)使用chain物件
通過chain物件,可以將多個字典轉換為像連結串列一樣的結果(這個連結串列是可迭代的),然後再使用dict物件將其轉換為字典,實現程式碼如下:
from itertools import chain
x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}
print(dict(chain(x.items(), y.items())))
6. 效能大比拼
到現在為止,已經講了很多種合併字典的方式,那麼這些方式哪些效率高,哪些效率低呢?下面就來做一個實驗。
from timeit import repeat
from itertools import chain
x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')
print('x | y'.ljust(50,' '), min(repeat(lambda: x | y)))
print('{**x, **y}'.ljust(50,' '),min(repeat(lambda: {**x, **y})))
print('merge_two_dicts(x, y)'.ljust(50,' '), min(repeat(lambda: merge_two_dicts(x, y))))
print('{k: v for d in (x, y) for k, v in d.items()}'.ljust(50,' '), min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()})))
print('dict(chain(x.items(), y.items()))'.ljust(50,' '), min(repeat(lambda: dict(chain(x.items(), y.items())))))
print('dict(item for d in (x, y) for item in d.items())'.ljust(50,' '), min(repeat(lambda: dict(item for d in (x, y) for item in d.items()))))
其中repeat函式可以用來方便地測試一小段程式碼的執行時間,在預設情況下,repeat函式會將由lambda引數指定的表示式執行時間放大100萬倍,也就是執行100萬次這個表示式,然後統計時間總和,並且這一過程進行5遍,也就是說,repeat函式會執行lambda引數指定的表示式500萬次,最後得到5組時間值(單位是秒),然後用min函式挑出最小的值。
執行這段程式碼,會輸出如圖1所示的結果:
很明顯,x | y的效率最高,兩個字典合併100萬次,只需要不到0.5秒,而最後一種方式最慢,需要2秒,所以最快的合併字典的方式比最慢的方式整整快了4倍。