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

2024-09-28 17:23:16 发布

您现在位置:Python中文网/ 问答频道 /正文

(我需要一位python3内部专家)

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

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上的cpython3.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实现细节有关?你知道吗

Tags: 方法selfaddreturndefmyclassfunctionmethod
2条回答

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

解决一些其他问题。。。你知道吗

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是一个很好的起点。它描述了描述符协议,以及属性和方法如何使用它的示例。你知道吗

补充@chepner的伟大答案,回答你的第4点。你知道吗

What does types.MethodType really mean?

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

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

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'>

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

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

相关问题 更多 >