Decorators for Instance Methods

非 Y 不嫁゛ 提交于 2019-12-08 02:01:17

问题


Wrapping a class's method in a "boilerplate" Python decorator will treat that method as a regular function and make it lose its __self__ attribute that refers to the class instance object. Can this be avoided?

Take the following class:

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

If meth() is undecorated, MyClass().meth.__self__ refers to an instance method and enables something like setattr(my_class_object.meth.__self__, 'a', 5).

But when wrapping anything in a decorator, only the function object is passed; the object to which it is actually bound is not passed on along with it. (See this answer.)

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            <--------

Can this be overriden?


回答1:


If you decorate method of the class, first argument is always self object (you can access it with 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()

Prints:

True

Edit:

You can specify also self in your wrapper function (based on comments):

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()

Prints also:

True



回答2:


Your main misunderstanding here is order of operation.

When the decorate() decorator is called, meth() is not a method yet - it is still a function - it is only when the class block is over that meth is transformed into a method by the metaclass descriptors! - that's why it doesn't have __self__ (yet).

In other words, to decorate methods you have to ignore the fact that they are methods and treat them as normal functions - because that's what they are when the decorator is called.

In fact, the original meth function will never turn into a method - instead the function wrapper you returned from the decorator will be part of the class and will be the one that will get the __self__ attribute later.




回答3:


Let me clarify the process of decorating:

When you decorate meth with decorate in class MyClass, you are doing:

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

As you can see, decorate takes method which is a function as parameter and return wrapper which is another funtion. And now the original meth in MyClass is replaced by new one wrapper. So when you call myclass_instance.meth(), you are calling the new wrapper function.

There isn't any black magic, so self can be definitely passed into wrapper, and it is safe to accept self using wrapper(self, *args, **kwargs).



来源:https://stackoverflow.com/questions/51542063/decorators-for-instance-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!