【问题标题】:decorating a class function with a callable instance用可调用实例装饰类函数
【发布时间】:2014-12-01 07:48:08
【问题描述】:

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

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,不是一个函数,没有上述描述符,因此不会破坏参数。

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

【问题讨论】:

  • 我刚刚意识到我可以简单地实现__get__ 并返回types.MethodType,但我真的不明白这样做仍然可以让您调用empty_function。它只是成为MethodType 实例的一个属性吗?这是明智的做法吗?
  • 签名是types.MethodType(callable, instance, class),您可以传递任何类型的可调用对象,包括具有__call__ 函数的类的实例。

标签: python class decorator


【解决方案1】:

我刚刚意识到我可以简单地实现__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 实例的一个属性吗?

是的,但只能动态地,通过为您提供底层可调用的属性。

我不认为他们专门打算启用类实例来替换具有自己的方法描述符的函数。但是,即使是简单的将属性附加到方法的情况,也需要这种支持。例如,对于独立函数,您可以将函数属性用于例如记忆缓存或延迟初始化首次调用设置。如果MethodType 没有将属性访问委托给它的im_func 对象,那么将这样的函数移动到一个类中会破坏它,并且开发人员将无法修复它,除非他知道描述符是如何工作的并在一种丑陋的方式。

事实上,直到 2.3,方法甚至都没有__dict__;正如您从the source 中看到的那样,除了C 插槽之外的所有 属性都被委托给im_func(通过有效地复制正常机制以将所有内容委托给im_func,但包装错误)。关于这个有一些争论,你可能可以通过在 python-dev 档案中搜索 Christian Tismer 在 2.4 之前的时期发表的一篇具有相关主题的帖子(可能是this thread,但我没有阅读全文……)。从 2.4 开始,方法现在执行正常的查找机制(__doc__ 的特殊情况除外),并且只有在失败时才委托给 im_func

这是一个理智的事情吗?

有点奇怪,将empty_function属性添加到函数对象中可能更简单,而不是将其包装在一个类中……但我认为这不是太不合理。 (我假设你问的是你的代码,而不是MethodType 和描述符是如何实现的。)

【讨论】:

  • 太棒了。 MethodType 实现 __getattribute__ 是否意味着 python 开发人员预料到了这种非常需要?还是有其他原因? MethodType 上的文档很少,有什么参考吗?
  • 好的,我在编辑后的答案中回答了您评论的前半部分。对于后半部分......不,除了阅读源代码和/或逆向工程之外,真的没有参考。 (您链接的描述符 HOWTO 仅指向 classobject.c 中的相同源,我为详细信息链接。)在 Python 中有一些类似的地方;随着 PyPy 和其他实现的开发人员提出它们,它们正在逐渐被清理,但这个仍然没有记录。
【解决方案2】:

你在正确的轨道上。您希望您的班级也成为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),如果它没有绑定到一个类,它的行为就像一个正常的装饰师。整洁。

【讨论】:

  • 不,这实际上不起作用;如果他返回partial 而不是MethodTypeempty_function 将引发AttributeError
  • @abarnert - 我不确定 OP 从哪里调用 empty_function,但它似乎在我的测试中有效。 . .
  • 他已经创建了一个返回 types.MethodType 的描述符,就像普通函数一样,正如文档明确推荐的那样。因为MethodType 有一个委托__getattribute__,所以t.dosomething.empty_function() 有效。随着您的更改,t.dosomething 现在是 partial,因此 t.dosomething.empty_function() 将引发 AttributeError
  • @abanert 现场。但在这一点上,我真的想知道这是否可行
  • @abarnert -- 啊,现在我明白 OP 想要什么了。我没有意识到目标是为函数添加属性。并感谢“作为文档明确推荐”。我不记得在描述符文档中 - 很遗憾types 文档没有更多地说明各种类型的签名以及它们的作用......
猜你喜欢
  • 2014-07-30
  • 2011-03-23
  • 1970-01-01
  • 2020-02-03
  • 2018-04-28
  • 2019-02-03
相关资源
最近更新 更多