实例方法的装饰器

Bra*_*mon 6 python python-3.x python-decorators

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

参加以下课程:

class MyClass(object):
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b
    def meth(self):
        pass
Run Code Online (Sandbox Code Playgroud)

如果meth()未修饰,MyClass().meth.__self__则引用实例方法并启用类似setattr(my_class_object.meth.__self__, 'a', 5).

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

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            <--------
Run Code Online (Sandbox Code Playgroud)

这可以被覆盖吗?

nos*_*klo 5

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

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

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

事实上,原始meth函数永远不会变成方法 - 相反,从装饰器返回的函数将成为类的一部分,并且将是稍后wrapper获取属性的函数。__self__


And*_*ely 3

如果您装饰类的方法,第一个参数始终是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()
Run Code Online (Sandbox Code Playgroud)

印刷:

True
Run Code Online (Sandbox Code Playgroud)

编辑:

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

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()
Run Code Online (Sandbox Code Playgroud)

还打印:

True
Run Code Online (Sandbox Code Playgroud)