2012-05-24 41 views
16

考虑等,其是通过用Cython运行并编译成一个扩展模块转换内建函数类型方法类型(在Python 3)

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

一个简单的函数。假设现在我想让这个函数成为一个类的方法。例如:

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) 

有没有更有效的方式来做到这一点?

+2

我有这个问题尝试使用丑'heapq'模块构建Heapq类。你的解决方案很好。它可以在一行中完成,但具有相同的效率:'def method_wraper(f):return functools.wraps(f)(lambda * a,** kw:f(* a,** kw))' – JBernardo

+0

。有趣的是,如果函数是在同一个模块中定义的(分配给一个类并绑定在实例化上的未绑定方法),赋值似乎可以正常工作。所以这只是C扩展的一个问题,或者与不同模块中的函数有关?无论如何,你可能想看看http://stackoverflow.com/questions/7490879/python3-bind-method-to-class-instance-with-get-it-works-but-why,这可以帮助你一点点以及。 – JAB

+0

另外,http://docs.python.org/py3k/howto/descriptor.html#functions-and-methods“对象/ classobject.c中PyMethod_Type的实际C实现是一个具有两个不同表示的对象,具体取决于是否im_self字段被设置或者为NULL(C的等效值为None)。“这使得看起来应该不会发生这个问题,除非Python在实例化对象时以某种方式不直接更新对象方法的该字段。 – JAB

回答

0

导入扩展这样的:

import compiled_extension 

在你的类你写:

def increment: return compiled_extension.increment() 

这似乎更容易阅读和可能更有效。

4

第一件事就是让正确的名字:

>>> 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 

您可以轻松地添加这些新界定方法 to object:

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 

或者创建自己的描述将将转换功能绑定方法

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__()方法。这意味着所有函数都是非数据描述符,它们返回绑定或未绑定的方法,具体取决于它们是从对象还是类调用。

而且功能成为绑定的方法实例的初始化期间:

>>> 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>> 
+0

通常,CPython中的内置类型使用'method_descriptor',它被绑定为'builtin_function_or_method',例如'str.upper .__ get __('a').__ self__ =='a''。'builtin_function_or_method'本身不是*描述符。后者是Cython创建的,所以OP正在寻找一种方法来将其包装在描述符中,例如['partialmethod'](https://docs.python.org/3/library/functools.html#functools.partialmethod )(3.4+)。 – eryksun

+0

我认为你的回答不是OP想要的。 – laike9m