我有以下代码,我创建了一个支持比较的泛型类型。当我比较不同类型时,代码会产生异常(如预期)。但是,我想修改异常的消息以使其更加透明。
这是目前的代码。
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
之间得到支持。
如果您想要获得“完全相同”的错误消息,但添加了类型参数,那么您恐怕不走运。当丰富的比较方法返回 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
可能永远不会被实例化。该方法也不需要主体。