在Python3中,用类装饰器包装构造函数的正确方法,使其也适用于不可变类型
我试图定义一个类装饰器,它(除其他外)用一些自定义代码包装构造函数 我使用的是类修饰符,而不是继承,因为我希望修饰符适用于类层次结构中的多个类,并且我希望为层次结构中的每个装饰类执行包装代码 我可以这样做:在Python3中,用类装饰器包装构造函数的正确方法,使其也适用于不可变类型,python,python-3.x,decorator,Python,Python 3.x,Decorator,我试图定义一个类装饰器,它(除其他外)用一些自定义代码包装构造函数 我使用的是类修饰符,而不是继承,因为我希望修饰符适用于类层次结构中的多个类,并且我希望为层次结构中的每个装饰类执行包装代码 我可以这样做: def decorate(klass): def Dec(klass): def __init__(self,*a,**k): # wrapping code here super().__init__(*a,**k)
def decorate(klass):
def Dec(klass):
def __init__(self,*a,**k):
# wrapping code here
super().__init__(*a,**k)
# wrapping code here
return Dec
对于简单的测试用例,它工作得很好。但是,我担心用另一个类替换这个类可能会导致微妙的破坏(例如,如果一个装饰过的类决定做一些神秘的东西来引用它自己)。此外,它还破坏了类的良好默认repr字符串(它显示为“decoration..Dec”,而不是klass最初的名称)
我试着改变课堂本身:
def decorate(klass):
old_init = klass.__init__
def new_init(self,*a,**k):
# wrapper code here
old_init(self,*a,**k)
# wrapper code here
klass.__init__ = new_init
return klass
这样,它就维护了正确的类名和所有内容,只要我的构造函数不带任何参数,它就可以正常工作。但是,当将其应用于例如str
之类的类型时,它会中断:
@decorate
class S(str):
pass
>>> s = S('foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "decorate.py", line 13, in new_init
old_init(self,*a,**k)
TypeError: object.__init__() takes no parameters
然后
>>条=条(1)
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
文件“decoration.py”,第12行,在new\u new中
i=旧的初始值(cls,*a,**k)
文件“decoration.py”,第12行,在new\u new中
i=旧的初始值(cls,*a,**k)
TypeError:对象()不接受任何参数
版本更改\uuuu init\uuuu
如何可能失败?用另一个最通用的签名类型(*a,**k)替换一个函数并将调用代理给原始签名类型,怎么可能导致失败
为什么object.init似乎违反了至少接受一个位置参数的约定
如何使其在两种情况下都有效?我不能同时覆盖\uuuuu init\uuuu
和\uuuu new\uuuuu
来扩展相同的行为;应该使用哪个标准来动态地知道哪一个是正确的钩子
这真的应该以完全不同的方式实现吗,例如使用元类?不可变类型没有
\uuuu init\uuuu
;他们使用\uuuu new\uuuu
来生成不可变的实例。另一个问题不能完全回答这个问题。它只显示如何处理一个特定类(str),而不是如何编写在这两种情况下都正确的通用代码。它也没有解释在一个愚蠢的调用的一般代理上崩溃的反直觉行为;它没有说明是否应该/如何用一种完全不同的技术来实现,比如元类;它只是指出,在这里不能将可变基类和不可变基类一视同仁。您需要检测是否使用了\uuuuu init\uuuu
或\uuuu new\uuuuu
,并以此为基础进行装饰。哦,好吧,好吧。有没有一种干净的方法可以做到这一点?首先,决定是否真的需要支持不可变类型。为什么需要子类str
并在该子类上使用decorator?你是在试图支持你能想象的一切,还是在解决一个特定的问题?
def decorate(klass):
old_new = klass.__new__
def new_new(cls,*a,**k):
# wrapper code
i = old_init(cls,*a,**k)
# wrapper code
return i
klass.__new__ = new_new
return klass
@decorate
class Foo:
pass
@decorate
class Bar(Foo):
def __init__(self, x):
self.x = x
>>> bar = Bar(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "decorate.py", line 12, in new_new
i = old_init(cls,*a,**k)
File "decorate.py", line 12, in new_new
i = old_init(cls,*a,**k)
TypeError: object() takes no parameters