【问题标题】:Catch exceptions in subclasses __init__ and exit捕获子类 __init__ 中的异常并退出
【发布时间】:2021-07-30 19:19:52
【问题描述】:

考虑下面的代码:

class Parent():
    
    def __init__(self, ...):
        # set up logging
        # ....


class Child(Parent):
    
    def __init__(self, ...):
        super(Child, self).__init__(...)

        print(1/0)


c = Child()

由于1/0,上述操作将失败。当__init__ 失败时,是否有可能在Parent 中执行一些逻辑(例如:我想记录一些东西)?也就是说,我想在 Child 类的 __init__ 中捕获异常,然后在整个实例被垃圾收集之前执行诸如日志记录之类的操作。

【问题讨论】:

  • super()调用返回后,Child类的构造函数完全独立于父类的构造函数。您将无法在 Child 类的构造函数中捕获错误,因为父构造函数已被调用(使用 super(...))并返回,因此它不再位于调用堆栈中。子构造函数中的错误将被抛出给调用者,这是初始化Child 类(在本例中为顶级)实例的代码。
  • 给孩子打扫卫生不是父母的工作。
  • Child__init__() 可以捕获异常并调用其Parent 的方法来处理它。
  • 您可能还在寻找Factory Pattern,您的工厂方法将能够在初始化实例时处理抛出的错误。

标签: python exception


【解决方案1】:

做到这一点的一种方法是用可以处理发生的任何异常的东西来装饰每个子类的__init__() 方法。您可以在每个子类中显式执行此操作,或者通过使用 Python 3.6 中添加的相对较新的 __init_subclass__() 方法定义元类自动执行此操作。

后者似乎是使其自动化的最简单方法,所以我将说明如何做到这一点:

from functools import wraps

class Parent:

    def __init__(self, *args, **kwargs):
        # set up logging...
        pass

    def logger(self, msg):
        print('logging msg:', msg)

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.__init__ = cls.log_exceptions(cls.__init__)  # Decorate subclass' __init__.

    @staticmethod
    def log_exceptions(method):
        """ Wrap method in an exception handler that will automatically log any
            exceptions that occur executing it.
        """
        @wraps(method)
        def wrapped(self, *args, **kwargs):
            try:
                return method(self, *args, **kwargs)
            except Exception as exc:
                self.logger(exc)
        return wrapped


class Child(Parent):

    def __init__(self, *args, **kwargs):
        print(f"{type(self).__name__}.__init__() called")
        super().__init__(*args, **kwargs)  # Base class initialization.
        # other initializations...
        print(1 / 0)


c = Child(1, 2, 3)

【讨论】:

    【解决方案2】:

    您必须使有问题的代码的执行由父级实际运行。一种方法是不在__init__ 本身中定义代码,而是在父级执行的挂钩中定义代码。 (dataclasses 模块做了类似的事情。)

    class Parent():
        
        def __init__(self, ...):
            # set up logging
            # ....
            
            try:
                self.post_init()
            except Exception:
                ...
    
        # Do-nothing implementation in case the child doesn't override it
        def post_init(self):
            pass
    
    class Child(Parent):
        
        def post_init(self):
            print(1/0)
        
    
    c = Child()
    

    【讨论】:

    • 我绝对不同意你的说法,即让有问题的代码由父母执行是一项严格的要求。事实上,我认为你提出的甚至可能是一种反模式。
    • 这只是许多框架空的同类代码的一个例子。孩子决定什么代码运行,但父母决定何时以及如何运行。这是 dataclass 装饰器提供的 __post_init__ 方法的几乎逐字复制。
    • 好吧,暗示它可能是一个反模式太严重了,我可以看到它在一些更大的框架的上下文中是如何有用的——抱歉——但我仍然坚持我的断言,即它是没有任何严格的要求,也没有您必须做的事情。
    【解决方案3】:

    这样做的一种方法是在您的父类中定义一个记录器方法,以便子类中的每个方法(包括__init__)都可以通过继承logger 方法来获取它。

    class Parent:
    
        def __init__(self, *args):
            print(args, 'Parent')
    
        def logger(self, msg):
            print('logging', msg)
    
    
    class Child(Parent):
    
        def __init__(self, *args):
            super().__init__(*args)
    
            # other initializations here
    
            # the code may cause problem
            try:
                print(1 / 0)
            except Exception as e:
                self.logger(e)
    
    
    c = Child(1, 2, 3)
    

    输出:

    (1, 2, 3) Parent
    logging division by zero
    

    如果需要,每个 child 类还可以覆盖 logger 方法并在父级的 logger 之外添加一些特定的内容。

    【讨论】:

    • @martineau 哦,当我写那句话时我在想什么... :) 谢谢更新。
    猜你喜欢
    • 2013-03-23
    • 1970-01-01
    • 2011-10-07
    • 1970-01-01
    • 2012-02-09
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多