2011-08-22 62 views
10

比方說,我有這樣的代碼:dict.get()方法返回一個指針

my_dict = {} 
default_value = {'surname': '', 'age': 0} 

# get info about john, or a default dict 
item = my_dict.get('john', default_value) 

# edit the data 
item[surname] = 'smith' 
item[age] = 68 

my_dict['john'] = item 

問題變得清晰,如果我們現在檢查DEFAULT_VALUE的價值:

>>> default_value 
{'age': 68, 'surname': 'smith'} 

很明顯,my_dict.get()未返回default_value的,而是返回指向其的指針(?)。

的問題可以通過代碼改變所工作圍繞:

item = my_dict.get('john', {'surname': '', 'age': 0}) 

,但似乎並沒有成爲一個很好的辦法做到這一點。任何想法,意見?

回答

16
item = my_dict.get('john', default_value.copy()) 

總是通過Python中的參考。

這不要緊,像strinttuple,等等。因爲你無法改變他們,只是在不同的目標點名稱不變的對象,但它確實像listset可變對象,並dict。你需要習慣這一點,並始終牢記在心。

編輯:扎克布魯姆和喬納森斯騰伯格都指出了方法,你可以用來避免在每次查找時調用copy。你應該使用的defaultdict方法,像喬納森的第一種方法,或者:

def my_dict_get(key): 
    try: 
     item = my_dict[key] 
    except KeyError: 
     item = default_value.copy() 

這將是快於if當鑰匙幾乎總是已經存在my_dict如果dict。您不必將其包裝在一個功能中,但每次訪問my_dict時可能不需要這四行。

查看喬納森的回答時間爲dictget方法在我測試的所有尺寸上表現不佳,但try方法在大尺寸下效果更好。

+1

這是蟒蛇的一個非常重要的原則 - *所有*值傳遞 引用。這些引用的可變性是一個完全不同的問題(儘管它通常以這種方式絆倒人)。 –

+1

我確定以前我已經閱讀過關於它的內容,但是如果長時間不使用某種語言,您往往會忘記一些事情。感謝您的澄清。 – Armandas

+0

爲什麼你的答案與問題中提供的答案有任何不同。問題似乎更多的是尋找返回字典的新實例的優雅方式,但只是在需要時才創建它。 – Dunes

7

在Python中,dicts都是對象(所以它們總是作爲引用傳遞)和可變的(意味着它們可以在不被重新創建的情況下進行更改)。

您可以複製你的字典裏每次使用它的時候:

my_dict.get('john', default_value.copy()) 

您也可以使用defaultdict集合:

from collections import defaultdict 

def factory(): 
    return {'surname': '', 'age': 0} 

my_dict = defaultdict(factory) 

my_dict['john'] 
8

不要使用得到。你可以這樣做:

item = my_dict.get('john', default_value.copy()) 

但是這需要即使辭典條目存在要複製字典。相反,請考慮只檢查值是否存在。

item = my_dict['john'] if 'john' in my_dict else default_value.copy() 

唯一的問題是,它將執行兩次查找'約翰',而不是一個。如果你願意使用一個額外的行(和無不是可能的值,你可以從字典中獲得),你可以這樣做:

item = my_dict.get('john') 
if item is None: 
    item = default_value.copy() 

編輯:我想我會做一些timeit速度比較。 default_value和my_dict是全局變量。如果鑰匙在那裏,以及是否有遺漏,我都會爲他們分別做。

使用異常:

def my_dict_get(): 
    try: 
     item = my_dict['key'] 
    except KeyError: 
     item = default_value.copy() 

# key present: 0.4179 
# key absent: 3.3799 

使用GET和檢查,如果是無。

def my_dict_get(): 
    item = my_dict.get('key') 
    if item is None: 
     item = default_value.copy() 

# key present: 0.57189 
# key absent: 0.96691 

檢查其與特殊所有腦幹的if/else語法

def my_dict_get(): 
    item = my_dict['key'] if 'key' in my_dict else default_value.copy() 

# key present: 0.39721 
# key absent: 0.43474 

天真複製字典。

def my_dict_get(): 
    item = my_dict.get('key', default_value.copy()) 

# key present: 0.52303 (this may be lower than it should be as the dictionary I used was one element) 
# key absent: 0.66045 

大多數情況下,除了使用異常的東西以外,其他東西都非常相似。特殊的if/else語法由於某種原因似乎有最短的時間(不知道爲什麼)。

+0

這是一個好點,我會在我的答案中加上一個註釋。如何在my_dict'中使用''john'而不是'my_dict.has_key('john')'和'my_dict.get('john')'而不是'my_dict.get('john',None)''? – agf

+0

我喜歡比has_key更好用。我忘記了存在。我不知道my_dict.get('john')默認返回null(我認爲它是一個IndexError)。 –

+0

或者使用: 從收藏導入defaultdict mydict = defaultdict(default_value.copy) 然後當你做mydict [密鑰 - 這就是 - 不這裏],你傳遞給構造函數將被調用。 –

2

要認識到的主要問題是Python中的所有東西都是傳遞引用。 C風格語言中的變量名通常是對象形狀的內存區域的縮寫,賦值給該變量會生成另一個對象形區域的副本......在Python中,變量只是字典中的鍵(locals() ),分配的行爲只是存儲一個新的參考。 (從技術上講,一切是一個指針,但這是一個實現細節)。

這有很多含義,主要的存在將永遠不會有一個對象的隱式副本,因爲您將它傳遞給一個函數,分配給它等。獲取副本的唯一方法是明確地執行所以。 Python stdlib提供了一個copy模塊,其中包含一些東西,其中包括copy()deepcopy()函數,用於您明確製作某些東西的副本。另外,某些類型揭示了它們自己的功能,但這不是一個標準,或者一貫實施。其他不可改變的方面往往會提供一種方法,這種方法會產生突變的拷貝。


在你的代碼的情況下,通過在原來的情況下顯然是行不通的,並製作一份拷貝的時間提前(當你可能不需要)是一種浪費。所以,最簡單的解決方案可能是...

item = my_dict.get('john') 
if item is None: 
    item = default_dict.copy() 

這將是有用的在這種情況下,如果.get()支持傳遞一個默認值的構造函數,但是這可能超過了實際工程基類的邊界情況。

1

因爲my_dict.get('john', default_value.copy())會造成每次得到的是所謂的(甚至當「約翰」存在並返回)默認字典的副本,它是速度更快,很OK使用這種嘗試/ except選項:

try: 
    return my_dict['john'] 
except KeyError: 
    return {'surname': '', 'age': 0} 

或者,你也可以使用一個defaultdict

import collections 

def default_factory(): 
    return {'surname': '', 'age': 0} 

my_dict = collections.defaultdict(default_factory)