2011-08-22 76 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)