使用可调用实例装饰类函数

时间:2014-10-06 23:57:08

标签: python class decorator

我尝试通过将其替换为可调用类的实例来修饰函数:

class FunctionFaker( object ):
    def __init__( self, f ):
        self.f= f
    def empty_function( self ):
        pass
    def __call__( self, *args, **kwargs ):
        self.f( *args, **kwargs)

def fakefunction( f ):
    '''a decorator that transforms a function into a FunctionFaker'''
    return FunctionFaker(f)

@fakefunction
def dosomething():
    pass

dosomething.empty_function()
dosomething()

这可以按预期工作。

然而,只要我尝试装饰一个类方法:

class Test( object ):
    @fakefunction
    def dosomething(self):
        pass

t=Test()
t.dosomething.empty_function()
t.dosomething()

我得到TypeError: dosomething() takes exactly 1 argument (0 given)

现在,我想I can answer the why

  

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

因此,FunctionFaker不是一个函数,没有上述描述符,因此不会破坏参数。

如何实现能够替换实例方法的可调用类?

2 个答案:

答案 0 :(得分:2)

  

我刚刚意识到我可以简单地实现__get__并返回types.MethodType,但我真的不明白这样做是如何让您调用empty_function的。

这是因为MethodType__getattribute__方法将未知属性委托给其im_func

>>> t.dosomething
<bound method Test.? of <__main__.Test object at 0x107639550>>
>>> 'empty_function' in dir(t.dosomething)
False
>>> t.dosomething.__getattribute__
<method-wrapper '__getattribute__' of instancemethod object at 0x109583aa0>
>>> t.dosomething.__getattribute__('empty_function')
<bound method FunctionFaker.empty_function of <__main__.FunctionFaker object at 0x1095f2510>>

当然在CPython中,C API并没有完全反映__getattribute____getattr__之间的Python级别区别,所以的实际方式 实现了自定义getattro插槽。您可以阅读详细信息in the source

  

它只是成为MethodType实例的属性吗?

是的,但只能动态地为您提供基础可调用的属性。

我不认为他们特意打算让类实例用自己的方法描述符替换函数。但是,即使是将属性附加到方法的简单情况,也需要这种支持。例如,使用独立函数,您可以将函数属性用于例如memoization缓存或lazy-initialize-on-first-call设置。如果MethodType没有委托对其im_func对象的属性访问权限,那么将这样的函数移动到类中会破坏它,并且除非他知道,否则开发人员无法修复它描述符如何工作并以丑陋的方式重写该方法。

事实上,高达2.3,方法甚至没有__dict__;从the source可以看到,所有属性除了C插槽被委托给im_func之外(通过有效地复制正常机制将所有内容委托给im_func但是包装错误)。关于这一点有一些争论,你可以通过搜索python-dev档案找到Christian Tismer在2.4之前的一个帖子中找到一个具有相关外观的主题(可能是this thread,但我没有& #39;读完整件事......)。从2.4开始,方法现在执行常规查找机制(__doc__的特殊情况除外),如果失败则仅委托给im_func

  

这是理智的事吗?

它有点奇怪,可能更容易将empty_function属性添加到函数对象中,而不是将其包装在类中...但我不会&#39认为这太不合理了。 (我假设你问的是你的代码,而不是MethodType和描述符是如何实现的。)

答案 1 :(得分:0)

你走在正确的轨道上。您希望自己的班级也是descriptor

import types

class FunctionFaker( object ):
    def __init__(self, f):
        self.f= f

    def empty_function(self):
        pass

    def __call__(self, *args, **kwargs):
        self.empty_function()
        self.f(*args, **kwargs)

    def __get__(self, instance, cls=None):
        # see https://docs.python.org/2/howto/descriptor.html#functions-and-methods
        return types.MethodType(self, instance, cls)

@FunctionFaker
def foo(arg1):
  print "in foo", arg1

class Bar(object):
  @FunctionFaker
  def bar(self, arg1):
    print "in bar", self, arg1

foo('hello')  # in foo hello
Bar().bar('world')  # in bar <__main__.Bar object at 0x7fc50b90fb10> world
Bar().bar.empty_function()

现在,如果你的装饰器绑定到一个类,它的行为就像一个描述符(在装饰函数中将正确的实例绑定到self),如果它没有绑定到类,它的行为就像一个普通的装饰者。整齐。