关于python:dict.get()方法返回一个指针

dict.get() method returns a pointer

假设我有这个代码:

1
2
3
4
5
6
7
8
9
10
11
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

如果我们现在检查默认值,问题就变得很明显了:

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

很明显,my_dict.get()没有返回默认值,而是返回一个指针(?)对它。

通过将代码更改为:

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

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


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

您总是在python中传递一个引用。

对于不可变的对象,如strinttuple等,这并不重要。因为您不能更改它们,所以只能将名称指向不同的对象,但对于易变的对象,如listsetdict,它确实如此。你需要习惯这一点并时刻牢记在心。

编辑:ZachBloom和JonathanSternberg都指出了避免每次查找时调用copy的方法。您应该使用defaultdict方法,类似于jonathan的第一种方法,或者:

1
2
3
4
5
def my_dict_get(key):
    try:
        item = my_dict[key]
    except KeyError:
        item = default_value.copy()

如果dict很大,当密钥几乎总是存在于my_dict中时,这将比if更快。您不必将它包装在函数中,但您可能不希望每次访问my_dict时都使用这四行。

看乔纳森的回答,他用一个小的dict计时。在我测试的所有尺寸中,get方法表现不佳,但在大尺寸中,try方法表现更好。


不要使用GET。你可以这样做:

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

但这要求即使字典条目存在,也要复制字典。相反,考虑检查值是否存在。

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

唯一的问题是它将对"john"执行两次查找,而不是一次。如果您愿意使用一个额外的行(并且没有一个是您从字典中得到的可能值),您可以这样做:

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

编辑:我想我会和timeit做一些速度比较。默认值和我的口述是全局的。如果钥匙在那里,如果有失手的话,我会为两个人都做。

使用例外:

1
2
3
4
5
6
7
8
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。

1
2
3
4
5
6
7
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语法检查其存在性

1
2
3
4
5
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

天真地抄写字典。

1
2
3
4
5
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语法的时间似乎最短(不知道为什么)。


在python中,dict都是对象(因此它们总是作为引用传递)和可变对象(这意味着它们可以在不重新创建的情况下进行更改)。

每次使用词典时,您都可以复制词典:

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

还可以使用defaultdict集合:

1
2
3
4
5
6
7
8
from collections import defaultdict

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

my_dict = defaultdict(factory)

my_dict['john']

要认识到的主要事情是,Python中的所有内容都是通过引用传递的。C样式语言中的变量名通常是内存中对象形状区域的简写,将该变量赋值可复制另一个对象形状区域…在Python中,变量只是字典(locals()中的键),而赋值操作只存储新的引用。(从技术上讲,一切都是一个指针,但这是一个实现细节)。

这有许多含义,主要的含义是永远不会有一个对象的隐式副本,因为您将它传递给了一个函数,分配了它,等等。获得副本的唯一方法是显式地这样做。python stdlib提供了一个copy模块,该模块包含一些内容,包括copy()deepcopy()函数,当您想要显式地复制某个内容时。此外,有些类型公开了自己的.copy()功能,但这不是标准,也不是一贯实现的。其他不可变的方法有时会提供一个.replace()方法,这会产生一个变异的拷贝。

在代码的情况下,传递原始实例显然不起作用,并且提前(可能不需要时)制作一个副本是浪费的。所以最简单的解决方案可能是…

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

在这种情况下,如果.get()支持传入默认值构造函数函数,这将非常有用,但这可能是在为边界情况设计一个基类。


因为每次调用get时,my_dict.get('john', default_value.copy())都会创建一个默认dict的副本(即使当出现并返回'john'时),所以使用此try/except选项更快也非常好:

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

或者,您也可以使用defaultdict

1
2
3
4
5
6
import collections

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

my_dict = collections.defaultdict(default_factory)