【问题标题】:python list sort key function choicepython列表排序键功能选择
【发布时间】:2017-04-24 03:43:28
【问题描述】:

我有一个对象列表,想根据实例函数的返回值对它们进行排序。有两种方法可以做到这一点

from operator import methodcaller

l.sort(key=lambda x:x.f())
l.sort(key=methodcaller('f'))

一种方式比另一种更好吗?还是只是个人喜好?

【问题讨论】:

    标签: python sorting key


    【解决方案1】:

    methodcaller('f')更快,因为它可以在 C 代码中进行属性查找和方法调用。

    lambda 增加了以下额外开销:

    • 调用lambda 必须跳出sort() C 循环回到Python 代码。这需要一个带有关联数据的新框架对象。

    • 查找方法属性是一个 Python 操作码,其开销比 C 中的直接等效操作码要多。

    • 接下来从 Python 框架调用方法必须再次将该框架推入 Python 调用堆栈。 C 代码也有一个堆栈,但这要轻得多。

    • 1234563 /p>

    您可以衡量差异:

    >>> from timeit import timeit
    >>> timeit('m("")', 'm = lambda s: s.lower()', number=10**7)
    1.2575681940070353
    >>> timeit('m("")', 'from operator import methodcaller; m = methodcaller("lower")', number=10**7)
    1.061251598992385
    

    因此,在空字符串上对 str.lower() 进行 700 万次调用后,methodcaller() 的速度大约提高了 16%。

    现在,如果您的所有数据都是完全相同的类型,其中object.f总是绑定到相同的方法,那么您可以只使用未绑定的方法:

    l.sort(key=SharedType.f)
    

    这样您就不必在每个实例上查找它。

    【讨论】:

      【解决方案2】:

      它们完全等价,但methodcaller 可能会快一点:

      class Fun(object):
          def __init__(self, value):
              self.value = value
      
          def f(self):
              return self.value
      
      import random
      from operator import methodcaller
      
      l = [Fun(random.random()) for _ in range(10000)]
      
      assert sorted(l, key=lambda x:x.f()) == sorted(l, key=methodcaller('f'))
      
      %timeit sorted(l, key=lambda x:x.f())     # 100 loops, best of 3: 8.4 ms per loop
      %timeit sorted(l, key=methodcaller('f'))  # 100 loops, best of 3: 7.5 ms per loop
      

      正如@PatrickHaugh 所指出的,您也可以只使用class.f 作为更快的关键函数,但正如@MartijnPieters 所说,这只有在所有对象都属于class 类型时才有效:

      %timeit sorted(l, key=Fun.f)              # 100 loops, best of 3: 6.1 ms per loop
      

      【讨论】:

      • 在这种情况下,methodcaller总是比 lambda 快。
      • @MartijnPieters 在几乎所有情况下,methodcaller 都更快。但是创建 lambda 比创建 methodcaller 更快(至少在我的计算机上),对于非常短的 2-5 个项目,lambda 更快/可比。
      • 它最多是太接近了,因为 2-5 项其他“噪音”会掩盖差异。
      • 好吧,但“太接近了”只是承认它不是“总是更快”,对吧?
      • 不,它 更快,但您无法衡量这一点,因为在多任务机器上,您无法阻止操作系统执行某些磁盘 I/O 或在指令之间进行调度;那些小小的“干扰”使我们无法准确读取如此少的数据。
      【解决方案3】:

      我认为最好的方法是,如果l 的所有元素都属于同一类型,则为

      class X:
          def __init__(self):
              ...
          def f(self):
              ...
      

      你可以的

      l.sort(key=X.f)
      

      【讨论】:

      • 这仅适用于列表中的所有对象都是相同类型的情况。如果它们是基类的子类,每个子类都被覆盖f,那么这肯定行不通。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-01
      • 1970-01-01
      • 2015-03-20
      • 1970-01-01
      • 1970-01-01
      • 2020-06-19
      • 2012-10-04
      相关资源
      最近更新 更多