Python 对派生类使用带类装饰器的超级方法时出现TypeError

Python 对派生类使用带类装饰器的超级方法时出现TypeError,python,decorator,python-decorators,Python,Decorator,Python Decorators,首先,为冗长的解释道歉 版本1-代码:类的类装饰器 版本#1-输出: Version#2-代码:派生类的类装饰器,它显式初始化基类。 版本2-输出: 版本3-代码:使用super() 版本#3-输出: A::uuu init_uuuu() A::______;呼叫__()) B::uuu init_uuuu() 回溯(最近一次呼叫最后一次): 文件“so.py”,第40行,在 main() 文件“so.py”,第36行,主 b=b() 调用中第10行的文件“so.py”__ 返回自我。_klas

首先,为冗长的解释道歉

版本1-代码:类的类装饰器 版本#1-输出: Version#2-代码:派生类的类装饰器,它显式初始化基类。 版本2-输出: 版本3-代码:使用super() 版本#3-输出:
A::uuu init_uuuu()
A::______;呼叫__())
B::uuu init_uuuu()
回溯(最近一次呼叫最后一次):
文件“so.py”,第40行,在
main()
文件“so.py”,第36行,主
b=b()
调用中第10行的文件“so.py”__
返回自我。_klass()
文件“so.py”,第32行,在_init中__
super(B,self)。\uuuuu init\uuuuuuu()
TypeError:必须是类型,而不是
A::_udel_uu()
问题: 第1版仅供参考。它解释了我试图做的事情,即捕获
B类
对象的
创建
删除

在版本2中,我对类B的对象尝试了相同的方法,该类对象派生自
Parent1
Parent2
,它们使用
Parent1.\uu init(self)
Parent2.\uu init(self)
显式初始化,工作正常


但是在版本3中,我用
super()
方法尝试了同样的方法。但是我得到了以下错误-
TypeError:必须是type,而不是
。我认为这是因为
MRO
链中所有父类的
\uuu init\uuu()方法没有正确调用-为什么?那么,我该如何解决这个问题呢

主要问题是
super
的第一个参数必须是实际的类,但在版本3中,在

super(B, self)
B
不是您创建的类。它是包装类的
实例。你需要像这样做

class _B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
        super(_B, self).__init__()
B = A(_B)
或者,不要在一个
A
实例中包装
B
,而是使用一个修饰符,用包装器替换
B
\uuuuu init\uuuuu
\uu del\uuuu
方法,而不替换整个
B

另外,如果要跟踪
B
实例的删除,则
a
上的
\uu del\uuu
方法将无法执行此操作。它将跟踪类的删除,而不是单个实例的删除


这里有一个decorator,它应该做您想做的事情,而不会因为用非类的东西包装类而产生很多问题:

def track_creation_and_deletion(klass):
    original_init = klass.__init__
    try:
        original_del = klass.__del__
    except AttributeError:
        def original_del(self):
            pass

    def new_init(self, *args, **kwargs):
        print '{}.{}.__init__'.format(klass.__module__, klass.__name__)
        return original_init(self, *args, **kwargs)
    def new_del(self):
        print '{}.{}.__del__'.format(klass.__module__, klass.__name__)
        return original_del(self)

    # functools.wraps doesn't play nicely with built-in methods,
    # so we handle it ourselves
    new_init.__name__ = '__init__'
    new_init.__doc__ = original_init.__doc__
    new_init.__module__ = klass.__module__
    new_init.__dict__.update(getattr(original_init, '__dict__', {}))

    new_del.__name__ = '__del__'
    new_del.__doc__ = original_del.__doc__
    new_del.__module__ = klass.__module__
    new_del.__dict__.update(getattr(original_del, '__dict__', {}))

    klass.__init__ = new_init
    klass.__del__ = new_del

    return klass
其中大约一半是错误处理和复制一些元数据,以使新方法看起来像是由调用方定义的。关键部分是我们定义了新的
\uuuu init\uuu
\uu del\uuu
方法来包装和替换类的旧方法。创建修饰类的实例时,我们给它的
\uuuu init\uuuu
方法将调用我们选择的日志代码。当装饰类的实例被垃圾收集时,我们给它的
\uu del\uu
方法将调用其他日志代码。因为我们没有替换类对象本身,所以在
super
调用中按名称引用类将引用它们需要引用的类


这种方法的一个限制是很难在
\uuuuu init\uuu
中检查实例本身,因为即使在包装的
\uuuu init\uu
返回后,它也可能没有完全构造。例如,如果我们试图
打印
实例,我们可能会触发子类的
\uu str\uu
方法,该方法依赖于尚未准备好的子类属性,导致AttributeError。

我花了一些时间来理解为什么分别使用
\uuuu call\uuu
\uu del\uu
方法很难捕获对象实例化和对象删除。以下是一些有用的参考资料

  • python列表mailer中也讨论了相同的主题-请查看此处的线程-
使用
\uu del\uuu
方法可以实现这一点,但它们有副作用!例如,@user2357112给出的答案是一个很好的技巧,但当我们进行循环引用时,它不起作用,因为垃圾收集器无法确定循环引用中要首先调用的
\uu del\uu
!然而,这可以通过使用弱ref来避免;但它仍然是一个黑客

其中一个建议是创建一个上下文管理器,它可以创建和删除特定类的对象

