清理引发的自定义异常消息中的内部路径和堆栈级别
Cleaning up interal path and stack levels in CustomException messages raised
既然我们要引发,除了 CustomException,我必须
学习新的东西来处理一个不存在的堆栈跟踪
raised except but as the exception that will be raised, 如果那使得
感觉。我只想摆脱 CustomException 的内部和
handler raiser 信息,只显示相关信息
给调用引发异常的处理程序的调用者。
我在清理自定义异常堆栈时遇到了一些困难
痕迹。因为此自定义异常会提供早期错字
和不正确的编码,我想清理它的消息和堆栈跟踪
不包括对内部模块路径和函数/方法的引用
水平。铁。而不是显示“变量期望 types.List[int]”,
我想显示“变量期望 List[int]。”。但那个特别的
增强不是我正在努力的。
我正在努力解决并寻求帮助的清理增强功能是
这个:而不是显示:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<cwd>/fibonacci.py", line 67, in fib
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
my_custom_modules.my_custom_exceptions.argumenterror.ArgumentError: index expects (<class 'int'>, typing.List[int],
typing.Tuple[int, int]) but found (0, 1, 2)
我希望它能更优雅地显示:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<cwd>/fibonacci.py", line 67, in fib
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0, 1, 2)
请注意,模块结构已缩减为只有异常 class 名称。
所以我减少并简化了代码,以便更容易清除,但为了说明问题,我仍然必须保留目录结构。
这里有 3 个文件的链接,1 个是本文,另外 2 个是下面显示的代码部分。
自定义异常代码:
#./my_custom_modules/my_custom_exceptions/argumenterror.py
from types import GenericAlias
class ArgumentError(ValueError):
'''
A substitution for ValueError specific for function and method
argument variable annotations which reduces the need for
repetitive validation code and message specing.
Parameters:
===========
name (:str)
The guilty variable argument name.
expects (:type, Generic, [type, Generic])
Annotations for the expected guilty variable value.
found (:Any)
The actual value of the guilty variable is question.
*specs (:*Any)
addition line specs.
**variables (:**Any)
additional variables to make available to the specs.
'''
MessageSpec = "{name} expects {expects!r} but found {found!r}"
def __new__(cls, name, expects, found, *specs, **variables):
"see help(ArgumentError) for correct annotations."
return super().__new__(cls)
def __init__(self, name, expects, found, *specs, **variables):
"see help(ArgumentError) for correct annotations."
expects_ = self.__expects__(expects)
message = self.__message__(name=name,
expects=expects_,
found=found,
**variables)
if specs:
details = tuple(self.__detail__(spec,
name=name,
expects=expects_,
found=found,
**variables)
for spec in specs)
self.__tbinit__(message, details)
else:
self.__tbinit__(message)
def __expects__(self, expects, _depth=0):
'''
internal expects formatting method.
strip "typing." and ("<class ", "'>"), and other extreme
details to keep message sweeter. oh well, next version.
for now let's keep it simple and easily readable.
'''
return expects
def __message__(self, **variables):
"internal message formatting method"
return self.MessageSpec.format(**variables)
def __detail__(self, spec, **variables):
"internal extra message lines formatting method"
return spec.format(**variables)
def __tbinit__(self, *lines):
"internal preprocessor to allow stack and message cleanup"
super().__init__(*lines)
使用模块代码:
'''
./fibonacci.py
A fibonacci sequence generator, mostly for annotation demonstration
purposes. Includes a single function fib. See function fib for usage
documentation.
Examples:
=========
from fibonacci import fib
fib(3) # -> 2
fib(-4) # -> -3
fib(-5) # -> 5
fib((-6, 6)) # -> (-8, 5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8)
fib([-7]) # -> (13, 13)
fib([-8, 8]) # -> (-21, 21)
fib([9, -10, 11]) # -> (34, -55, 89)
raises ArgumentError:
=====================
fib(9, -10)
#ArgumentError: cache expects list[int] but found -10
fib(())
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found ()
fib((0,))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0,)
fib((0,1,2))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0, 1, 2)
'''
from typing import List, Tuple
from my_custom_modules.my_custom_exceptions.argumenterror \
import ArgumentError
def fib(index:[int, Tuple[int,int, List[int]]],
cache:List[int]=[0, 1]):
'''
Returns the nth(index) or sequence of fibonacci number(s).
Parameters:
===========
index :(int | tuple[int, int] | list[*int])
The index or index range (inclusive) of fibonacci number(s)
to return.
cache :(list[int])
For caching purposes only, not for use as a parameter,
but you can always use it to force regeneration but
just be sure you use [0, 1]. Other values would render a
custom sequence and may not handle negative indexes
correctly. It's not a global variable simply to help
support the example. Yeah a bit OCD!
'''
if not (isinstance(index, int)
or (isinstance(index, list)
and all(isinstance(i, int) for i in index))
or (isinstance(index, tuple)
and len(index) == 2
and all(isinstance(i, int) for i in index))):
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
index)
if not (isinstance(cache, list)
and len(cache) >= 2
and all(isinstance(i, int) for i in cache)):
raise ArgumentError("cache", list, cache)
single = isinstance(index, int)
m = abs(index) if single else max(abs(v) for v in index)
while m >= len(cache):
cache.append(sum(cache[-2:]))
if single:
return cache[abs(index)] if index >= 0 or index % 2 else \
-cache[-index]
if isinstance(index, list):
return tuple(cache[abs(i)]
if i >= 0 or i % 2 else
-cache[-i]
for i in index)
return tuple(cache[abs(i)]
if i >= 0 or i % 2 else
-cache[abs(i)]
for i in range(index[0], index[1] + 1))
最后是测试用例代码:
from fibonacci import fib
fib(3) # -> 2
fib(-4) # -> -3
fib(-5) # -> 5
fib((-6, 6)) # -> (-8, 5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8)
fib([-7]) # -> (13, 13)
fib([-8, 8]) # -> (-21, 21)
fib([9, -10, 11]) # -> (34, -55, 89)
fib(9, -10)
#ArgumentError: cache expects list[int] but found -10
fib(())
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found ()
fib((0,))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0,)
fib((0,1,2))
#ArgumentError: index expects (int, list[int], tuple[int, int])
but found (0, 1, 2)
您将无法隐藏自定义异常的完整名称,要么为此使用内置 TypeError
,要么您将不得不使用长名称,不幸的是,除了使用内置 TypeError
:
def raise_wrong_type_exception(name, expects, found):
raise TypeError(f"{name} expects {repr(expects)} but found {repr(found)}")
通过将错误消息作为字符串传递给异常,您可以将以上内容用于任何内置异常。现在在要引发自定义异常的地方使用上面的函数
好吧,我想我的野心太大了,这甚至不是一个好主意。所以我缩减到我想要完成的最低要求。基本上我发现自己花了太多时间写论证检查,这让我放慢了速度,有时甚至让我注意力不集中。所以,我重新考虑并提出了这个简单的解决方案。
# ./expects.py
from typing import *
from collections import abc as cabc
NoneType = type(None)
def _expects(typing, depth=None, _depth=0):
if depth is not None and _depth >= depth:
return "..."
if typing is type(None):
return "None"
if isinstance(typing, type):
return typing.__name__
origin = get_origin(typing)
sep, args = ",", None
if origin:
args = get_args(typing)
name = origin.__name__ if isinstance(origin, type) else \
origin._name
if typing._inst:
sep = '|'
elif isinstance(typing, cabc.Sequence):
name, sep, args = "", "|", typing
elif callable(typing):
name = typing.__name__
else:
name = repr(typing)
if args:
items = sep.join(_expects(e, depth, _depth+1) for e in args) \
if depth is None or _depth+1 < depth else \
"..."
return "{:}[{:}]".format(name, items)
return name
__EXPECTS_CACHE__ = {}
def expects(method, name, found, depth=None, cache=True):
typing = get_type_hints(method)[name]
hashkey = (tuple(typing) if isinstance(typing, list) else
typing, depth) # because list is unhashable
expects = None
if cache:
try:
expects = __EXPECTS_CACHE__[hashkey]
except KeyError:
pass
elif cache is None:
__EXPECTS_CACHE__.clear()
if expects is None:
expects = _expects(typing, depth)
if cache:
__EXPECTS_CACHE__[hashkey] = expects
return "{name} expects {expects} but found {found!r}" \
.format(name=name, expects=expects, found=found)
class ArgumentError(ValueError):
def __new__(cls, method, name, found, depth=None):
return super().__new__(cls)
def __init__(self, method, name, found, depth=None):
super().__init__(expects(method, name, found, depth))
用法很简单,我会稍微打磨和测试后把功能写出来。但基本上你只需将 3 个参数传递给 Argumenterror,它们是 , 和 ,它会创建一个很好的短信息异常。或者,您可以传递期望相同的参数来仅获取消息。短而甜美,相当轻盈。这是一个用法示例:
>>> from expects import *
>>> def foo(n:[int,Tuple[int,int]]):
... if not (isinstance(n, int) or (isinstance(n, tuple) and len(n) == 2)):
... raise ArgumentError(foo, "n", n)
...
>>> foo(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
expects.ArgumentError: n expects [int|tuple[int,int]] but found None
>>>
或者,我可以编写一个代码生成器来对参数检查/验证进行类型提示,这会很酷。但是,对参数检查进行动态提示只会耗尽代码并减慢代码速度,尤其是对于经常调用或在循环中调用的函数和方法。所以现在不在考虑范围之内。但是,是的,编写自定义检查的代码生成器会 运行 一次,然后生成一个 .py 文件或缓存它。也许我会在将来的某个时候尝试使用我在早期实现中学到的一些东西来实现它。
既然我们要引发,除了 CustomException,我必须 学习新的东西来处理一个不存在的堆栈跟踪 raised except but as the exception that will be raised, 如果那使得 感觉。我只想摆脱 CustomException 的内部和 handler raiser 信息,只显示相关信息 给调用引发异常的处理程序的调用者。
我在清理自定义异常堆栈时遇到了一些困难 痕迹。因为此自定义异常会提供早期错字 和不正确的编码,我想清理它的消息和堆栈跟踪 不包括对内部模块路径和函数/方法的引用 水平。铁。而不是显示“变量期望 types.List[int]”, 我想显示“变量期望 List[int]。”。但那个特别的 增强不是我正在努力的。 我正在努力解决并寻求帮助的清理增强功能是 这个:而不是显示:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<cwd>/fibonacci.py", line 67, in fib
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
my_custom_modules.my_custom_exceptions.argumenterror.ArgumentError: index expects (<class 'int'>, typing.List[int],
typing.Tuple[int, int]) but found (0, 1, 2)
我希望它能更优雅地显示:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<cwd>/fibonacci.py", line 67, in fib
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0, 1, 2)
请注意,模块结构已缩减为只有异常 class 名称。
所以我减少并简化了代码,以便更容易清除,但为了说明问题,我仍然必须保留目录结构。 这里有 3 个文件的链接,1 个是本文,另外 2 个是下面显示的代码部分。
自定义异常代码:
#./my_custom_modules/my_custom_exceptions/argumenterror.py
from types import GenericAlias
class ArgumentError(ValueError):
'''
A substitution for ValueError specific for function and method
argument variable annotations which reduces the need for
repetitive validation code and message specing.
Parameters:
===========
name (:str)
The guilty variable argument name.
expects (:type, Generic, [type, Generic])
Annotations for the expected guilty variable value.
found (:Any)
The actual value of the guilty variable is question.
*specs (:*Any)
addition line specs.
**variables (:**Any)
additional variables to make available to the specs.
'''
MessageSpec = "{name} expects {expects!r} but found {found!r}"
def __new__(cls, name, expects, found, *specs, **variables):
"see help(ArgumentError) for correct annotations."
return super().__new__(cls)
def __init__(self, name, expects, found, *specs, **variables):
"see help(ArgumentError) for correct annotations."
expects_ = self.__expects__(expects)
message = self.__message__(name=name,
expects=expects_,
found=found,
**variables)
if specs:
details = tuple(self.__detail__(spec,
name=name,
expects=expects_,
found=found,
**variables)
for spec in specs)
self.__tbinit__(message, details)
else:
self.__tbinit__(message)
def __expects__(self, expects, _depth=0):
'''
internal expects formatting method.
strip "typing." and ("<class ", "'>"), and other extreme
details to keep message sweeter. oh well, next version.
for now let's keep it simple and easily readable.
'''
return expects
def __message__(self, **variables):
"internal message formatting method"
return self.MessageSpec.format(**variables)
def __detail__(self, spec, **variables):
"internal extra message lines formatting method"
return spec.format(**variables)
def __tbinit__(self, *lines):
"internal preprocessor to allow stack and message cleanup"
super().__init__(*lines)
使用模块代码:
'''
./fibonacci.py
A fibonacci sequence generator, mostly for annotation demonstration
purposes. Includes a single function fib. See function fib for usage
documentation.
Examples:
=========
from fibonacci import fib
fib(3) # -> 2
fib(-4) # -> -3
fib(-5) # -> 5
fib((-6, 6)) # -> (-8, 5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8)
fib([-7]) # -> (13, 13)
fib([-8, 8]) # -> (-21, 21)
fib([9, -10, 11]) # -> (34, -55, 89)
raises ArgumentError:
=====================
fib(9, -10)
#ArgumentError: cache expects list[int] but found -10
fib(())
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found ()
fib((0,))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0,)
fib((0,1,2))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0, 1, 2)
'''
from typing import List, Tuple
from my_custom_modules.my_custom_exceptions.argumenterror \
import ArgumentError
def fib(index:[int, Tuple[int,int, List[int]]],
cache:List[int]=[0, 1]):
'''
Returns the nth(index) or sequence of fibonacci number(s).
Parameters:
===========
index :(int | tuple[int, int] | list[*int])
The index or index range (inclusive) of fibonacci number(s)
to return.
cache :(list[int])
For caching purposes only, not for use as a parameter,
but you can always use it to force regeneration but
just be sure you use [0, 1]. Other values would render a
custom sequence and may not handle negative indexes
correctly. It's not a global variable simply to help
support the example. Yeah a bit OCD!
'''
if not (isinstance(index, int)
or (isinstance(index, list)
and all(isinstance(i, int) for i in index))
or (isinstance(index, tuple)
and len(index) == 2
and all(isinstance(i, int) for i in index))):
raise ArgumentError("index", (int, List[int], Tuple[int,int]),
index)
if not (isinstance(cache, list)
and len(cache) >= 2
and all(isinstance(i, int) for i in cache)):
raise ArgumentError("cache", list, cache)
single = isinstance(index, int)
m = abs(index) if single else max(abs(v) for v in index)
while m >= len(cache):
cache.append(sum(cache[-2:]))
if single:
return cache[abs(index)] if index >= 0 or index % 2 else \
-cache[-index]
if isinstance(index, list):
return tuple(cache[abs(i)]
if i >= 0 or i % 2 else
-cache[-i]
for i in index)
return tuple(cache[abs(i)]
if i >= 0 or i % 2 else
-cache[abs(i)]
for i in range(index[0], index[1] + 1))
最后是测试用例代码:
from fibonacci import fib
fib(3) # -> 2
fib(-4) # -> -3
fib(-5) # -> 5
fib((-6, 6)) # -> (-8, 5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8)
fib([-7]) # -> (13, 13)
fib([-8, 8]) # -> (-21, 21)
fib([9, -10, 11]) # -> (34, -55, 89)
fib(9, -10)
#ArgumentError: cache expects list[int] but found -10
fib(())
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found ()
fib((0,))
#ArgumentError: index expects (int, list[int], tuple[int, int]) but found (0,)
fib((0,1,2))
#ArgumentError: index expects (int, list[int], tuple[int, int])
but found (0, 1, 2)
您将无法隐藏自定义异常的完整名称,要么为此使用内置 TypeError
,要么您将不得不使用长名称,不幸的是,除了使用内置 TypeError
:
def raise_wrong_type_exception(name, expects, found):
raise TypeError(f"{name} expects {repr(expects)} but found {repr(found)}")
通过将错误消息作为字符串传递给异常,您可以将以上内容用于任何内置异常。现在在要引发自定义异常的地方使用上面的函数
好吧,我想我的野心太大了,这甚至不是一个好主意。所以我缩减到我想要完成的最低要求。基本上我发现自己花了太多时间写论证检查,这让我放慢了速度,有时甚至让我注意力不集中。所以,我重新考虑并提出了这个简单的解决方案。
# ./expects.py
from typing import *
from collections import abc as cabc
NoneType = type(None)
def _expects(typing, depth=None, _depth=0):
if depth is not None and _depth >= depth:
return "..."
if typing is type(None):
return "None"
if isinstance(typing, type):
return typing.__name__
origin = get_origin(typing)
sep, args = ",", None
if origin:
args = get_args(typing)
name = origin.__name__ if isinstance(origin, type) else \
origin._name
if typing._inst:
sep = '|'
elif isinstance(typing, cabc.Sequence):
name, sep, args = "", "|", typing
elif callable(typing):
name = typing.__name__
else:
name = repr(typing)
if args:
items = sep.join(_expects(e, depth, _depth+1) for e in args) \
if depth is None or _depth+1 < depth else \
"..."
return "{:}[{:}]".format(name, items)
return name
__EXPECTS_CACHE__ = {}
def expects(method, name, found, depth=None, cache=True):
typing = get_type_hints(method)[name]
hashkey = (tuple(typing) if isinstance(typing, list) else
typing, depth) # because list is unhashable
expects = None
if cache:
try:
expects = __EXPECTS_CACHE__[hashkey]
except KeyError:
pass
elif cache is None:
__EXPECTS_CACHE__.clear()
if expects is None:
expects = _expects(typing, depth)
if cache:
__EXPECTS_CACHE__[hashkey] = expects
return "{name} expects {expects} but found {found!r}" \
.format(name=name, expects=expects, found=found)
class ArgumentError(ValueError):
def __new__(cls, method, name, found, depth=None):
return super().__new__(cls)
def __init__(self, method, name, found, depth=None):
super().__init__(expects(method, name, found, depth))
用法很简单,我会稍微打磨和测试后把功能写出来。但基本上你只需将 3 个参数传递给 Argumenterror,它们是 , 和 ,它会创建一个很好的短信息异常。或者,您可以传递期望相同的参数来仅获取消息。短而甜美,相当轻盈。这是一个用法示例:
>>> from expects import *
>>> def foo(n:[int,Tuple[int,int]]):
... if not (isinstance(n, int) or (isinstance(n, tuple) and len(n) == 2)):
... raise ArgumentError(foo, "n", n)
...
>>> foo(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in foo
expects.ArgumentError: n expects [int|tuple[int,int]] but found None
>>>
或者,我可以编写一个代码生成器来对参数检查/验证进行类型提示,这会很酷。但是,对参数检查进行动态提示只会耗尽代码并减慢代码速度,尤其是对于经常调用或在循环中调用的函数和方法。所以现在不在考虑范围之内。但是,是的,编写自定义检查的代码生成器会 运行 一次,然后生成一个 .py 文件或缓存它。也许我会在将来的某个时候尝试使用我在早期实现中学到的一些东西来实现它。