python - 类装饰器问题,或: How does Python discriminate methods from staticmethods?

标签 python python-3.x decorator

(我需要一位 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 方法,以在运行之前打印“I'mdecorated”。

我们将把它应用到这个类:

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 则变成了一个绑定(bind)方法。装饰工作正常。

但是当我现在更改方法,使 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 绑定(bind)到什么对象?
  3. Python 在内部如何区分普通方法和 @classmethod@staticmethod
  4. types.MethodType 的真正含义是什么?
  5. 我应该在其位置编写什么来分别获取普通方法、类方法或静态方法?
  6. 我在哪里可以找到所有这些的文档?
  7. 哪些答案与 Python 属性有关,而不是与 CPython 实现细节有关?

最佳答案

问题是您不应该用绑定(bind)方法替换函数 add。方法的工作方式是 function 对象有一个 __get__ 方法,在实例方法的情况下,该方法返回一个绑定(bind)方法,供您在提供的参数上调用。也就是说,给定

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

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

How does Python internally discriminate a normal method from a @classmethod or a @staticmethod?

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

Where would I have found the documentation of all this?

Descriptor How-to Guide是一个很好的起点。它描述了描述符协议(protocol),以及属性和方法等如何使用它的示例。

关于python - 类装饰器问题,或: How does Python discriminate methods from staticmethods?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55207543/

相关文章:

python - 加载表单后如何运行函数 Kivy

具有修饰方法和 __del__ 定义的 Python 类不会被垃圾收集 : how do I uncouple the decorated method?

python - 提升.Python : how to get super() methods called?

python - 在python中将日期时间转换为时间

python-3.x - 在Python 3中播放具有开始时间和结束时间的MP3文件的最佳方式是如何

python - 如何使用xlrd在python中按列名读取Excel数据

python - 如何使用 joblib.Memory 缓存 Python 类的成员函数的输出

python - (半)自动生成函数的 argparsers

python - 索引错误: list index out of range for pyswip library

python - 将列表附加到字典以获取嵌套列表