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

问题描述 投票:1回答:1

在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中完成此任务的方法吗?

我看过的东西:

  1. functools.partialmethod(似乎没有C实现,因此未满足要求,并且在Python实现和比我需要的更通用的目的之间),它是[[slow,大约需要5个[[us在我的测试中,与直接Python定义或Py2中的attrgetter相比,约200-300 ns,开销大约增加了20倍]
  2. 试图使attrgetter等遵循非数据描述符协议(不可能的AFAICT,不能在__get__等中进行猴子补丁)[试图找到将attrgetter赋予其__get__的方法,但是当然,__get__需要以某种方式委托给C层,现在我们回到了开始的位置
  3. (特定于attrgetter用例)首先使用__slots__使成员成为描述符,然后尝试以某种方式将数据的所得描述符转换为跳过绑定和获取最终结果的步骤实际值,使其可以调用,因此推迟了实际值检索]
  • 我不能发誓,尽管如此,我没有错过任何东西。有人有解决方案吗?允许黑客入侵;我知道我在这里做病理性的事情。理想情况下,它应该是灵活的(使您可以使用class,Python内置函数(例如hexlen等)或未定义为Python层)。重要的是,它需要附加到类上,而不是附加到每个实例上(两者都是为了减少每实例的开销,并且必须在特殊的方法下正常工作,这些方法在大多数情况下会绕过实例查找)。
  • python python-3.x methods built-in python-descriptors
    1个回答
    0
    投票
    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微基准测试表明,当通过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,我将重新确定基准并更新此答案。

    © www.soinside.com 2019 - 2024. All rights reserved.