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