簡體   English   中英

類裝飾器問題,或者:Python如何區分靜態方法和方法?

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

(我需要一個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

因此,當裝飾的add變成綁定方法時, mul保持普通功能。 裝飾工作正常。

但是,當我現在更改方法以使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綁定到哪個對象?
  3. Python如何在內部將普通方法與@classmethod@staticmethod
  4. types.MethodType到底是什么意思?
  5. 我要用什么來分別獲取普通方法,類方法或靜態方法?
  6. 我在哪里可以找到所有這些文檔?
  7. 與CPython實現細節相對應,哪個答案與Python屬性有關?

問題是您不應該使用綁定方法替換函數 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__方法。

我在哪里可以找到所有這些文檔?

描述符操作指南是一個不錯的起點。 它描述了描述符協議,以及諸如屬性和方法之類的東西如何使用它的示例。

添加到@chepner的出色答案中並回答您的觀點4。

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

這意味着現在內部addself不是實際實例,而是類。 這意味着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