我正在编写一个带有属性访问权限的简单dict
子类,我偶然发现了一些在我优化时看起来很奇怪的东西。我最初将__getattr__
和__setattr__
方法编写为self[key]
等的简单别名,但之后我认为直接调用self.__getitem__
和self.__setitem__
会更快因为他们可能会被[key]
符号在引擎盖下调用。出于好奇,我计算了两个实现,并发现了一些惊喜。
以下是两种实现方式:正如您所见,这里没有太多内容。
# brackets
class AttrDict(dict):
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, val):
self[key] = val
# methods
class AttrDict(dict):
def __getattr__(self, key):
return self.__getitem__(key)
def __setattr__(self, key, val):
self.__setitem__(key, val)
直观地说,我预计第二个实现会稍快一些,因为它可能会跳过从括号表示法转换为函数调用的步骤。但是,这并不是我的timeit
结果显示的结果。
>>> methods = '''\
... class AttrDict(dict):
... def __getattr__(self, key):
... return self.__getitem__(key)
... def __setattr__(self, key, val):
... self.__setitem__(key, val)
... o = AttrDict()
... o.att = 1
... '''
>>> brackets = '''\
... class AttrDict(dict):
... def __getattr__(self, key):
... return self[key]
... def __setattr__(self, key, val):
... self[key] = val
...
... o = AttrDict()
... o.att = 1
... '''
>>> getting = 'foo = o.att'
>>> setting = 'o.att = 1'
上面的代码都只是设置。以下是测试:
>>> for op in (getting, setting):
... print('GET' if op == getting else 'SET')
... for setup in (brackets, methods):
... s = 'Brackets:' if setup == brackets else 'Methods:'
... print(s, min(timeit.repeat(op, setup, number=1000000, repeat=20)))
...
GET
Brackets: 1.109725879526195
Methods: 1.050940903987339
SET
Brackets: 0.44571820606051915
Methods: 0.7166479863124096
>>>
正如您所看到的,使用self.__getitem__
比<{1}} 更快 ,但self[key]
显着更慢而不是self.__setitem__
{1}}。这看起来很奇怪 - 我知道函数调用开销可能很大,但如果这是问题我希望在两种情况下看到括号表示法更快,这在这里没有发生。
我进一步调查了一下;以下是self[key] = val
结果:
dis
我唯一能想到的是,与其他通话相比,>>> exec(brackets)
>>> dis.dis(AttrDict.__getattr__)
3 0 LOAD_FAST 0 (self)
3 LOAD_FAST 1 (key)
6 BINARY_SUBSCR
7 RETURN_VALUE
>>> dis.dis(AttrDict.__setattr__)
5 0 LOAD_FAST 2 (val)
3 LOAD_FAST 0 (self)
6 LOAD_FAST 1 (key)
9 STORE_SUBSCR
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
>>> exec(methods)
>>> dis.dis(AttrDict.__getattr__)
3 0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (__getitem__)
6 LOAD_FAST 1 (key)
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 RETURN_VALUE
>>> dis.dis(AttrDict.__setattr__)
5 0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (__setitem__)
6 LOAD_FAST 1 (key)
9 LOAD_FAST 2 (val)
12 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
15 POP_TOP
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
指令可能会有很大的开销,但它真的可以 吗?这是唯一在这里脱颖而出的东西......任何人都可以看到正在发生的事情POP_TOP
相对于__setitem__
而言比它的表兄弟慢得多吗?
潜在相关信息:
win32CPython 3.3.2 32位
答案 0 :(得分:4)
嗯那很有意思。如果我运行你的东西的简化版本:
setup="""
def getbrack(a, b):
return a[b]
def getitem(a, b):
return a.__getitem__(b)
def setbrack(a, b, c):
a[b] = c
def setitem(a, b, c):
return a.__setitem__(b, c)
a = {2: 3}
"""
setitem
和getitem
都比相应的setbrack
和getbrack
慢:
>>> timeit.timeit("getbrack(a, 2)", setup, number=10000000)
1.1424450874328613
>>> timeit.timeit("getitem(a, 2)", setup, number=10000000)
1.5957350730895996
>>> timeit.timeit("setbrack(a, 2, 3)", setup, number=10000000)
1.4236340522766113
>>> timeit.timeit("setitem(a, 2, 3)", setup, number=10000000)
2.402789831161499
但是,如果我完全按照您的测试运行,那么我会得到相同的结果 - GET 'Brackets'
比GET 'Methods'
慢。
这意味着它与你正在使用的类有关,而不是括号与setitem本身。
现在,如果我将测试修改为不再引用self
...
brackets = '''d = {}
class AttrDict2(dict):
def __getattr__(self, key):
return d[key]
def __setattr__(self, key, val):
d[key] = val
o = AttrDict2()
o.att = 1'''
methods = '''d = {}
class AttrDict2(dict):
def __getattr__(self, key):
return d.__getitem__(key)
def __setattr__(self, key, val):
d.__setitem__(key, val)
o = AttrDict2()
o.att = 1'''
然后我再次得到括号总是比方法更快的行为。那么它是否与self[]
子类中的dict
如何工作有关?