假设我有这段代码:
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})
但这似乎不是一个很好的方法。有什么想法,评论吗?
答案 0 :(得分:21)
item = my_dict.get('john', default_value.copy())
你总是在Python中传递引用。
这对于str
,int
,tuple
等不可变对象无关紧要,因为您无法更改它们,只能将名称指向其他对象,但它适用于list
,set
和dict
等可变对象。你需要习惯这一点,并始终牢记这一点。
编辑: Zach Bloom和Jonathan Sternberg都指出了可以用来避免在每次查找时调用copy
的方法。您应该使用defaultdict
方法,例如Jonathan的第一种方法,或者:
def my_dict_get(key):
try:
item = my_dict[key]
except KeyError:
item = default_value.copy()
如果if
中的密钥几乎始终存在,则my_dict
会更快;如果dict
很大,则。您不必将其包装在函数中,但每次访问my_dict
时,您可能不希望这四行。
请参阅Jonathan对小dict
的时间安排的回答。 get
方法在我测试的所有尺寸上都表现不佳,但try
方法在大尺寸下效果更好。
答案 1 :(得分:9)
不要使用get。你可以这样做:
item = my_dict.get('john', default_value.copy())
但即使字典条目存在,也需要复制字典。相反,请考虑检查值是否存在。
item = my_dict['john'] if 'john' in my_dict else default_value.copy()
唯一的问题是它会对'john'执行两次查找,而不只是一次。如果你愿意使用一个额外的行(并且无法从字典中获得可能的值),你可以这样做:
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并检查它是否为None。
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语法似乎具有最短的时间(不知道为什么)。
答案 2 :(得分:8)
在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']
答案 3 :(得分:3)
要实现的主要事情是Python中的所有是传递引用。 C风格语言中的变量名通常是对象形状的内存区域的简写,并且分配给该变量会生成另一个对象形区域的副本...在Python中,变量只是字典中的键({ {1}}),分配行为只存储一个新的引用。 (从技术上讲,所有是一个指针,但这是一个实现细节。)
这有很多含义,主要的一个是永远不会有一个对象的隐式副本,因为你将它传递给一个函数,分配它等等。获取副本的唯一方法是明确地这样做。 python stdlib提供了一个copy
模块,其中包含一些内容,包括locals()
和copy()
函数,用于何时显式复制某些内容。此外,某些类型会公开自己的deepcopy()
函数,但这不是标准的,也不是一贯实现的。其他不可变的方法有时会提供.copy()
方法,这会产生变异的副本。
对于您的代码,传入原始实例显然不起作用,提前复制(当您可能不需要时)是浪费。所以最简单的解决方案可能就是......
.replace()
在这种情况下,如果item = my_dict.get('john')
if item is None:
item = default_dict.copy()
支持传入默认值构造函数,那将是有用的,但这可能会过度设计边界情况的基类。
答案 4 :(得分:2)
因为my_dict.get('john', default_value.copy())
会在每次调用 get时创建默认dict 的副本(即使存在并返回'john'),使用此尝试也会更快更好/除了选项:
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)