简体   繁体   English

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

[英]class decorator issue, or: How does Python discriminate methods from staticmethods?

(I need a Python 3 internals guru, please) (我需要一个Python 3内部专家。)

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: 我有很多问题:

  1. Where does this astonishing effect come from? 这种惊人的效果从何而来?
  2. What object is add bound to? add绑定到哪个对象?
  3. How does Python internally discriminate a normal method from a @classmethod or a @staticmethod ? Python如何在内部将普通方法与@classmethod@staticmethod
  4. What does types.MethodType really mean? types.MethodType到底是什么意思?
  5. What would I write in its place to get a normal method, class method, or static method, respectively? 我要用什么来分别获取普通方法,类方法或静态方法?
  6. Where would I have found the documentation of all this? 我在哪里可以找到所有这些文档?
  7. Which of the answers pertain to Python properties as opposed to CPython implementation details? 与CPython实现细节相对应,哪个答案与Python属性有关?

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. classmethodstaticmethod对象返回的对象具有与常规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. 这意味着现在内部addself不是实际实例,而是类。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM