【发布时间】:2017-04-24 03:43:28
【问题描述】:
我有一个对象列表,想根据实例函数的返回值对它们进行排序。有两种方法可以做到这一点
from operator import methodcaller
l.sort(key=lambda x:x.f())
l.sort(key=methodcaller('f'))
一种方式比另一种更好吗?还是只是个人喜好?
【问题讨论】:
我有一个对象列表,想根据实例函数的返回值对它们进行排序。有两种方法可以做到这一点
from operator import methodcaller
l.sort(key=lambda x:x.f())
l.sort(key=methodcaller('f'))
一种方式比另一种更好吗?还是只是个人喜好?
【问题讨论】:
methodcaller('f')更快,因为它可以在 C 代码中进行属性查找和方法调用。
lambda 增加了以下额外开销:
调用lambda 必须跳出sort() C 循环回到Python 代码。这需要一个带有关联数据的新框架对象。
查找方法属性是一个 Python 操作码,其开销比 C 中的直接等效操作码要多。
接下来从 Python 框架调用方法必须再次将该框架推入 Python 调用堆栈。 C 代码也有一个堆栈,但这要轻得多。
您可以衡量差异:
>>> 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)
这样您就不必在每个实例上查找它。
【讨论】:
它们完全等价,但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 快。
methodcaller 都更快。但是创建 lambda 比创建 methodcaller 更快(至少在我的计算机上),对于非常短的 2-5 个项目,lambda 更快/可比。
我认为最好的方法是,如果l 的所有元素都属于同一类型,则为
class X:
def __init__(self):
...
def f(self):
...
你可以的
l.sort(key=X.f)
【讨论】:
f,那么这肯定行不通。