是否有一种内置方法可以使用 CPython 内置函数使任意可调用对象表现为未绑定的 class 方法?

Is there a built-in way to use CPython built-ins to make an arbitrary callable behave as an unbound class method?

在 Python 2 中,可以将任意可调用对象转换为 class 的方法。重要的是,如果可调用对象是在 C 中实现的 CPython 内置函数,您可以使用它来创建用户定义的 classes 方法,这些方法本身就是 C 层,调用时不调用字节代码。

如果您依赖 GIL 提供 "lock-free" 同步,这偶尔会很有用;由于 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)

在Python3中,不再有未绑定的方法类型,types.MethodType只接受两个参数,只创建绑定方法(这对Python特殊方法没有用像 __len____hash__ 等,因为通常直接在类型而不是实例上查找特殊方法。

有什么方法可以在 Py3 中完成我所缺少的吗?

我看过的东西:

  1. functools.partialmethod(似乎没有 C 实现,所以它不符合要求,并且在 Python 实现和比我需要的更通用的目的之间,它是 ,在我的测试中大约需要 5 us,而直接 Python 定义需要 ~200-300 ns或 Py2 中的 attrgetter,开销大约增加 20 倍)
  2. 试图让 attrgetter 或类似的遵循非数据描述符协议(不可能的 AFAICT,不能在 __get__ 或类似的中进行猴子补丁)
  3. 想办法subclass attrgetter给它一个__get__,当然,这个__get__需要以某种方式委托给C层,现在我们回到了起点
  4. (特定于 attrgetter 用例)首先使用 __slots__ 使成员成为描述符,然后尝试以某种方式将数据的结果描述符转换为跳过将实际值绑定并获取到使其可调用的东西的最后一步,因此延迟了实际值检索

我不能发誓我没有错过任何这些选项。任何人有任何解决方案?完全黑客是允许的;我知道我在这里做病态的事情。理想情况下它是灵活的(让你从 class、Python 内置函数(如 hexlen 等)中创建一些行为类似于未绑定方法的东西.,或任何其他未在 Python 层定义的可调用对象)。重要的是,它需要附加到 class,而不是每个实例(既是为了减少每个实例的开销,也是为了在大多数情况下绕过实例查找的 dunder 特殊方法正确工作)。

最近找到了一个(可能只有 CPython)的解决方案。它有点难看,是一个直接调用 CPython API 的 ctypes hack,但它有效,并获得了所需的性能:

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 版本的一种改进,因为它不需要定义 class 来为它创建未绑定的方法,您可以定义它在 class 主体中通过简单赋值(其中 Python 2 版本必须在 Foo.__len__ = types.MethodType(attrgetter('length'), None, Foo) 中明确引用 Foo 两次,并且仅在 class Foo 完成定义之后) .

另一方面,它实际上并没有在 CPython 3.7 AFAICT 上提供性能优势,至少对于它正在替换的简单情况而言不是这样def __len__(self): return self.length;事实上,对于通过 len(instance)Fooipython %%timeit 实例上访问的 __len__,微基准测试显示 len(instance)__len__ 是通过 __len__ = make_instance_method(attrgetter('length')), 定义的。这可能是 attrgetter 本身的产物,由于 CPython 没有将其移动到 "FastCall" 协议(在 3.8 中称为 "Vectorcall" ,当它被制作成半-public 供第三方临时使用),而用户定义的函数在 3.7 中已经从中受益,并且每次都必须动态选择是否执行带点或不带点的属性查找以及单个或多个属性查找( Vectorcall 可以通过选择适合在构造时执行的获取的 __call__ 实现来避免)增加了普通方法避免的更多开销。它应该适用于更复杂的情况(例如,如果要检索的属性是嵌套属性,如 self.contained.length),因为 attrgetter 的开销大部分是固定的,而 [=41= 中的嵌套属性查找] 意味着更多的字节码,但现在,它并不经常有用。

如果他们有时间为 Vectorcall 优化 operator.attrgetter,我将重新进行基准测试并更新此答案。