dict.get()方法返回一个指针

时间:2011-08-22 21:35:05

标签: 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

如果我们现在检查default_value的值:

,问题就变得清晰了
>>> default_value
{'age': 68, 'surname': 'smith'}

很明显,my_dict.get()没有返回default_value的,而是返回指针(?)。

可以通过将代码更改为:

来解决此问题
item = my_dict.get('john', {'surname': '', 'age': 0})

但这似乎不是一个很好的方法。有什么想法,评论吗?

5 个答案:

答案 0 :(得分:21)

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

总是在Python中传递引用。

这对于strinttuple等不可变对象无关紧要,因为您无法更改它们,只能将名称指向其他对象,但它适用于listsetdict等可变对象。你需要习惯这一点,并始终牢记这一点。

编辑: 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)