为什么对魔术方法的显式调用要慢于#34;加糖"句法?

时间:2014-04-08 14:57:54

标签: python performance magic-methods syntactic-sugar

当我遇到一组奇怪的时序结果时,我正在搞乱一个小的自定义数据对象,需要可以清洗,可比较和快速。这个对象的一些比较(和散列方法)只是委托给一个属性,所以我使用的是:

def __hash__(self):
    return self.foo.__hash__()

然而,经过测试,我发现hash(self.foo)明显更快。好奇,我测试了__eq____ne__以及其他魔术比较,但发现如果我使用含糖形式(<{1}},所有的运行速度更快,==!=等)。为什么是这样?我认为加糖形式必须在引擎盖下进行相同的函数调用,但也许情况并非如此?

Timeit结果

设置:围绕控制所有比较的实例属性的薄包装。

<

测试

我的自定义对象正在包装Python 3.3.4 (v3.3.4:7ff62415e426, Feb 10 2014, 18:13:51) [MSC v.1600 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import timeit >>> >>> sugar_setup = '''\ ... import datetime ... class Thin(object): ... def __init__(self, f): ... self._foo = f ... def __hash__(self): ... return hash(self._foo) ... def __eq__(self, other): ... return self._foo == other._foo ... def __ne__(self, other): ... return self._foo != other._foo ... def __lt__(self, other): ... return self._foo < other._foo ... def __gt__(self, other): ... return self._foo > other._foo ... ''' >>> explicit_setup = '''\ ... import datetime ... class Thin(object): ... def __init__(self, f): ... self._foo = f ... def __hash__(self): ... return self._foo.__hash__() ... def __eq__(self, other): ... return self._foo.__eq__(other._foo) ... def __ne__(self, other): ... return self._foo.__ne__(other._foo) ... def __lt__(self, other): ... return self._foo.__lt__(other._foo) ... def __gt__(self, other): ... return self._foo.__gt__(other._foo) ... ''' ,这就是我使用的内容,但它不应该有任何区别。是的,我在测试中创建了日期时间,所以显然有一些相关的开销,但是从一个测试到另一个测试的开销是不变的,所以它不应该有所作为。为简洁起见,我省略了datetime__ne__测试,但这些结果与此处显示的结果基本相同。

__gt__

结果

>>> test_hash = '''\
... for i in range(1, 1000):
...     hash(Thin(datetime.datetime.fromordinal(i)))
... '''
>>> test_eq = '''\
... for i in range(1, 1000):
...     a = Thin(datetime.datetime.fromordinal(i))
...     b = Thin(datetime.datetime.fromordinal(i+1))
...     a == a # True
...     a == b # False
... '''
>>> test_lt = '''\
... for i in range(1, 1000):
...     a = Thin(datetime.datetime.fromordinal(i))
...     b = Thin(datetime.datetime.fromordinal(i+1))
...     a < b # True
...     b < a # False
... '''
  • 哈希:
    • 明确: 1.0805227295846862
    • 加糖: 1.0135617737162192
  • 等于:
    • 明确: 2.349765956168767
    • 加糖: 2.1486044757355103
  • 小于:
    • 明确: 1.156479287717275
    • 加糖: 1.0673696685109917

1 个答案:

答案 0 :(得分:12)

有两个原因:

  • API查找仅查看类型。他们不看self.foo.__hash__,他们会查找type(self.foo).__hash__。这是一本不太容易找到的词典。

  • C槽查找比纯Python属性查找(将使用__getattribute__)更快;相反,查找方法对象(包括描述符绑定)完全在C中完成,绕过__getattribute__

因此,您必须在本地缓存type(self._foo).__hash__查找,即使这样,调用也不会像C代码那样快。如果速度非常快,请坚持使用标准库函数。

避免直接调用魔术方法的另一个原因是比较运算符 more 而不是只调用一个魔术方法;方法也反映了版本;对于x < y,如果未定义x.__lt__x.__lt__(y)返回NotImplemented单身,则还会咨询y.__gt__(x)