【问题标题】:class decorator issue, or: How does Python discriminate methods from staticmethods?类装饰器问题,或者:Python 如何区分方法和静态方法?
【发布时间】:2019-03-17 13:24:21
【问题描述】:

(我需要一位 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(虽然它和以前一样!)现在被调用就好像它是@staticmethodself 没有通过。

我有很多问题:

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

【问题讨论】:

    标签: python python-3.x decorator


    【解决方案1】:

    问题是你不应该用绑定方法替换函数 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__ 方法。

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

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

    【讨论】:

    • 请注意,这回答了问题 1、3、6 和(隐式)5。
    【解决方案2】:

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

    types.MethodType 的真正含义是什么?

    这种特殊类型允许我们向已经创建的实例添加方法,在您的情况下没有实例,因此当您将 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'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      • 2011-09-18
      • 2015-02-21
      • 2013-11-25
      • 2010-12-31
      • 1970-01-01
      • 2010-10-16
      相关资源
      最近更新 更多