是否有一种内置的方法来使用CPython内置函数来使任意可调用行为作为未绑定的类方法?

时间:2016-10-19 01:26:21

标签: python python-3.x methods built-in python-descriptors

在Python 2中,可以将任意callables转换为类的方法。重要的是,如果callable是一个用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只接受两个参数并仅创建绑定方法(这对于__len__,{{1}等Python特殊方法无用。因为特殊方法通常直接在类型上查找,而不是实例)。

在Py3中有没有某种方法可以解决这个问题?

我看过的事情:

  1. __hash__(似乎没有C实现,因此它不符合要求,并且在Python实现之间,并且比我需要的更通用的目的,它,在我的测试中大约有5个我们,而对于直接Python定义大约需要200-300 ns ,或者在Py2中大约需要functools.partialmethod,大约增加20倍的开销)
  2. 尝试使attrgetter等遵循非数据描述符协议(不可能是AFAICT,不能在attrgetter中进行猴子补丁等)
  3. 试图找到一种方法来将__get__子类化为attrgetter,但当然,__get__需要以某种方式委托给C层,现在我们就可以了。回到我们开始的地方
  4. (特定于__get__用例)首先使用attrgetter使成员成为描述符,然后尝试以某种方式将数据的结果描述符转换为跳过最后一步的内容绑定和获取真实价值的东西,使其可调用,以便推迟实际价值检索
  5. 我不能发誓,我并没有因为任何这些选择而错过任何东西。有人有任何解决方案?允许完全hackery;我知道我在这里做了病态的事情。理想情况下,它会灵活(让你的行为像__slots__中的非绑定方法,Python内置函数,如classhex等,或任何其他可调用对象未在Python层定义)。重要的是,它需要附加到类,而不是每个实例(都是为了减少每个实例的开销,并且在dunder特殊方法中正常工作,在大多数情况下绕过实例查找)。

1 个答案:

答案 0 :(得分:0)

最近为此找到了一个(可能只有CPython)解决方案。有点丑陋,是直接调用CPython API的ctypes黑客,但它确实有效,并获得了所需的性能:

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)访问的Fooipython %%timeit的微基准测试表明,{ {1}}是通过len(instance),定义的。这可能是__len__本身的人工产物,其开销略高,原因是CPython尚未将其移至“ FastCall”协议(在3.8中被临时公共使用,以供第三方临时使用,在3.8中称为“ Vectorcall”) ,而用户定义的函数已在3.7中受益,并且每次都必须动态选择是执行点还是不点属性查找以及单次或多次属性查找(Vectorcall可以通过选择{{ }}适合在构造时执行的实现)会增加普通方法避免的开销。对于更复杂的情况(例如,如果要检索的属性是诸如__len__ = make_instance_method(attrgetter('length'))之类的嵌套属性),它应该会获胜,因为attrgetter的开销在很大程度上是固定的,而在Python中嵌套属性查找意味着更多字节代码,但是现在,它并不是经常使用。

如果他们曾经为Vectorcall优化__call__,我将重新确定基准并更新此答案。