类装饰问题,或者:Python如何区分方法和静态方法? (我需要一位Python 3内部专家)

类装饰问题,或者:Python如何区分方法和静态方法? (我需要一位Python 3内部专家),python,python-3.x,decorator,Python,Python 3.x,Decorator,我有一个类decorator,它修改一些函数,但不修改其他函数。 简化示例: 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)

我有一个类decorator,它修改一些函数,但不修改其他函数。 简化示例:

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 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'
MyClass.add:MyClass.mul:
3+4 = 7 3*4 = 12
MyClass=mydecorator(MyClass)
MyClass.add:MyClass.mul:
我被装饰了!
回溯(最近一次呼叫最后一次):
文件“tryout.py”,第34行,在
打印(“3+4=,MyClass().add(3,4),“3*4=,MyClass().mul(3,4),)
文件“tryout.py”,第16行,采用修饰的方法
返回myobj(*args,**kwargs)
文件“tryout.py”,第7行,添加
def add(self,x,y):z=self.mul(x,y);返回x+y#第2轮
TypeError:mul()缺少1个必需的位置参数:“y”
事实证明,
mul
(尽管它与以前一样!)现在被调用,就好像它是一个
@staticmethod
self
没有被传递一样

我有很多问题:

  • 这种惊人的效果是从哪里来的
  • add
    绑定到哪个对象
  • Python如何在内部区分普通方法与
    @classmethod
    @staticmethod
  • types.MethodType
    真正的意思是什么
  • 我应该在它的位置写些什么来分别获得普通方法、类方法或静态方法
  • 我在哪里可以找到所有这些文件
  • 以下哪一个答案与Python属性有关,而不是与CPython实现细节有关

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

    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)。\uuu dict\uuu['add']。\uu get\uuu(o,type(o))(3,5)

    您的装饰程序还应该简单地返回一个新函数,而不是
    方法
    对象,并让它的
    \uuuu get\uuu
    方法完成它的工作

    您的新装饰师,经过一些简化:

    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

    classmethod
    staticmethod
    对象返回的对象与常规的
    函数
    对象具有不同的
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu方法

    我在哪里可以找到所有这些文件


    这是一个很好的开始。它描述了描述符协议,以及属性和方法等如何使用它的示例。

    补充@chepner的伟大答案,并回答您的第4点

    types.MethodType真正的意思是什么

    这种特殊类型允许我们向已经创建的实例添加方法,在您的例子中没有实例,因此当您将
    myobj
    传递给它时,您基本上是将包装的
    \uuuuu self\uuuu
    设置到它的类中,而不是使用它的实例

    让我们来看一个更简单的类版本,没有decorator:

    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 = types.MethodType(ins.add.__func__, MyClass)
    
    现在将类传递给函数
    add
    ,而不是实例:

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

    请注意,这回答了问题1、3、6和(隐式)5。
    >>> ins = MyClass()
    >>> ins.add.__func__
    <function MyClass.add at 0x7f483050cf28>
    >>> ins.add.__self__
    <__main__.MyClass object at 0x7f48304ef390>
    
    >>> ins.add(1, 2)
    3
    >>> ins.add.__func__(ins.add.__self__, 1, 2)
    3
    
    >>> MyClass.add = types.MethodType(ins.add.__func__, MyClass)
    
    >>> ins = MyClass()
    >>> ins.add.__self__
    <class '__main__.MyClass'>
    
    >>> MyClass.mul(1, 2)
    ...
    TypeError: mul() missing 1 required positional argument: 'y'