【问题标题】:Reverse AND inverse python3.x OrderedDict efficiently有效地反转和反转python3.x OrderedDict
【发布时间】:2012-08-08 06:38:52
【问题描述】:

在多次尝试创建将反转键值对并反转 OrderedDict 的单行代码之后,我有了这个:

    from collections import OrderedDict as OD

    attributes=OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')))
    print(attributes)

    reversed_attributes=OD(reversed(list(attributes.items())))
    print(reversed_attributes)

    inverted_attributes=OD([reversed(item) for item in attributes.items()])
    print(inverted_attributes)

    ''' Prints 
        OrderedDict([('brand', 'asus'), ('os', 'linux'), ('processor', 'i5'), ('memory', '4G')])
        OrderedDict([('memory', '4G'), ('processor', 'i5'), ('os', 'linux'), ('brand', 'asus')])
        OrderedDict([('asus', 'brand'), ('linux', 'os'), ('i5', 'processor'), ('4G', 'memory')])
    '''

这行得通,但是效率低吗?通过使用 reversed(list(a.items())) 这会产生很多开销,所以不是 pythonic 吗?倒置属性也一样。

关键是要避免 for 循环等,但是随着我们扩大规模,这会降低性能吗?

【问题讨论】:

  • reversed(list(a.items())) 会产生大量开销,因为您不必要地创建list,然后反向迭代它。删除list 构造函数将直接反向迭代items(无中间复制)。同样,在初始化新的OrderedDict 时,您希望使用生成器表达式(周围没有[]),而不是列表推导式,以避免创建无意义的中间lists。

标签: python dictionary python-3.x reverse performance


【解决方案1】:

有趣的是我还想出了其他方法。

>>> from collections import OrderedDict as OD
>>> attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')))

如果你想反转,你可以这样做

>>> reverse = OD(attributes.items()[::-1])

或者更pythonic的方法:

>>> reverse = OD(reversed(attributes.items()))

请注意,您不需要创建 list 项目已经是一个列表,而 reversed 是一个生成器 OrderedDict 将简单地遍历它以生成新的字典。

两者都产生相似的时间。

$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')))" "reverse = OD(attributes.items()[::-1])"
10000 loops, best of 3: 54.8 usec per loop
$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')))" "reverse = OD(reversed(attributes.items()))"
10000 loops, best of 3: 54.4 usec per loop
$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')))" "reversed_attributes=OD(reversed(list(attributes.items())))"
10000 loops, best of 3: 54.4 usec per loop

如果你想反转:

>>> invert = OD(zip(*zip(*attributes.items())[::-1]))

或更多pythonic:

>>> invert = OD(map(reversed, attributes.items()))

再次产生相似的时间。

$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')));" "invert = OD(zip(*zip(*attributes.items())[::-1]))"
10000 loops, best of 3: 57 usec per loop
$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')));" "invert = OD(map(reversed, attributes.items()))"
10000 loops, best of 3: 56.8 usec per loop
$ python -m timeit "from collections import OrderedDict as OD; attributes = OD((('brand','asus'), ('os','linux'), ('processor','i5'), ('memory','4G')));" "inverted_attributes=OD([reversed(item) for item in attributes.items()])"
10000 loops, best of 3: 55.8 usec per loop

您可以结合使用这两种方法来反转和反转。

这行得通,但是效率低吗?通过使用 reversed(list(a.items())) 这会产生很多开销,所以不是 pythonic 吗?倒置属性也一样。

有些东西可能会产生很多开销并且是 Python 的,另一方面,有些东西可能非常非常有效但不是很 Python,这个词有点被滥用了,但这只是我的看法

来自维基百科:

Python 社区中一个常见的新词是 pythonic,它可以具有与程序风格相关的广泛含义。说代码是 Pythonic 就是说它很好地使用了 Python 习语,它是自然的或表现出对语言的流利程度。同样,说一个接口或语言特性是 Pythonic 就是说它可以很好地与 Python 习语配合使用,它的使用与该语言的其余部分很好地结合。

相比之下,非 Python 代码的一个标志是它试图用 Python 编写 C++(或 Lisp、Perl 或 Java)代码——也就是说,提供粗略的转录,而不是从另一种语言的形式的惯用翻译。 pythonicity 的概念与 Python 的可读性极简主义哲学紧密相连,并避免了“有不止一种方法可以做到这一点”的方法。不可读的代码或难以理解的习语是不合 Python 的。

