Python 2中经过修饰的子类中的super()

时间:2018-08-15 14:13:00

标签: python decorator python-2.x

我试图在子类中使用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。

2 个答案:

答案 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)内置调用一致。