[英]class decorator issue, or: How does Python discriminate methods from staticmethods?
我有一個類修飾器 ,可以修飾某些功能,但不能修飾其他功能。 簡化示例:
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
。
我有很多問題:
add
綁定到哪個對象? @classmethod
或@staticmethod
? types.MethodType
到底是什么意思? 問題是您不應該使用綁定方法替換函數 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?
classmethod
和staticmethod
對象返回的對象具有與常規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'>
這意味着現在內部add
的self
不是實際實例,而是類。 這意味着self.mul
調用等效於:
>>> MyClass.mul(1, 2)
...
TypeError: mul() missing 1 required positional argument: 'y'
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.