【问题标题】:Convert builtin function type to method type (in Python 3)将内置函数类型转换为方法类型(在 Python 3 中)
【发布时间】:2012-05-24 01:05:57
【问题描述】:

考虑一个简单的函数

def increment(self):
    self.count += 1

通过 Cython 运行并编译成扩展模块。假设现在我想让这个函数成为一个类的方法。例如:

class Counter:
    def __init__(self):
        self.count = 0

from compiled_extension import increment
Counter.increment = increment

现在这将不起作用,因为 C 级别的调用约定将被破坏。例如:

>>> c = Counter()
>>> c.increment()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: increment() takes exactly one argument (0 given)

但在 Python 2 中,我们可以通过以下方式将函数转换为未绑定的方法:

Counter.increment = types.MethodType(increment, None, Counter)

如何在 Python 3 中完成同样的事情?

一种简单的方法是使用纤细的包装器:

from functools import wraps
def method_wraper(f):
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return wraps(f)(wrapper)

Counter.increment = method_wrapper(increment)

有没有更有效的方法?

【问题讨论】:

  • 我在尝试使用丑陋的 heapq 模块构建 Heapq 类时遇到了这个问题。你的解决方案很好。可以一行完成,但效率相同:def method_wraper(f): return functools.wraps(f)(lambda *a, **kw: f(*a, **kw))
  • ...有趣的是,如果函数是在同一个模块中定义的(未绑定的方法被分配给一个类并在实例化时绑定),那么分配似乎可以正常工作。所以这只是 C 扩展的问题,还是不同模块中的函数的问题?无论如何,您可能想查看stackoverflow.com/questions/7490879/…,这也可以帮助您一点。
  • 另外,docs.python.org/py3k/howto/…“在 Objects/classobject.c 中 PyMethod_Type 的实际 C 实现是具有两种不同表示的单个对象,具体取决于 im_self 字段是否设置或为 NULL(C 等效于没有任何)。”这使得这个问题看起来根本不应该发生,除非 Python 在实例化对象时不直接为对象的方法更新该字段。
  • 问题在于运行时对内置函数和 python 函数的处理方式不同,与位于不同的模块中无关。关于 PyMethod_Type 的评论并不真正适用,因为它代表了在 python 中定义的方法,而不是内置函数/方法。
  • 不应该是“将函数转换为绑定方法”吗?

标签: python python-3.x cython


【解决方案1】:

首先要正确获取名称:

>>> def increment(obj):
...     obj.count += 1
...
>>> class A(object):
...     def __init__(self):
...         self.count = 0
...
>>> o = A()
>>> o.__init__
<bound method A.__init__ of <__main__.A object at 0x0000000002766EF0>>
>>> increment
<function increment at 0x00000000027797C8>

所以专有名称是函数绑定方法。现在您可以查找Bind an Unbound Method 的方法,您可能最终会阅读到有关descriptors 的信息:

一般来说,描述符是具有“绑定”的对象属性 行为”,其属性访问已被方法覆盖 在描述符协议中。这些方法是__get____set____delete__。如果为对象定义了这些方法中的任何一个,则称其为描述符。

您只需使用__get__的不同调用即可轻松地将函数转换为方法

>>> increment.__get__(None, type(None))
<function increment at 0x00000000027797C8>
>>> increment.__get__(o, type(o))
<bound method A.increment of <__main__.A object at 0x00000000027669B0>>

它就像一个魅力:

>>> o = A()
>>> increment.__get__(None, type(None))(o)
>>> o.count
1
>>> increment.__get__(o, type(o))()
>>> o.count
2

您可以轻松地将这些新绑定的方法添加到对象中:

def increment(obj):
    obj.count += 1

def addition(obj, number):
    obj.count += number

class A(object):
    def __init__(self):
        self.count = 0

o = A()
o.inc = increment.__get__(o)
o.add = addition.__get__(o)
print(o.count) # 0
o.inc()
print(o.count) # 1
o.add(5)
print(o.count) # 6

或者创建你自己的descriptor,它将function转换成bound method

class BoundMethod(object):
    def __init__(self, function):
        self.function = function

    def __get__(self, obj, objtype=None):
        print('Getting', obj, objtype)
        return self.function.__get__(obj, objtype)

class B(object):
    def __init__(self):
        self.count = 0

    inc = BoundMethod(increment)
    add = BoundMethod(addition)


o = B()
print(o.count) # 0
o.inc()
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 1
o.add(5) 
# Getting <__main__.B object at 0x0000000002677978> <class '__main__.B'>
print(o.count) # 6

您还可以看到这与function/bound method principles 非常一致:

类字典将方法存储为函数。在类定义中,方法是使用 def 和 lambda 编写的,它们是创建函数的常用工具。与常规函数的唯一区别是第一个参数是为对象实例保留的。按照 Python 约定,实例引用称为 self 但可以称为 this 或任何其他变量名。

为了支持方法调用,函数包括__get__() 方法,用于在属性访问期间绑定方法。这意味着所有函数都是非数据描述符,它们返回绑定或未绑定的方法,具体取决于它们是从对象调用还是从类调用。

functions在实例初始化时变成绑定方法

>>> B.add
# Getting None <class '__main__.B'>
<function addition at 0x00000000025859C8>
>>> o.add
# Getting <__main__.B object at 0x00000000030B1128> <class '__main__.B'>
<bound method B.addition of <__main__.B object at 0x00000000030B1128>>

【讨论】:

  • 通常,CPython 中的内置类型使用method_descriptor,绑定为builtin_function_or_method,例如str.upper.__get__('a').__self__ == 'a'builtin_function_or_method 本身不是描述符。后者是 Cython 创建的,因此 OP 正在寻找一种方法将其包装在描述符中,例如 partialmethod (3.4+)。
  • 我认为你的答案不是 OP 想要的。
【解决方案2】:

像这样导入扩展:

import compiled_extension

在你的课堂上你写:

def increment: return compiled_extension.increment()

这似乎更具可读性并且可能更有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 2019-08-18
    • 2022-01-24
    • 2014-11-17
    • 2018-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多