类装饰器问题,或者:Python 如何区分方法和静态方法?



(请问我需要一个Python 3内部大师)

我有一个类装饰器,可以修改某些函数,但不能修改其他函数。 简化示例:

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"

因此,这将修改任何add方法以在运行之前打印"我已装饰"。

我们将它应用于此类:

class MyClass:
def add(self, x, y): return x + y
def mul(self, x, y): return x * y

它工作正常。我们做

#--- 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), )

并获取此输出(来自 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

因此,mul保持普通函数,而修饰的add则变为绑定方法。装饰工作正常。

但是当我现在更改方法以使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

输出变成这样:

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'

事实证明,mul(虽然和以前一样!)现在被当作一个@staticmethod来称呼:self没有通过。

我有很多问题:

  1. 这种惊人的效果从何而来?
  2. add绑定到什么对象?
  3. Python 如何在内部区分普通方法与@classmethod@staticmethod
  4. types.MethodType到底是什么意思?
  5. 我会写什么来分别得到普通方法、类方法或静态方法?
  6. 我在哪里可以找到所有这些的文档?
  7. 哪些答案与 Python 属性有关,而不是 CPython 实现细节?

问题是你不应该用绑定方法替换函数add。方法的工作方式是function对象具有__get__方法,对于实例方法,该方法返回一个绑定方法,以便在提供的参数上调用。也就是说,给定

class MyClass:
def add(self, x, y): 
return x + y
def mul(self, x, y):
return x * y
o = MyClass()

o.add(3,5)这样的调用相当于type(o).__dict__['add'].__get__(o, type(o))(3,5)

装饰器还应该简单地返回一个新函数,而不是method对象,并让它的__get__方法完成它的工作。

您的新装饰器,并进行了一些简化:

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

解决您的其他一些问题...

Python 如何在内部区分普通方法与@classmethod或@staticmethod?

classmethodstaticmethod对象返回具有与常规function对象不同的__get__方法的对象。

我在哪里可以找到所有这些的文档?

描述符操作指南是一个很好的起点。它描述了描述符协议,以及属性和方法等内容如何使用它的示例。

补充@chepner的精彩答案并回答您的第 4 点。

什么类型。方法类型真的意味着?

这种特殊类型允许我们将方法添加到已经创建的实例中,在您的情况下没有实例,因此当您myobj传递给它时,您基本上将包装器__self__设置为其类而不是使用它的实例。

让我们采用你的类的更简单版本,没有装饰器:

class MyClass:
def add(self, x, y):
z = self.mul(x, y);
return x + y
def mul(self, x, y): return x * y

现在:

>>> ins = MyClass()
>>> ins.add.__func__
<function MyClass.add at 0x7f483050cf28>
>>> ins.add.__self__
<__main__.MyClass object at 0x7f48304ef390>

如您所见,add是一个全新的对象,现在具有有关要调用哪个函数以及作为第一个参数传递什么的信息。

>>> ins.add(1, 2)
3
>>> ins.add.__func__(ins.add.__self__, 1, 2)
3

现在,当我们这样做时,您所做的:

>>> MyClass.add = types.MethodType(ins.add.__func__, MyClass)

现在,这会将类传递给函数add而不是实例:

>>> ins = MyClass()
>>> ins.add.__self__
<class '__main__.MyClass'>

这意味着现在add内部的self不是实际实例,而是类。这意味着那里self.mul调用等效于:

>>> MyClass.mul(1, 2)
...
TypeError: mul() missing 1 required positional argument: 'y'

相关内容

最新更新