1. 程式人生 > 其它 >這樣合併Python字典,可以讓程式的執行效率提高4倍

這樣合併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倍。