【问题标题】:Decorator changing function status from method to function装饰器将函数状态从方法更改为函数
【发布时间】:2011-04-03 14:30:58
【问题描述】:

[更新]:回答以下问题

我有一个检查程序,一个目标是让装饰器中的逻辑知道它正在装饰的函数是类方法还是常规函数。这以一种奇怪的方式失败了。以下是在 Python 2.6 中运行的代码:

def decorate(f):
    print 'decorator thinks function is', f
    return f

class Test(object):
    @decorate
    def test_call(self):
        pass

if __name__ == '__main__':
    Test().test_call()
    print 'main thinks function is', Test().test_call

然后在执行时:

decorator thinks function is <function test_call at 0x10041cd70>
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>>

关于出了什么问题的任何线索,以及@decorate 是否有可能正确推断出 test_call 是一种方法?

[回答] 卡尔在下面的回答几乎是完美的。在子类调用的方法上使用装饰器时遇到问题。我修改了他的代码以包含对超类成员的 im_func 比较:

ismethod = False
for item in inspect.getmro(type(args[0])):
    for x in inspect.getmembers(item):
        if 'im_func' in dir(x[1]):
            ismethod = x[1].im_func == newf
            if ismethod:
                break
    else:
        continue
    break

【问题讨论】:

    标签: python decorator inspection


    【解决方案1】:

    正如其他人所说,函数在绑定之前就被修饰了,所以你无法直接确定它是“方法”还是“函数”。

    判断一个函数是否是一个方法的合理方法是检查'self'是否是第一个参数。虽然并非万无一失,但大多数 Python 代码都遵循此约定:

    import inspect
    ismethod = inspect.getargspec(method).args[0] == 'self'
    

    这是一种复杂的方式,似乎可以自动判断该方法是否是绑定的。适用于 CPython 2.6 上的一些简单案例,但没有承诺。如果第一个参数是一个绑定了修饰函数的对象,它就决定一个函数是一个方法。

    import inspect
    
    def decorate(f):
        def detect(*args, **kwargs):
            try:
                members = inspect.getmembers(args[0])
                members = (x[1].im_func for x in members if 'im_func' in dir(x[1]))
                ismethod = detect in members
            except:
                ismethod = False
            print ismethod
    
            return f(*args, **kwargs)
        return detect
    
    @decorate
    def foo():
        pass
    
    class bar(object):
        @decorate
        def baz(self):
            pass
    
    foo() # prints False
    bar().baz() # prints True
    

    【讨论】:

      【解决方案2】:

      我尝试了一个稍微不同的示例,一种装饰方法和一种未装饰方法。

      def decorate(f):
        print 'decorator thinks function is', f
        return f
      
      class Test(object):
        @decorate
        def test_call(self):
          pass
        def test_call_2(self):
          pass
      
      if __name__ == '__main__':
        print 'main thinks function is', Test.test_call
        print 'main thinks function 2 is', Test.test_call_2
      

      那么输出是:

      decorator thinks function is <function test_call at 0x100426b18>
      main thinks function is <unbound method Test.test_call>
      main thinks function 2 is <unbound method Test.test_call_2>
      

      因此,装饰器看到了与主函数不同的类型,但装饰器没有改变函数的类型,否则会与未装饰的函数不同。

      【讨论】:

        【解决方案3】:

        不,这是不可能的,因为绑定的方法和函数之间没有内在的区别。方法只是一个封装起来的函数,以获取调用实例作为第一个参数(使用 Python descriptors)。

        类似的调用:

        Test.test_call
        

        返回一个未绑定的方法,转换为

        Test.__dict__[ 'test_call' ].__get__( None, spam )
        

        这是一个未绑定的方法,即使

        Test.__dict__[ 'test_call' ]
        

        是一个函数。这是因为函数是其__get__ 方法返回方法的描述符;当 Python 在查找链中看到其中之一时,它会调用 __get__ 方法而不是继续查找链。

        实际上,函数的“绑定方法”是在运行时确定的,而不是在定义时确定的!

        装饰器只看到定义的函数,而不是在__dict__ 中查找它,因此无法判断它是否正在查看绑定的方法。


        可能可以使用修改 __getattribute__ 的类装饰器来做到这一点,但这是一个特别讨厌的 hack。为什么一定要有这个功能?当然,既然你必须自己把装饰器放在函数上,你可以给它传递一个参数来说明该函数是否定义在一个类中?

        class Test:
            @decorate( method = True )
            def test_call:
                ...
        
        @decorate( method = False )
        def test_call:
            ...
        

        【讨论】:

          【解决方案4】:

          您的装饰器在函数成为方法之前运行。类中的def关键字在任何其他地方定义了一个函数行,然后将类主体中定义的函数作为方法添加到类中。装饰器在函数被类处理之前对其进行操作,这就是您的代码“失败”的原因。

          @decorate 无法看到函数实际上是一个方法。一种解决方法是装饰函数(例如添加属性do_something_about_me_if_I_am_a_method;-)),然后在计算类后再次处理它(迭代类成员并对那些装饰做任何你想做的事情) .

          【讨论】:

          • 它们实际上作为函数存储在__dict__ 类中——试试看!它们仅在您访问它们时才被绑定。但是,是的,没有办法做到这一点=)。
          • 这是可能的。那么,我一定是使用 getattr() 来访问它们。
          猜你喜欢
          • 1970-01-01
          • 2022-10-18
          • 2020-02-29
          • 1970-01-01
          • 2019-07-21
          • 2021-11-21
          • 2014-09-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多