我试图在子类中使用super
,该子类使用类装饰器包装在另一个类中:
def class_decorator(cls):
class WrapperClass(object):
def make_instance(self):
return cls()
return WrapperClass
class MyClass(object):
def say(self, x):
print(x)
@class_decorator
class MySubclass(MyClass):
def say(self, x):
super(MySubclass, self).say(x.upper())
但是,对super
的呼叫失败:
>>> MySubclass().make_instance().say('hello')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in say
TypeError: super(type, obj): obj must be an instance or subtype of type
问题在于,当调用say
时,MySubclass
不再引用原始类,而是引用装饰器的返回值。
一种可能的解决方案是在修饰之前存储MySubclass
的值:
class MySubclass(MyClass):
def say(self, x):
super(_MySubclass, self).say(x.upper())
_MySubclass = MySubclass
MySubclass = class_decorator(MySubclass)
这有效,但不直观,需要为每个装饰的子类重复。我正在寻找一种不需要为每个装饰的子类添加额外样板的方法-在一个地方添加更多代码(例如,装饰器)就可以了。
更新:在Python 3中这不是问题,因为您可以使用__class__
(或不带参数的super
变体),所以以下工作: / p>
@class_decorator
class MySubclass(MyClass):
def say(self, x):
super().say(x.upper())
不幸的是,我在该项目中使用Python 2.7。
答案 0 :(得分:2)
问题是您的装饰器返回的类不同于python(或使用您的代码的任何人)期望的类。 super
失灵只是许多不幸后果之一:
>>> isinstance(MySubclass().make_instance(), MySubclass)
False
>>> issubclass(MySubclass, MyClass)
False
>>> pickle.dumps(MySubclass().make_instance())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.MySubclass'>: it's not the same object as __main__.MySubclass
这就是为什么类装饰器应该修改而不是返回另一个类。正确的实现应如下所示:
def class_decorator(wrapped_cls):
@classmethod
def make_instance(cls):
return cls()
wrapped_cls.make_instance = make_instance
return wrapped_cls
现在super
,其他所有内容都将按预期工作:
>>> MySubclass().make_instance().say('hello')
HELLO
答案 1 :(得分:1)
发生此问题的原因是,在调用MySubclass.say()时,全局符号MySubclass不再引用代码中定义为“类MySubclass”的内容。它是WrapperClass的一个实例,与MySubclass无关。
如果您使用的是Python3,则可以通过不向'super'传递任何参数来解决此问题,如下所示:
super().say(x.upper())
我真的不知道为什么要使用您拥有的特定构造,但是对于MyClass的子类定义'say()'并在其中具有'say()'方法,这确实很奇怪。源代码必须最终以不具有该方法的东西结尾-这就是您的代码中的情况。
请注意,您可以更改class WrapperClass
行以使其可读
class WrapperClass(cls):
这将使包装器成为刚装饰的包装器的子类。这对您的super(SubClass,self)调用没有帮助-您仍然需要删除args(仅在Python3上可以),但是至少创建为x = MySubclass()的实例将具有“ say”方法乍一看,就像人们期望的那样。
编辑:我想出了一种解决方法,但是它看起来确实很奇怪,并且具有使“包装”类知道的缺点(并且变得依赖)这样,如果您移除装饰器,将使其不可用):
def class_decorator(cls):
class WrapperClass(object):
def make_instance(self):
i = cls()
i._wrapped = cls
return i
return WrapperClass
class MyClass(object):
def say(self, x):
print(x)
@class_decorator
class MySubclass(MyClass):
def say(self, x):
super(self._wrapped, self).say(x.upper())
# make_instance returns inst of the original class, non-decorated i = MySubclass().make_instance() i.say('hello')
本质上,_wrapped
将类引用保存为声明时的 ,这与使用常规的super(this_class_name,self)内置调用一致。