[英]class decorator issue, or: How does Python discriminate methods from staticmethods?
I have a class decorator that modifies some functions but not others. 我有一个类修饰器 ,可以修饰某些功能,但不能修饰其他功能。 Simplified example: 简化示例:
import functools
import inspect
import types
def mydecorator(myobj):
@functools.wraps(myobj)
def decorated_method(*args, **kwargs):
print("I'm decorated!")
return myobj(*args, **kwargs)
if inspect.isclass(myobj): # act as class decorator
for name, obj in myobj.__dict__.items():
if name == "add":
setattr(myobj, name, types.MethodType(mydecorator(obj), myobj))
return myobj # return decorated class
elif inspect.isfunction(myobj): # act as function decorator
return decorated_method
else:
assert False, "can decorate only classes and functions"
So this will modify any add
method to print "I'm decorated" before it runs. 因此,这将修改任何add
方法以在运行之前打印“我被装饰”。
We'll apply it to this class: 我们将其应用于此类:
class MyClass:
def add(self, x, y): return x + y
def mul(self, x, y): return x * y
and it works alright. 它可以正常工作。 We do 我们的确是
#--- try out undecorated MyClass:
print("MyClass.add:", MyClass.add, "MyClass.mul:", MyClass.mul)
print("3+4 =", MyClass().add(3, 4), "3*4 =", MyClass().mul(3, 4), )
#--- decorate MyClass:
print("MyClass = mydecorator(MyClass)")
MyClass = mydecorator(MyClass)
#--- try out decorated MyClass in the same manner:
print("MyClass.add:", MyClass.add, "MyClass.mul:", MyClass.mul)
print("3+4 =", MyClass().add(3, 4), "3*4 =", MyClass().mul(3, 4), )
and get this output (from CPython 3.6.7 on Linux) 并获得此输出(从Linux上的CPython 3.6.7)
MyClass.add: <function MyClass.add at 0x7faededda0d0> MyClass.mul: <function MyClass.mul at 0x7faededda158>
3+4 = 7 3*4 = 12
MyClass = mydecorator(MyClass)
MyClass.add: <bound method MyClass.add of <class '__main__.MyClass'>> MyClass.mul: <function MyClass.mul at 0x7faededda158>
I'm decorated!
3+4 = 7 3*4 = 12
So mul
stays a plain function while the decorated add
turns into a bound method. 因此,当装饰的add
变成绑定方法时, mul
保持普通功能。 The decoration works correctly. 装饰工作正常。
But when I now change the method such that add
calls mul
(ignore the fact this does not make much sense) as follows: 但是,当我现在更改方法以使add
调用mul
(忽略这一事实并没有多大意义),如下所示:
class MyClass:
def add(self, x, y): z = self.mul(x, y); return x + y
def mul(self, x, y): return x * y
the output turns into this: 输出变为:
MyClass.add: <function MyClass.add at 0x7fbc760870d0> MyClass.mul: <function MyClass.mul at 0x7fbc76087158>
3+4 = 7 3*4 = 12
MyClass = mydecorator(MyClass)
MyClass.add: <bound method MyClass.add of <class '__main__.MyClass'>> MyClass.mul: <function MyClass.mul at 0x7fbc76087158>
I'm decorated!
Traceback (most recent call last):
File "tryout.py", line 34, in <module>
print("3+4 =", MyClass().add(3, 4), "3*4 =", MyClass().mul(3, 4), )
File "tryout.py", line 16, in decorated_method
return myobj(*args, **kwargs)
File "tryout.py", line 7, in add
def add(self, x, y): z = self.mul(x, y); return x + y # round 2
TypeError: mul() missing 1 required positional argument: 'y'
It turns out that mul
(although it is the same as before!) is now being called as if it were a @staticmethod
: self
is not passed. 事实证明,现在正在调用mul
(尽管它与以前相同!)就像是@staticmethod
:未传递self
。
I have plenty of questions: 我有很多问题:
add
bound to? add
绑定到哪个对象? @classmethod
or a @staticmethod
? Python如何在内部将普通方法与@classmethod
或@staticmethod
? types.MethodType
really mean? types.MethodType
到底是什么意思? The problem is that you should not replace the function add
with a bound method. 问题是您不应该使用绑定方法替换函数 add
。 The way methods work is that a function
object has a __get__
method which, in the case of an instance method, returns a bound method for you to be called on the provided arguments. 方法的工作方式是一个function
对象具有一个__get__
方法,在实例方法的情况下,该方法返回绑定的方法供您在提供的参数上调用。 That is, given 也就是说,给定
class MyClass:
def add(self, x, y):
return x + y
def mul(self, x, y):
return x * y
o = MyClass()
a call like o.add(3,5)
is equivalent to type(o).__dict__['add'].__get__(o, type(o))(3,5)
. 像o.add(3,5)
这样的调用等效于type(o).__dict__['add'].__get__(o, type(o))(3,5)
。
Your decorator should also simply return a new function, rather than a method
object, and let its __get__
method do its job. 装饰器还应该简单地返回一个新函数,而不是method
对象,并使其__get__
方法完成其工作。
Your new decorator, with some simplifications: 您的新装饰器,有一些简化:
def mydecorator(myobj):
@functools.wraps(myobj)
def decorated_method(*args, **kwargs):
print("I'm decorated!")
return myobj(*args, **kwargs)
# Decorating a function
if inspect.isfunction(myobj):
return decorated_method
# Decorating a class
if inspect.isclass(myobj):
if "add" in myobj.__dict__:
setattr(myobj, "add", mydecorator(obj))
# Or just setattr(myobj, "add", decorated_method),
# unless you think myobj.add might be a nested class
return myobj
# Anything else is type error.
raise TypeError("can decorate only classes and functions")
Addressing some of your other questions... 解决您的其他一些问题...
How does Python internally discriminate a normal method from a @classmethod or a @staticmethod? Python如何在内部区分普通方法与@classmethod或@staticmethod?
The classmethod
and staticmethod
objects return objects that have different __get__
methods than a regular function
object. classmethod
和staticmethod
对象返回的对象具有与常规function
对象不同的__get__
方法。
Where would I have found the documentation of all this? 我在哪里可以找到所有这些文档?
The Descriptor How-to Guide is a good place to start. 描述符操作指南是一个不错的起点。 It describes the descriptor protocol, as well as examples of how things like properties and methods make use of it. 它描述了描述符协议,以及诸如属性和方法之类的东西如何使用它的示例。
To add to @chepner's great answer and to answer your point 4. 添加到@chepner的出色答案中并回答您的观点4。
What does types.MethodType really mean? type.MethodType到底是什么意思?
This special type allows us to add methods to already created instances, in your case there's no instance, hence when you pass myobj
to it, you're basically setting __self__
of the wrapper to its class rather than using its instance. 这种特殊类型允许我们向已经创建的实例添加方法,在您的情况下没有实例,因此,当将myobj
传递给它时,基本上是将包装器的__self__
设置为其类,而不是使用其实例。
Let's take the simpler version of your class, without the decorator: 让我们使用没有装饰器的类的简单版本:
class MyClass:
def add(self, x, y):
z = self.mul(x, y);
return x + y
def mul(self, x, y): return x * y
Now: 现在:
>>> ins = MyClass()
>>> ins.add.__func__
<function MyClass.add at 0x7f483050cf28>
>>> ins.add.__self__
<__main__.MyClass object at 0x7f48304ef390>
As you can see, the add
is a whole new object that has now info on which function to call and what to pass as first argument. 如您所见, add
是一个全新的对象,该对象现在具有有关调用哪个函数以及作为第一个参数传递什么的信息。
>>> ins.add(1, 2)
3
>>> ins.add.__func__(ins.add.__self__, 1, 2)
3
Now when we do, what you did: 现在,当我们做时,您做了什么:
>>> MyClass.add = types.MethodType(ins.add.__func__, MyClass)
This now passes the class to the function add
instead of the instance: 现在,将类传递给函数add
而不是实例:
>>> ins = MyClass()
>>> ins.add.__self__
<class '__main__.MyClass'>
which means the self
inside add
now is not the actual instance, but the class. 这意味着现在内部add
的self
不是实际实例,而是类。 Which means self.mul
call there is equivalent to: 这意味着self.mul
调用等效于:
>>> MyClass.mul(1, 2)
...
TypeError: mul() missing 1 required positional argument: 'y'
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.