超级不使用类装饰器?

时间:2017-04-27 13:38:09

标签: python inheritance python-decorators

让我们定义简单的类装饰器函数,它创建子类并仅将'Dec'添加到原始类名:

def decorate_class(klass):
    new_class = type(klass.__name__ + 'Dec', (klass,), {})
    return new_class

现在将它应用于一个简单的子类定义:

class Base(object):
    def __init__(self):
        print 'Base init'

@decorate_class
class MyClass(Base):
    def __init__(self):
        print 'MyClass init'
        super(MyClass, self).__init__()

现在,如果您尝试实例化装饰MyClass,它将以无限循环结束:

c = MyClass()
# ...
# File "test.py", line 40, in __init__
#   super(MyClass, self).__init__()
# RuntimeError: maximum recursion depth exceeded while calling a Python object

看来,super无法处理这种情况,也不会从继承链中跳过当前类。

问题是,如何使用super在类上正确使用类装饰器?

奖金问题,如何从super创建的代理对象中获取最终类? IE浏览器。从object表达式获取super(Base, self).__init__类,作为定义为__init__的已确定父类。

3 个答案:

答案 0 :(得分:2)

由于装饰器返回一个具有不同名称的全新类,因此该类MyClass对象甚至不存在。这不是类装饰器的用途。它们旨在为现有类添加其他功能,而不是直接将其替换为其他类。

如果您使用的是Python3,解决方案很简单 -

@decorate_class
class MyClass(Base):
    def __init__(self):
        print 'MyClass init'
        super().__init__()

否则,我怀疑有任何直接的解决方案,您只需要更改您的实施。当您重命名该类时,您需要使用更新的名称重写覆盖__init__

答案 1 :(得分:2)

如果您只是想更改班级的.__name__属性,请制作一个装饰者来做这件事。

from __future__ import print_function

def decorate_class(klass):
    klass.__name__ += 'Dec'
    return klass

class Base(object):
    def __init__(self):
        print('Base init')

@decorate_class
class MyClass(Base):
    def __init__(self):
        print('MyClass init')
        super(MyClass, self).__init__()

c = MyClass()
cls = c.__class__
print(cls, cls.__name__)

Python 2输出

MyClass init
Base init
<class '__main__.MyClassDec'> MyClassDec

Python 3输出

MyClass init
Base init
<class '__main__.MyClass'> MyClassDec

请注意cls的repr的差异。 (我不确定为什么你想改变一个班级的名字,但这听起来像是一个混乱的秘诀,但我想它还可以对于这个简单的例子)。

正如其他人所说,@decorator并不打算创建一个子类。您可以使用无{arg-less} super形式(即super().__init__())在Python 3中执行此操作。通过显式提供父类而不是使用super,您可以在Python 3和Python 2中使用它。

from __future__ import print_function

def decorate_class(klass):
    name = klass.__name__
    return type(name + 'Dec', (klass,), {})

class Base(object):
    def __init__(self):
        print('Base init')

@decorate_class
class MyClass(Base):
    def __init__(self):
        print('MyClass init')
        Base.__init__(self)

c = MyClass()
cls = c.__class__
print(cls, cls.__name__)

Python 2&amp; 3输出

MyClass init
Base init
<class '__main__.MyClassDec'> MyClassDec    

最后,如果我们只使用普通函数语法而不是decorate_class来调用@decorator,我们可以使用super

from __future__ import print_function

def decorate_class(klass):
    name = klass.__name__
    return type(name + 'Dec', (klass,), {})

class Base(object):
    def __init__(self):
        print('Base init')

class MyClass(Base):
    def __init__(self):
        print('MyClass init')
        super(MyClass, self).__init__()

MyClassDec = decorate_class(MyClass)
c = MyClassDec()
cls = c.__class__
print(cls, cls.__name__)

输出与上一版本相同。

答案 2 :(得分:0)

问题是你的装饰者创建了原始的子类。这意味着super(Myclass)现在指向......原始类本身!

我甚至无法解释super的0 arg形式是如何在Python 3中完成工作的,我在参考手册中找不到任何明确的内容。我假设它必须使用声明时使用它的类。但我无法想象在Python2中获得该结果的方法。

如果您希望能够在Python 2中的装饰类中使用super,则不应创建派生类,而应直接修改原始类。

例如,这是一个装饰器,它在调用任何方法之前和之后打印一行:

def decorate_class(klass):
    for name, method in klass.__dict__.iteritems(): # iterate the class attributes
        if isinstance(method, types.FunctionType):  # identify the methods
            def meth(*args, **kwargs):        # define a wrapper
                print "Before", name
                method(*args, **kwargs)
                print "After", name
            setattr(klass, name, meth)        # tell the class to use the wrapper
    return klass

根据您的示例,它会按预期显示:

>>> c = MyClass()
Before __init__
MyClass init
Base init
After __init__