【问题标题】:Decorators for Instance Methods实例方法的装饰器
【发布时间】:2019-01-03 15:22:09
【问题描述】:

将类的方法包装在“样板”Python 装饰器中会将该方法视为常规函数,并使其失去引用类实例对象的__self__ 属性。这可以避免吗?

参加以下课程:

class MyClass(object):
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b
    def meth(self):
        pass

如果meth() 未修饰,MyClass().meth.__self__ 指的是实例方法并启用类似setattr(my_class_object.meth.__self__, 'a', 5) 的东西。

但是当在装饰器中包装任何东西时,只传递函数对象;它实际绑定的对象不会随它一起传递。 (见this答案。)

import functools

def decorate(method):
    @functools.wraps(method)
    def wrapper(*args, **kwargs):
        # Do something to method.__self__ such as setattr
        print(hasattr(method, '__self__'))
        result = method(*args, **kwargs)
        return result
    return wrapper

class MyClass(object):
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b
    @decorate
    def meth(self):
        pass

MyClass().meth()
# False            <--------

这可以被覆盖吗?

【问题讨论】:

  • 这对我来说似乎是一个 XY 问题。为什么要访问__self__ 属性?为什么不直接添加self 作为wrapper 的第一个参数?
  • 为什么你不能做setattr(args[0], 'a', 5)args[0] on 方法是 self
  • @Aran-Fey 我怀疑有人会这么说......一个例子:使用装饰器更新一个类属性,该属性是方法调用及其参数的日志。
  • @AndrejKesely 啊,呃。我认为这应该在这里工作
  • 真的有意义吗?使用args[0] 而不是def wrapper(self, *args, **kwargs):?有什么区别?

标签: python python-3.x python-decorators


【解决方案1】:

您在这里的主要误解是操作顺序。

decorate() 装饰器被调用时,meth() 还不是一个方法——它仍然是一个函数——只有当class 块结束时,meth 才被元类转换为一个方法描述符! - 这就是为什么它还没有__self__

换句话说,要装饰方法,您必须忽略它们是方法这一事实,并将它们视为普通函数 - 因为当调用装饰器时它们就是这样。

事实上,原来的 meth 函数永远不会变成一个方法——相反,你从装饰器返回的函数 wrapper 将成为类的一部分,并且稍后将获得 __self__ 属性.

【讨论】:

    【解决方案2】:

    如果你装饰类的方法,第一个参数总是self对象(你可以用args[0]访问它):

    import functools
    
    def decorate(method):
        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            print(hasattr(args[0], 'a'))
            result = method(*args, **kwargs)
            return result
        return wrapper
    
    class MyClass(object):
        def __init__(self, a=1, b=2):
            self.a = a
            self.b = b
        @decorate
        def meth(self):
            pass
    
    MyClass().meth()
    

    打印:

    True
    

    编辑:

    您还可以在 wrapper 函数中指定 self(基于 cmets):

    import functools
    
    def decorate(method):
        @functools.wraps(method)
        def wrapper(self, *args, **kwargs):
            print(hasattr(self, 'a'))
            result = method(self, *args, **kwargs)
            return result
        return wrapper
    
    class MyClass(object):
        def __init__(self, a=1, b=2):
            self.a = a
            self.b = b
        @decorate
        def meth(self):
            pass
    
    MyClass().meth()
    

    也打印:

    True
    

    【讨论】:

      【解决方案3】:

      让我澄清一下装饰的过程:

      当你在课堂MyClass 中用decorate 装饰meth 时,你正在做:

      class MyClass(object):
          ... omit
          meth = decorate(meth)  # the meth in "()" is the original function.
      

      如您所见,decorate 将函数 method 作为参数,并返回另一个函数 wrapper。现在MyClass 中的原始meth 被新的wrapper 替换。所以当你调用myclass_instance.meth() 时,你调用的是新的wrapper 函数。

      没有什么黑魔法,所以self绝对可以传递给wrapper,使用wrapper(self, *args, **kwargs)接受self是安全的。

      【讨论】:

        猜你喜欢
        • 2019-02-03
        • 2013-02-12
        • 1970-01-01
        • 2017-11-12
        相关资源
        最近更新 更多