Arm*_*das 15 python dictionary pass-by-reference
假设我有这段代码:
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
Run Code Online (Sandbox Code Playgroud)
如果我们现在检查default_value的值,问题就变得清晰了:
>>> default_value
{'age': 68, 'surname': 'smith'}
Run Code Online (Sandbox Code Playgroud)
很明显,它my_dict.get()
没有返回default_value 的值,而是返回指针(?).
可以通过将代码更改为:
item = my_dict.get('john', {'surname': '', 'age': 0})
Run Code Online (Sandbox Code Playgroud)
但这似乎不是一个很好的方法.有什么想法,评论?
agf*_*agf 23
item = my_dict.get('john', default_value.copy())
Run Code Online (Sandbox Code Playgroud)
你总是在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()
Run Code Online (Sandbox Code Playgroud)
这将是比快if
当钥匙几乎总是已经存在my_dict
,如果dict
是大的.您不必将其包装在函数中,但每次访问时可能不需要这四行my_dict
.
看看Jonathan对小时间的回答dict
.该get
方法在我测试的所有尺寸上都表现不佳,但该try
方法在大尺寸下表现更好.
不要使用get.你可以这样做:
item = my_dict.get('john', default_value.copy())
Run Code Online (Sandbox Code Playgroud)
但这需要复制字典,即使存在字典条目.相反,请考虑检查值是否存在.
item = my_dict['john'] if 'john' in my_dict else default_value.copy()
Run Code Online (Sandbox Code Playgroud)
唯一的问题是它将对'john'执行两次查找,而不只是一次.如果您愿意使用额外的行(并且None不是可以从字典中获得的值),您可以:
item = my_dict.get('john')
if item is None:
item = default_value.copy()
Run Code Online (Sandbox Code Playgroud)
编辑:我以为我会和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
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
使用特殊的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
Run Code Online (Sandbox Code Playgroud)
天真地复制字典.
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
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,除了使用异常之外的所有内容都非常相似.由于某种原因,特殊的if/else语法似乎具有最低的时间(不知道为什么).
在Python中,dicts既是对象(因此它们总是作为引用传递)又是可变的(意味着它们可以在不重新创建的情况下进行更改).
您可以在每次使用时复制字典:
my_dict.get('john', default_value.copy())
Run Code Online (Sandbox Code Playgroud)
您还可以使用defaultdict集合:
from collections import defaultdict
def factory():
return {'surname': '', 'age': 0}
my_dict = defaultdict(factory)
my_dict['john']
Run Code Online (Sandbox Code Playgroud)
要意识到的主要事情是Python中的一切都是按引用传递的。C 风格语言中的变量名通常是对象形状的内存区域的简写,分配给该变量会复制另一个对象形状的区域......在 Python 中,变量只是字典中的键 ( locals()
) ,并且赋值操作只是存储一个新的引用。(从技术上讲,一切都是一个指针,但这是一个实现细节)。
这有很多含义,主要是永远不会有一个对象的隐式副本,因为你将它传递给一个函数,分配它等。获得副本的唯一方法是显式这样做。python stdlib 提供了一个copy
包含一些东西的模块,包括一个copy()
和deepcopy()
函数,用于当你想显式地制作某些东西的副本时。此外,某些类型公开了.copy()
自己的函数,但这不是标准,也不是一致实现的。其他不可变的往往会提供一种.replace()
方法,该方法会产生变异的副本。
就您的代码而言,传入原始实例显然不起作用,提前制作副本(当您可能不需要时)是一种浪费。所以最简单的解决方案可能是......
item = my_dict.get('john')
if item is None:
item = default_dict.copy()
Run Code Online (Sandbox Code Playgroud)
如果.get()
支持传入默认值构造函数,在这种情况下会很有用,但这可能会过度设计边界情况的基类。