【问题标题】:__str__ wrapper in custom Exception自定义异常中的 __str__ 包装器
【发布时间】:2022-01-03 16:48:54
【问题描述】:

为什么下面的代码打印的是error msg而不是ABC\nerror msg

class CustomException(Exception):
    """ABC"""
    def __init__(self, *args):
        super().__init__(*args)
        self.__str__ = self._wrapper(self.__str__)
    def _wrapper(self, f):
        def _inner(*args, **kwargs):
            return self.__doc__ + '\n' + f(*args, **kwargs)
        return _inner

print(CustomException('error msg'))

【问题讨论】:

  • __str__ 在类 CustomException 上调用,而不是在单个对象上调用。您只是在单个对象 self 上更改 __str__
  • 您是否有理由尝试对__str__ 进行包装和替换,而不是仅仅重新实现__str__ 方法 并调用super().__str__()
  • @khelwood 是的,我完全错过了,感谢您的提示。宫城先生,不,除了把事情复杂化之外没有别的原因 :) 我会按照你的方式做,也谢谢!
  • @khelwood __str__ 是在 CustomException 类上调用的,而不是在单个对象上调用的。 我有点困惑。 __str__ 不是实例方法吗?
  • @TheScore 它是一个实例方法,因为实例是它的第一个参数;但它是在类上查找的,并调用将实例作为第一个参数传递。通常这是调用魔法方法的方式。

标签: python python-3.x exception decorator wrapper


【解决方案1】:

暂时忘记 Exception 类。考虑一下:

class A:
    def __str__(self):
        return 'A'

obj = A()
print(obj)
obj.__str__ = lambda x: 'B'
print(obj)
A.__str__ = lambda x: 'B'
print(obj)

输出:

A
A   # Not B !
B

Dunder 方法的查找在 Python 中有所不同。来自docs

对于自定义类,特殊方法的隐式调用仅 如果在对象的类型上定义,则保证正常工作,而不是在 对象的实例字典。

你想要达到的是:

class CustomException(Exception):
    """ABC"""

    def __init__(self, *args):
        super().__init__(*args)

    def __str__(self):
        return self.__doc__ + '\n' + super().__str__()


print(CustomException('error msg'))

输出:

ABC
error msg

【讨论】:

    【解决方案2】:

    由特殊方法支持的操作通常将特殊方法显式地查找为适当的方法,而不仅仅是可调用的属性。具体来说,解释器不是self.__str__,而是大致查看type(self).__str__.__get__(self, type(self))——即a descriptor __str__ on the class to be bound with the instance.。为了覆盖一个特殊的方法,因此需要覆盖类的描述符而不是实例的属性。

    这可以通过以下方式完成:a) 将特殊方法声明为一个槽,它处理 type(self).__str__ 部分,b) 分配一个函数,它处理 __get__(self, type(self)) 部分。

    class CustomException(Exception):
        """ABC"""
        __slots__ = ("__str__",)  # <<< magic
    
        def __init__(self, *args):
            super().__init__(*args)
            # vvv self.__str__ is the class' slot
            self.__str__ = self._wrapper(super().__str__)
            #                            AAA real __str__ lives on the super class
        def _wrapper(self, f):
            def _inner(*args, **kwargs):
                return self.__doc__ + '\n' + f(*args, **kwargs)
            return _inner
    
    print(CustomException('error msg'))
    

    请注意,由于在这种情况下每个实例的行为都相同,因此建议在实践中定义一个新的 __str__ 方法。

    【讨论】:

      【解决方案3】:

      根据 @khelwood 在 cmets 中的回复,此代码按预期工作:

      class CustomException(Exception):
          """ABC"""
          def __init__(self, *args):
              super().__init__(*args)
              self.__class__.__str__ = self._wrapper(self.__str__)
          def _wrapper(self, f):
              def _inner(*args, **kwargs):
                  return self.__doc__ + '\n' + f()
              return _inner
      

      但我最终得到了这个等价物 (@MisterMiyagi):

      class CustomException(Exception):
          """ABC"""
          def __str__(self):
              return self.__doc__ + '\n' + super().__str__()
      

      【讨论】:

      • 第一个示例并没有真正按预期工作,因为您创建的每个CustomException 实例都会改变__str__所有 个实例的工作方式。
      • @chepner 如何解决这个问题?
      • 通过正确定义一个名为__str__的实例方法,而不是创建一个名为__str__的实例属性。
      • 如果你也想让第一个例子工作(我不推荐),你应该把self._wrapper(self.__str__)改为self._wrapper(super().__str__),否则你创建的CustomException('error msg')对象越多你得到的越多ABC 在输出中。测试一下
      猜你喜欢
      • 2012-10-17
      • 1970-01-01
      • 2016-02-02
      • 2021-06-27
      • 1970-01-01
      • 2011-12-20
      • 2013-12-07
      • 2017-07-28
      • 1970-01-01
      相关资源
      最近更新 更多