簡體   English   中英

為什么兩級字典的值都指向Python 2.7中的同一個對象?

[英]Why are values of a two-level dictionary all pointing to the same object in Python 2.7?

我試圖定義一個函數來創建一個雙層字典,所以它應該生成格式

dict = {tier1:{tier2:value}}.

代碼是:

def two_tier_dict_init(tier1,tier2,value):
    dict_name = {}
    for t1 in tier1:
        dict_name[t1] = {}
        for t2 in tier2:
            dict_name[t1][t2] = value
    return dict_name

所以下面的例子

tier1 = ["foo","bar"]
tier2 = ["x","y"]
value = []
foobar_dict = two_tier_dict_init(tier1,tier2,value)

從它的表面產生我想要的東西:

foobar_dict =  {'foo':{'x': [],'y':[]},
                'bar':{'x': [],'y':[]}}                   }

但是,當附加任何值時

foobar_dict["foo"]["x"].append("thing")

所有值都被附加,因此結果為:

foobar_dict =  {'foo':{'x': ["thing"],'y':["thing"]},
                'bar':{'x': ["thing"],'y':["thing"]}}

起初我假設由於我的定義構建字典的方式,所有值都指向內存中的相同空間,但我無法弄清楚為什么會出現這種情況。 然后我發現如果我將值從空列表更改為整數,當我執行以下操作時,

foobar_dict["foo"]["x"] +=1

僅更改所需的值。

因此,我必須得出結論,這與list.append方法有關,但我無法弄清楚。 解釋是什么?

注意我需要這個函數來構建大型字典詞典,其中每個層都有數百個元素。 我也使用相同的方法來構建一個出現相同問題的三層版本。

您只傳入了一個列表對象,而第二層字典僅存儲了對該對象的引用。

如果需要存儲不同的列表,則需要為每個條目創建一個新列表。 您可以為此使用工廠函數:

def two_tier_dict_init(tier1, tier2, value_factory):
    dict_name = {}
    for t1 in tier1:
        dict_name[t1] = {}
        for t2 in tier2:
            dict_name[t1][t2] = value_factory()
    return dict_name

然后使用:

two_tier_dict_init(tier1, tier2, list)

讓它創建空列表。 您可以在此處使用任何可調用的值工廠,包括lambda如果要存儲字符串或整數之類的不可變對象):

two_tier_dict_init(tier1, tier2, lambda: "I am shared but immutable")

您可以使用dict理解來簡化您的功能:

def two_tier_dict_init(tier1, tier2, value_factory):
    return {t1: {t2: value_factory() for t2 in tier2} for t1 in tier1}

之所以發生這種情況,是因為您使用作為值傳遞的相同列表填充所有第二層dicts,並且所有條目都指向相同的列表對象。

一種解決方案是復制每個屬性的列表:

dict_name [t1] [t2] =值[:]

這僅在您確定值始終為列表時才有效。

可以復制任何對象(包括嵌套列表和字典)的另一個更通用的解決方案是深度復制:

dict_name [t1] [t2] = copy.deepcopy(value)

如果用不可變對象(如數字或字符串)填充dicts,內部所有條目也會引用同一個對象,但不會發生不良影響,因為數字和字符串是不可變的。

所有值都引用相同的列表對象。 當您在該列表對象上調用append()時,所有字典值似乎都在同時更改。

創建列表更改的副本

        dict_name[t1][t2] = value

        dict_name[t1][t2] = value[:]

或者

        dict_name[t1][t2] = copy.deepcopy(value)

前者將制作淺層(即一層)副本,后者將進行深層復制。

之所以對int起作用,是因為它們是不可變的,並且增強的賦值( +=和朋友)會像普通的賦值語句一樣重新命名(可能會返回到同一對象)。 當你這樣做:

foobar_dict["foo"]["x"] +=1

你最終用另一個替換舊的int對象。 int不具備就地更改值的功能,因此加法生成(或可能找到,因為CPython實習了某些int)具有新值的另一個int。

因此,即使foobar_dict["foo"]["x"]foobar_dict["foo"]["y"]以相同的int開頭(並且他們確實如此),添加其中一個使它們現在包含不同的 int。

如果您使用更簡單的變量進行嘗試,您可以看到這種差異:

>>> a = b = 1
>>> a is b
True
>>> a += 1
>>> a 
2
>>> b
1

另一方面, list是可變的, 並且調用append不會進行任何重新綁定。 因此,正如您所懷疑的那樣,如果foobar_dict["foo"]["x"]foobar_dict["foo"]["y"]是相同的列表(並且它們是 - 請用is檢查),並附加到它,他們仍然是同一個名單。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM