【问题标题】:Is it safe to use autocall __init__ in this way?以这种方式使用自动调用 __init__ 是否安全?
【发布时间】:2021-02-21 11:24:21
【问题描述】:

我想强迫孩子调用父构造函数并找到this answer,这似乎可以很好地完成工作。但是,我有点不确定我所做的是否安全。我有这个:

# Copied from answer linked above
class meta(type):
    def __init__(cls,name,bases,dct):
        def auto__call__init__(self, *a, **kw):
            for base in cls.__bases__:
                base.__init__(self, *a, **kw)
            cls.__init__child_(self, *a, **kw)
        cls.__init__child_ = cls.__init__
        cls.__init__ = auto__call__init__

# My code

import unittest

class TestBase(unittest.TestCase, metaclass=meta):
    def __init__(self):
        self.testvar="Hello, World!"
        

class A(TestBase):
    def foo(self):
        # Inherited from TestBase
        print(self.testvar)
        # Inherited from unittest.TestCase
        self.assertEqual("Hello, World!", self.testvar)

A().foo()

这会打印“Hello, World!”正如预期的那样,我可以使用unittest.TestCase 中的assertEqual,但我有一种感觉,我可能在这里如履薄冰。这样做安全吗?它会以任何方式与unittest 发生冲突吗?

在原帖中,TestBase 没有继承自 unittest.TestCase。这就是区别。

【问题讨论】:

  • unittest 已经有关于自动运行类(以及它们的 init 方法)的特殊规则 - 所以要小心。您是否需要将其用于实际的“统一”事物或一般课程?
  • @jsbueno 它用于单元测试,但我将根据下面的答案跳过这个想法。

标签: python python-3.x inheritance metaclass


【解决方案1】:

无论如何,这里什么都没有发生,如果你真的重写了方法,你只需要委托给父方法,如果你不这样做,那么原始方法不会被隐藏并且会被正常调用。

我强烈建议不要这样做,特别是对于单元测试,因为 __init__ 实际上在那里没有任何用处:unittest 只调用 __init__ 根据它找到的方法名称来初始化测试用例,初始化你想要的钩子是setUp(和tearDown虽然addCleanup通常更安全、更可靠)。您可以在测试类中定义辅助方法,但您不应该自己实例化测试用例。

加上unittest 不是很好,它相当冗长,API 表面很大,基于类的设计阻碍了模块化(可能有点出乎意料)。我强烈建议您去pytest看看。

【讨论】:

    【解决方案2】:

    除了单元测试(它干预了一些自动运行类本身的规则),这段代码搞得一团糟。

    它将以难以确定的顺序多次运行类的__init__ 方法。

    当我添加一个中间类时,您的代码会发生以下情况,实际协作 __init__ 调用 super()(并取出 unittest 以保持理智):

    
    class meta(type):
        def __init__(cls,name,bases,dct):
            def auto__call__init__(self, *a, **kw):
                for base in cls.__bases__:
                    base.__init__(self, *a, **kw)
                cls.__init__child_(self, *a, **kw)
            cls.__init__child_ = cls.__init__
            cls.__init__ = auto__call__init__
    
    # My code
    
    
    
    class TestBase(metaclass=meta):
        def __init__(self):
            print("base")
            self.testvar="hello"
    
    class Middle(TestBase):
        def __init__(self):
            print("middle")
            super().__init__()
    
    class A(Middle):
    
        def foo(self):
            # Inherited from TestBase
            print(self.testvar)
            # Inherited from unittest.TestCase
    
            assert self.testvar == "hello"
    
    A().foo()
    

    将打印:

    base
    middle
    base
    base
    middle
    base
    hello
    

    正确执行此操作需要元类: 检查正在创建的类是否有自己的__init____mro__ 中的每个__init__(不是__bases__,这些只是直系祖先)。对于每个__init__ 方法,如果它还不是包装器,则将其包装(与应用装饰器并替换原始方法相同),这将使其在运行后在实例上标记一个特殊标志,如果它已经被实例调用,并且(相同的包装器) - 在其出口处调用 mro 中的下一个 __init__

    这可以确保链中的所有__init__ 都运行,即使其中一个或多个没有super() 调用 - 但也有缺点:超类__init__ 只会在结束时调用孩子的跑步 - 一个 __init__ 方法将变得特殊,因此它不能作为普通方法手动调用(尽管很少使用 - 但是,在这方面它们会变得“特殊”)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-01
      • 2011-10-15
      • 2021-03-05
      • 1970-01-01
      • 2012-11-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多