在Python 2中,可以将任意可调用对象转换为类的方法。重要的是,如果可调用对象是用C实现的CPython内置函数,则可以使用它来创建用户定义类的方法,这些方法本身就是C层,在调用时不调用字节代码。
如果您依靠GIL提供“无锁”同步,这有时会很有用;由于GIL只能在操作码之间交换出来,因此,如果可以将代码特定部分中的所有步骤都推送到C,则可以使其自动执行。
在Python 2中,您可以执行以下操作:
import types
from operator import attrgetter
class Foo(object):
... This class maintains a member named length storing the length...
def __len__(self):
return self.length # We don't want this, because we're trying to push all work to C
# Instead, we explicitly make an unbound method that uses attrgetter to achieve
# the same result as above __len__, but without no byte code invoked to satisfy it
Foo.__len__ = types.MethodType(attrgetter('length'), None, Foo)
[在Python 3中,不再有未绑定的方法类型,types.MethodType
只接受两个参数并仅创建绑定的方法(这对于Python特殊方法,例如__len__
,__hash__
等无用,因为特殊方法通常直接在类型上查询,而不是在实例上查询。
我缺少在Py3中完成此任务的方法吗?
我看过的东西:
functools.partialmethod
(似乎没有C实现,因此未满足要求,并且在Python实现和比我需要的更通用的目的之间),它是[[slow,大约需要5个[[us在我的测试中,与直接Python定义或Py2中的attrgetter
相比,约200-300 ns,开销大约增加了20倍]attrgetter
等遵循非数据描述符协议(不可能的AFAICT,不能在__get__
等中进行猴子补丁)[试图找到将attrgetter
赋予其__get__
的方法,但是当然,__get__
需要以某种方式委托给C层,现在我们回到了开始的位置attrgetter
用例)首先使用__slots__
使成员成为描述符,然后尝试以某种方式将数据的所得描述符转换为跳过绑定和获取最终结果的步骤实际值,使其可以调用,因此推迟了实际值检索]class
,Python内置函数(例如hex
,len
等)或未定义为Python层)。重要的是,它需要附加到类上,而不是附加到每个实例上(两者都是为了减少每实例的开销,并且必须在特殊的方法下正常工作,这些方法在大多数情况下会绕过实例查找)。import ctypes
from operator import attrgetter
make_instance_method = ctypes.pythonapi.PyInstanceMethod_New
make_instance_method.argtypes = (ctypes.py_object,)
make_instance_method.restype = ctypes.py_object
class Foo:
# ... This class maintains a member named length storing the length...
# Defines a __len__ method that, at the C level, fetches self.length
__len__ = make_instance_method(attrgetter('length'))
这是对Python 2版本的一种改进,因为不需要定义类来为其创建未绑定方法,因此您可以通过简单的赋值在类主体中对其进行定义(其中Python 2版本必须在Foo
中明确引用两次Foo.__len__ = types.MethodType(attrgetter('length'), None, Foo)
,并且仅在class Foo
定义完成之后才可以引用。)>
另一方面,实际上在CPython 3.7上并没有提供性能上的好处
AFAICT,至少不是在这里替换def __len__(self): return self.length
的简单情况下;实际上,对于在__len__
实例上通过len(instance)
访问的Foo
,ipython
%%timeit
微基准测试表明,当通过len(instance)
定义__len__
时,__len__ = make_instance_method(attrgetter('length'))
慢10%。这可能是attrgetter
本身的人工产物,其开销略高,原因是CPython尚未将其移至“ FastCall”协议(在3.8中被临时公共使用,以供第三方临时使用,在3.8中称为“ Vectorcall”),而用户定义的函数已经在3.7中受益,并且每次必须动态选择是执行点还是不点属性查找以及一次或多次属性查找(Vectorcall可以通过选择适当的__call__
实现来避免到在构造时执行的获取)增加了普通方法避免的开销。对于更复杂的情况(例如,如果要检索的属性是诸如self.contained.length
之类的嵌套属性),它应该会获胜,因为attrgetter
的开销在很大程度上是固定的,而在Python中嵌套属性查找意味着更多的字节代码,但是正确现在,它不再经常有用。如果他们能够为Vectorcall优化
operator.attrgetter
,我将重新确定基准并更新此答案。