在Python中,重复访问字典是否进行了优化

3
考虑以下Python代码,它遍历一个单词数组并将它们计数到字典a ['words']中。
a['words'] = {}
for word in words:
    if word not in a['words']:
        a['words'][word] = 0
    a['words'][word] += 1

问题是,Python是否已经优化了对a['words']的重复访问,以便自动保存a['words']的引用直到更改,还是我应该自己编写“优化”的代码,如下所示:
a['words'] = {}
words_dict = a['words']
for word in words:
    if word not in words_dict:
        words_dict[word] = 0
    words_dict[word] += 1

4
不是的。Python不能轻易地进行这些优化。解释器无法保证变量始终具有相同的值。 - juanpa.arrivillaga
4
好消息是你可以这样操作:a['words'] = words_dict = {} - juanpa.arrivillaga
2
我会使用第二个选项 - 只有你可以使用 collections.Counter 进一步简化事情。 - volcano
1
那是另一回事,但你可以通过使用collections.defaultdict(lambda: 0)进行优化,然后避免为每个单词进行初始化,直接使用for word in words: a['words'][word] += 1 - pawamoy
2
你需要意识到,解释器并不知道 a 是一个 dict,而且像 a['words'] 这样的表达式对于任何定义了 __getitem__ 方法的类型都可以起作用,这可能会导致一些问题。 - juanpa.arrivillaga
显示剩余5条评论
3个回答

4

很好的解决方案是collections.Counter,因为它是高性能容器:

from collections import Counter
words = ['aaa', 'bbb', 'ccc', 'ddd', 'aaa', 'bbb', 'eee']
a = {'words' : dict(Counter(words))}
a
#{'words': {'aaa': 2, 'bbb': 2, 'ccc': 1, 'ddd': 1, 'eee': 1}}

4
不需要在Counter周围包裹dict,因为Counter对象已经是一个字典。 Counter有一些额外的(可能有用的)方法,在dict中不可用。 - jpp
谢谢。计数器只是一个例子。我的问题涉及Python代码优化(因为不将a['words']分配给变量,代码更易读且美观)。 - SomethingSomething
无论如何,了解collections.Counter真是太好了。我以前真的不知道它 :) - SomethingSomething
@SomethingSomething 你可以在这里看到Counter的实现示例:[https://code.activestate.com/recipes/576611/] - zipa
@jpp 虽然 Counter 对象更有用,但我选择了问题描述中的格式。 - zipa

2
for word in words:
    if word not in words_dict:
        words_dict[word] = 0
    words_dict[word] += 1

每次循环最多执行3个字典访问。即使访问是O(1),哈希计算也不是免费的,特别是对于字符串对象。

在这种特殊情况下,collections.Counter非常适合使用。对于其他情况(例如创建列表或向其添加元素),collections.defaultdict是一个很好的替代方案,并且更快。虚构的备选示例:

c = collections.defaultdict(list)
for i,word in enumerate(words):
    c[word].append(i)

如果你想避免使用 collections 模块,也可以使用 dict.setdefault() 方法。


0

我认为这对于解释器是可能的。因此,我打算讨论如何做到这一点。

首先我们需要明确问题,如果我错了请纠正我:

给定一个查找操作和一个作用域,如果该查找操作的结果在该作用域内未被更改,则解释器应缓存该结果。

缓存很简单,真正有价值的是讨论未更改的查找操作和作用域。

据我所知,Python 中的最小作用域是函数块(实际上我不确定)。但未更改的查找操作作用域可以只是函数块的一部分。因此,在这种情况下,为了缓存此操作,应该将一个大部分时间无用的新作用域引入到 Python 的运行时中。


换个角度思考,编译期间能否检测到未改变的查找操作呢?我认为...这可能是有可能的。Pypy使用JIT技术来优化性能,特别是在一些循环块中(例如HTTP服务器)。因此,也许Pypy已经实现了这个功能?


感谢您的回答。我认为作用域并不足够,因为字典“a”可能会传递给其他可能更改“words”值的线程。我想知道Python是否检测重复的表达式并对其进行跟踪,以便在可能的情况下不必每次出现时重新计算它们。 - SomethingSomething

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接