至于:

但是随着我们扩大规模,这会降低性能吗?

这很难说,不知道您为什么要进行此类转换,也不知道它们是否是您系统的一个组成部分,从根本上讲,它们会增加线性时间/空间开销,这可能是好的,也可能不是好的,如果条目的数量仍然很少,那么没问题,但是如果在每个请求中,假设这发生在 Web 服务器上,您正在对大型 dicts 执行此操作,这可能非常苛刻,可能需要重新设计以避免这种情况。

【讨论】:

  • samy.vilar,非常感谢您的出色回应——学到了很多东西,还有很多关于 zip、ma​​p 和 timeit 的知识。我是一个初级程序员,所以也许现在不应该担心什么是“pythonic”,什么不是“pythonic”。
  • @user1583728 没问题,希望你继续学习和享受python :)
  • @SamyVilar:问题被标记为 Python 3.x;您的计时测试似乎在 Python 2.x 上运行,这在行为上产生了巨大差异(例如 map/zip/dict.items 等。生成中间列表,而不是生成器和视图。所有的计时都将仅凭这一点就值得怀疑。
【解决方案2】:

在 Python 3.x 中,最好的方法是避免不必要的中间 lists。您在所有解决方案中都很接近,但您总是不必要地使用列表理解或 list() 构造函数。在 Python 3.x 中反转和反转的最 Pythonic 方式是:

 reversed_and_inverted = OD((v, k) for k, v in reversed(attributes.items()))

虽然这不是 Pythonic,但更快(渐近):

 reversed_and_inverted = OD(map(reversed, reversed(attributes.items())))

这使用生成器表达式从旧的 OrderedDict 初始化新的,没有中间副本(vk 的反向 tuple 的创建无关紧要;CPython 优化了像这样固定长度的tuples 以避免malloc/free 开销)。

同样,只做一个或另一个:

# Remove list() wrapper to save copy
reversed_attributes=OD(reversed(attributes.items()))

# Remove list comprehension brackets to generate into the OrderedDict directly
# Explicitly unpack and reverse key and value (repeated calls to reversed
# built-in invoke expensive LEGB and function call machinery)
inverted_attributes=OD((v, k) for k, v in attributes.items())

# Or faster, but slightly less Pythonic in some people's opinions
inverted_attributes=OD(map(reversed, attributes.items()))

从 3.5 开始的一些时间,内置的 OrderedDict 运行得足够快,以至于方法之间的百分比差异实际上有些重要。我正在使用ipython%timeit 魔法来简化:

# Make a dict large enough that the differences might actually matter

>>> od1 = OrderedDict(enumerate(string.ascii_uppercase))
>>> %timeit -r5 OrderedDict(reversed(od1.items()))
100000 loops, best of 5: 7.29 μs per loop

# Unnecessary list-ification of items view adds ~15% to run time
>>> %timeit -r5 OrderedDict(reversed(list(od1.items())))
100000 loops, best of 5: 8.35 μs per loop

>>> %timeit -r5 OrderedDict((v, k) for k, v in od1.items())
100000 loops, best of 5: 10 μs per loop

# Surprisingly, runs a little faster as a list comprehension; likely a
# coincidence of construction of OrderedDict from an input of known length
# being optimized to presize the output dict (while lists are optimized for
# reading from unknown length input iterable)
>>> %timeit -r5 OrderedDict([(v, k) for k, v in od1.items()])
100000 loops, best of 5: 9.34 μs per loop

# map produces a generator that knows how long it is if the input also knows
# how long it is, so we can beat either one in this case by avoiding repeated
# lookups of reversed and execution of Python level byte code via:
>>> %timeit -r5 OrderedDict(map(reversed, od1.items()))
100000 loops, best of 5: 8.86 μs per loop

【讨论】:

  • 注意:由于示例输入很小,时间不会告诉你太多。特别是在 Python 3.5 之前,OrderedDict 不是内置的(它是作为 Python 定义的类实现的),迭代和构造它的开销将超过大多数其他成本。
猜你喜欢
  • 2017-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多