修改泛型类型的丰富比较TypeError异常消息

问题描述 投票:0回答:1

我有以下代码,我创建了一个支持比较的泛型类型。当我比较不同类型时,代码会产生异常(如预期)。但是,我想修改异常的消息以使其更加透明。

这是目前的代码。

import abc
import typing

class Comparable(typing.Protocol):
    """A simple protocol to signal to TypeVar that each 
        value will have a less than (lt) dunder available."""

    @abc.abstractmethod
    def __lt__(self, other: typing.Any, /) -> bool:
        raise NotImplementedError

# each value that will be assigned to T will be 'Comparable', 
# i.e., meets Comparable's interface
T = typing.TypeVar("T", bound=Comparable)


class Node(typing.Generic[T]):
    """Acts as a wrapper around any value.
    This is to show in code the issue I am trying to fix"""

    def __init__(self, value: T) -> None:
        self.value = value

    def __lt__(self, __other: typing.Union[T, Node[T]]) -> bool:
        """Implements support for the '<' operator"""
        try:
            if isinstance(__other, Node):
                return self.value < __other.value
            return self.value < __other
        except TypeError:
            return NotImplemented

上面的代码按预期工作,MyPy 很高兴。当使用某个值创建

Node
的实例时,会推断类型,并且
Node[type]
可用于注释,如预期的那样。

这是使用

Node
的一些示例以及我面临的问题。

value = Node(1)  # value: Node[int] = Node(1) -- value has a type of Node[int]
value2 = Node(2)  # likewise value2 has a type of Node[int]

# Example 1
print(
    value < 1
)  # -> False; the less than (lt) dunder can deal with Node[int] < int. 
# As you recall, __other is meant to accept T and Node[T]. 
# In this case, __other is 1, an int which is T.

# Example 2
print(
    value < value2
)  # -> True; the less than (lt) dunder should be able to deal with 
# Node[int] < Node[int] as __other would be Node[T]


# consider this
print(
    value < "0"
)  # As expected, this will fail because we cannot compare int and str; 
# likewise, we can't compare Node[int] with Node[str].
# Yields; <<Exeption>> 
# TypeError: '<' not supported between instances of 'Node' and 'str'

我不确定这是否可能;但是,我想修改以下异常的输出以使其打印:

TypeError: '<' not supported between instances of 'Node[int]' and 'str'

因为从技术上讲,

<
Node
str

之间得到支持。
python generics python-typing mypy
1个回答
1
投票

如果您想要获得“完全相同”的错误消息,但添加了类型参数,那么您恐怕不走运。当丰富的比较方法返回 NotImplemented

 时,处理收到的错误消息的逻辑是 Python 本身的实现细节。例如,您可以看到错误消息是如何在 
do_richcompare
 的 CPython 3.11.1 实现中形成的。

您可以获得的最接近的是修改错误消息并手动重新引发

TypeError

,但它将包含整个堆栈跟踪,包括来自 
__lt__
 方法的行。

这是一个完整的工作示例:

from __future__ import annotations from typing import Any, Generic, Protocol, TypeVar, Union, cast class Comparable(Protocol): def __lt__(self, other: Any, /) -> bool: ... T = TypeVar("T", bound=Comparable) class Node(Generic[T]): def __init__(self, value: T) -> None: self.value = value def __lt__(self, __other: Union[T, Node[T]]) -> bool: try: if isinstance(__other, Node): return self.value < __other.value return self.value < __other except TypeError as exc: # Modify exception message to to specify Node[T] # and raise the TypeError manually cls_name = self.value.__class__.__name__ msg = cast(str, exc.args[0]) msg = msg.replace(f"{cls_name}", f"Node[{cls_name}]", 1) if isinstance(__other, Node): cls_name = __other.value.__class__.__name__ msg = msg.replace( f"and '{cls_name}'", f"and 'Node[{cls_name}]'", ) exc.args = (msg, ) + exc.args[1:] raise exc def test() -> None: print(Node(1) < 1) print(Node(1) < Node(2)) # Node(1) < "0" # Node(1) < Node("0") if __name__ == "__main__": test()
这样的输出还是

False

 \ 
True
.

如果你取消注释

Node(1) < "0"

,你会得到这样的结果:

Traceback (most recent call last): File "[...].py", line 47, in <module> test() File "[...].py", line 42, in test Node(1) < "0" File "[...].py", line 36, in __lt__ raise exc from None File "[...].py", line 22, in __lt__ return self.value < __other TypeError: '<' not supported between instances of 'Node[int]' and 'str'
如果您取消注释 

Node(1) < Node("0")

,您将得到:

Traceback (most recent call last): File "[...].py", line 47, in <module> test() File "[...].py", line 43, in test Node(1) < Node("0") File "[...].py", line 36, in __lt__ raise exc from None File "[...].py", line 21, in __lt__ return self.value < __other.value TypeError: '<' not supported between instances of 'Node[int]' and 'Node[str]'
我想,如果您不是修改现有异常实例上的消息,而是引发了一个全新的 

TypeError

 实例并添加了 
from None
,您可以切断堆栈跟踪消息中的一个额外步骤,但这也可能会花费在某些情况下,您可以了解一些可能有用的额外异常详细信息。


在一个不相关的注释中,不需要在

abc.abstractmethod 上使用

Comparable.__lt__
,因为 
Protocol
 可能永远不会被实例化。该方法也不需要主体。

© www.soinside.com 2019 - 2024. All rights reserved.