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

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,setdict.你需要习惯这个,并始终牢记这一点.

编辑: 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方法在大尺寸下表现更好.

  • 这是python的一个非常重要的原则 - *all*值通过引用传递.这些参考文献的可变性是一个完全不同的问题(虽然它经常以这种方式绊倒人们). (2认同)
  • 我确定我之前已经读过这篇文章了,但是当你长时间不使用某种语言时,你往往会忘记这些事情.谢谢你说清楚. (2认同)

Jon*_*erg 9

不要使用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语法似乎具有最低的时间(不知道为什么).


Zac*_*oom 8

在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)


Eli*_*ins 5

要意识到的主要事情是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()支持传入默认值构造函数,在这种情况下会很有用,但这可能会过度设计边界情况的基类。