我有下面的例子,它模拟了这一点。请仔细查看
控制器
装饰器

class Parent1(object):
    def __init__(self):
        #print "Parent1::__init__()"
        super(Parent1, self).__init__()

class Parent2(object):
    def __init__(self):
        #print "Parent2::__init__()"
        super(Parent2, self).__init__()

def Controller(_cls):
    class Wrapper(_cls):
        def create(self, name):
            ret = _cls.create(self, name)
            print "Added to Database! :: ", name
            # Database add here!
            return ret

        def remove(self, name):
            ret = _cls.remove(self, name)
            print "Deleted from Database! :: ", name
            # Database delete here!
            return ret
    return Wrapper

@Controller
class Manager(object):
    def __init__(self):
        #print "Manager::__init__()"
        self._repo = []

    def create(self, name):
        a = A(name)
        print "Object created :: ", name
        self._repo.append(a)

    def remove(self, name):
        for i, item in enumerate(self._repo):
            if item._name == name:
                del self._repo[i]
                print "Object removed :: ", name

    def display(self):
        for item in self._repo:
            print item

class A(Parent1, Parent2):
    def __init__(self, name):
        #print "A::__init__()"
        self._name = name
        super(A, self).__init__()

    def __repr__(self):
        return self._name

def main():
    m1 = Manager()
    m1.create("apples")
    m1.create("oranges")
    m1.create("grapes")
    #m1.display()
    m1.remove("apples")
    #m1.display()

if __name__ == "__main__":
    main()
执行时,会产生以下结果:

Object created ::  apples
Added to Database! ::  apples
Object created ::  oranges
Added to Database! ::  oranges
Object created ::  grapes
Added to Database! ::  grapes
Object removed ::  apples
Deleted from Database! ::  apples

这是我能想出的解决问题的最安全的办法。欢迎您的建议

+1我正在考虑将
self.\u klass
传递给
\uuu init\uuu
调用:
self.\u klass)
,然后在
super
调用中使用它,而不是
B
。但是,这看起来更好。这里已经回答了:是亚历克斯·马泰利写的,你可以相信那个家伙!:)
A::__init__()
A::__call__()
B::__init__()
Traceback (most recent call last):
  File "so.py", line 40, in <module>
    main()
  File "so.py", line 36, in main
    b = B()
  File "so.py", line 10, in __call__
    return self._klass()
  File "so.py", line 32, in __init__
    super(B, self).__init__()
TypeError: must be type, not A
A::__del__()
super(B, self)
class _B(Parent1, Parent2):
    def __init__(self):
        print "B::__init__()"
        super(_B, self).__init__()
B = A(_B)
def track_creation_and_deletion(klass):
    original_init = klass.__init__
    try:
        original_del = klass.__del__
    except AttributeError:
        def original_del(self):
            pass

    def new_init(self, *args, **kwargs):
        print '{}.{}.__init__'.format(klass.__module__, klass.__name__)
        return original_init(self, *args, **kwargs)
    def new_del(self):
        print '{}.{}.__del__'.format(klass.__module__, klass.__name__)
        return original_del(self)

    # functools.wraps doesn't play nicely with built-in methods,
    # so we handle it ourselves
    new_init.__name__ = '__init__'
    new_init.__doc__ = original_init.__doc__
    new_init.__module__ = klass.__module__
    new_init.__dict__.update(getattr(original_init, '__dict__', {}))

    new_del.__name__ = '__del__'
    new_del.__doc__ = original_del.__doc__
    new_del.__module__ = klass.__module__
    new_del.__dict__.update(getattr(original_del, '__dict__', {}))

    klass.__init__ = new_init
    klass.__del__ = new_del

    return klass
class Parent1(object):
    def __init__(self):
        #print "Parent1::__init__()"
        super(Parent1, self).__init__()

class Parent2(object):
    def __init__(self):
        #print "Parent2::__init__()"
        super(Parent2, self).__init__()

def Controller(_cls):
    class Wrapper(_cls):
        def create(self, name):
            ret = _cls.create(self, name)
            print "Added to Database! :: ", name
            # Database add here!
            return ret

        def remove(self, name):
            ret = _cls.remove(self, name)
            print "Deleted from Database! :: ", name
            # Database delete here!
            return ret
    return Wrapper

@Controller
class Manager(object):
    def __init__(self):
        #print "Manager::__init__()"
        self._repo = []

    def create(self, name):
        a = A(name)
        print "Object created :: ", name
        self._repo.append(a)

    def remove(self, name):
        for i, item in enumerate(self._repo):
            if item._name == name:
                del self._repo[i]
                print "Object removed :: ", name

    def display(self):
        for item in self._repo:
            print item

class A(Parent1, Parent2):
    def __init__(self, name):
        #print "A::__init__()"
        self._name = name
        super(A, self).__init__()

    def __repr__(self):
        return self._name

def main():
    m1 = Manager()
    m1.create("apples")
    m1.create("oranges")
    m1.create("grapes")
    #m1.display()
    m1.remove("apples")
    #m1.display()

if __name__ == "__main__":
    main()
Object created ::  apples
Added to Database! ::  apples
Object created ::  oranges
Added to Database! ::  oranges
Object created ::  grapes
Added to Database! ::  grapes
Object removed ::  apples
Deleted from Database! ::  apples