自定义异常 - 捕获不继承自 BaseException 的类

Posted

技术标签:

【中文标题】自定义异常 - 捕获不继承自 BaseException 的类【英文标题】:Custom exceptions - catching classes that do not inherit from BaseException 【发布时间】:2021-11-14 08:41:55 【问题描述】:

我正在尝试编写一些自定义异常处理,但一直遇到“TypeError:不允许捕获不从 BaseException 继承的类”错误的问题。我有一个名为 NodeError 的基本异常类,它继承自 Exception。从那里,我有几个继承自 NodeError 的自定义异常。

web3 模块使用 requests 模块与节点通信。我的测试不断尝试从节点获取 tx 计数,在此过程中,我尝试通过禁用我的 NIC 来模拟中断。我尝试在 get_tx_count() 中捕获 requests.exceptions.ConnectionError 并引发我自己的异常。它似乎根据堆栈跟踪正确地命中了 NodeConnectionError 自定义异常,但随后又得到了另一个异常并抱怨捕获了不从 BaseException 继承的类。

不知道为什么它认为我没有从 BaseException 继承,但我觉得这与首先捕获请求异常有关。

堆栈跟踪:

Traceback (most recent call last):
  File "C:\Python39\lib\site-packages\urllib3\connection.py", line 169, in _new_conn
    conn = connection.create_connection(
  File "C:\Python39\lib\site-packages\urllib3\util\connection.py", line 73, in create_connection
    for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
  File "C:\Python39\lib\socket.py", line 953, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno 11001] getaddrinfo failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Python39\lib\site-packages\urllib3\connectionpool.py", line 699, in urlopen
    httplib_response = self._make_request(
  File "C:\Python39\lib\site-packages\urllib3\connectionpool.py", line 382, in _make_request
    self._validate_conn(conn)
  File "C:\Python39\lib\site-packages\urllib3\connectionpool.py", line 1010, in _validate_conn
    conn.connect()
  File "C:\Python39\lib\site-packages\urllib3\connection.py", line 353, in connect
    conn = self._new_conn()
  File "C:\Python39\lib\site-packages\urllib3\connection.py", line 181, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Python39\lib\site-packages\requests\adapters.py", line 439, in send
    resp = conn.urlopen(
  File "C:\Python39\lib\site-packages\urllib3\connectionpool.py", line 755, in urlopen
    retries = retries.increment(
  File "C:\Python39\lib\site-packages\urllib3\util\retry.py", line 574, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='matic-mainnet-full-rpc.bwarelabs.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\t\extest.py", line 87, in get_tx_count
    nonce = w3.eth.get_transaction_count(address)
  File "C:\Python39\lib\site-packages\web3\module.py", line 57, in caller
    result = w3.manager.request_blocking(method_str,
  File "C:\Python39\lib\site-packages\web3\manager.py", line 186, in request_blocking
    response = self._make_request(method, params)
  File "C:\Python39\lib\site-packages\web3\manager.py", line 147, in _make_request
    return request_func(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:\Python39\lib\site-packages\web3\middleware\formatting.py", line 76, in apply_formatters
    response = make_request(method, params)
  File "C:\Python39\lib\site-packages\web3\middleware\gas_price_strategy.py", line 84, in middleware
    return make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:\Python39\lib\site-packages\web3\middleware\formatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "C:\Python39\lib\site-packages\web3\middleware\attrdict.py", line 33, in middleware
    response = make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:\Python39\lib\site-packages\web3\middleware\formatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:\Python39\lib\site-packages\web3\middleware\formatting.py", line 76, in apply_formatters
    response = make_request(method, params)
  File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    return self.func(*args, **kwargs)
  File "C:\Python39\lib\site-packages\web3\middleware\formatting.py", line 74, in apply_formatters
    response = make_request(method, formatted_params)
  File "C:\Python39\lib\site-packages\web3\middleware\buffered_gas_estimate.py", line 40, in middleware
    return make_request(method, params)
  File "C:\Python39\lib\site-packages\web3\middleware\exception_retry_request.py", line 105, in middleware
    return make_request(method, params)
  File "C:\Python39\lib\site-packages\web3\providers\rpc.py", line 88, in make_request
    raw_response = make_post_request(
  File "C:\Python39\lib\site-packages\web3\_utils\request.py", line 48, in make_post_request
    response = session.post(endpoint_uri, data=data, *args, **kwargs)  # type: ignore
  File "C:\Python39\lib\site-packages\requests\sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "C:\Python39\lib\site-packages\requests\sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "C:\Python39\lib\site-packages\requests\sessions.py", line 655, in send
    r = adapter.send(request, **kwargs)
  File "C:\Python39\lib\site-packages\requests\adapters.py", line 516, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='matic-mainnet-full-rpc.bwarelabs.com', port=443): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x000001413971AB50>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\t\extest.py", line 114, in main
    print(get_tx_count(w3, address))
  File "C:\t\extest.py", line 90, in get_tx_count
    raise NodeConnectionError(w3) from e
__main__.NodeConnectionError: An error occurred with the node at A web3 connection error occurred talking to https://matic-mainnet-full-rpc.bwarelabs.com:443..

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\t\extest.py", line 140, in <module>
    main()
  File "C:\t\extest.py", line 116, in main
    except NodeNotConnected(w3.provider.endpoint_uri, w3):
TypeError: catching classes that do not inherit from BaseException is not allowed

测试代码:

from web3 import Web3
from time import sleep
from requests.exceptions import ConnectionError, ConnectTimeout, HTTPError
import sys

class NodeError(Exception):
    """Base exception for node errors"""
    def __init__(self, url, msg=None):
        if msg is None:
            msg = f"An error occurred with the node at url."
        super().__init__(msg)
        self.url = url

class NodeNotConnected(NodeError):
    """web3 could not connect to a node"""
    def __init__(self, url, w3=None):
        msg = f"A web3 connection could not be made to URL url."
        super().__init__(url, msg=msg)
        self.w3 = w3

class NodeConnectionError(NodeError):
    """A web3 error occurred communicating with a node"""
    def __init__(self, w3):
        msg = (
            f"A web3 connection error occurred talking to "
            f"w3.provider.endpoint_uri."
        )
        super().__init__(msg)
        self.w3 = w3

class NodeTooManyRequests(NodeError):
    def __init__(self, w3):
        msg = (
            f"Too many requests made to w3.provider.endpoint_uri.  Try a "
            f"different node."
        )
        super().__init__(msg)
        self.w3 = w3

class NodeNoAvailableNodes(NodeError):
    def __init__(self):
        msg = f"Unable to connect to any nodes."
        super().__init__(msg)

class NodeInternalError(NodeError):
    def __init__(self):
        msg = "The node had an internal error."
        super().__init__(msg)

def lib_connect_to_node(url):
    """Emulates library connect to node function"""
    try:
        w3 = Web3(
            Web3.HTTPProvider(
                url,
                request_kwargs="timeout": 5
            )
        )
        if not w3.isConnected():
            raise NodeNotConnected(url, w3)
    except Exception as e:
        raise NodeNotConnected(url) from e
    else:
        return w3

def connect_to_node(urls, node_retries):
    while node_retries >= 0:
        try:
            w3 = lib_connect_to_node(urls[0])
        except NodeNotConnected(urls[0]) as e:
            if node_retries == 0:
                raise NodeNoAvailableNodes from e
            node_retries -= 1
            urls = get_next_node(urls)
            print('Trying another node')
            sleep(1)
            continue
        else:
            return w3

def get_next_node(urls):
    urls.append(urls.pop(urls.index(urls[0])))
    return urls

def get_tx_count(w3, address):
    try:
        nonce = w3.eth.get_transaction_count(address)
    except (ConnectionError, ConnectTimeout) as e:
        print("A requests.exceptions.ConnectionError occurred.")
        raise NodeConnectionError(w3) from e
    except HTTPError as e:
        if e.code == 429:
            raise NodeTooManyRequests from e
    else:
        return nonce

def main():
    urls = [
        'https://matic-mainnet-full-rpc.bwarelabs.com:443',
        'https://matic-mainnet.chainstacklabs.com:443',
        'https://rpc-mainnet.maticvigil.com:443',
        'https://rpc-mainnet.matic.network:443'
    ]
    node_retries = 3
    address = '0xe18A0D121057B002BaFb90aD5F1AB951594A61E8'
    try:
        w3 = connect_to_node(urls, node_retries)
    except NodeNoAvailableNodes as e:
        print(e)
        sys.exit()

    while True:
        try:
            print(get_tx_count(w3, address))
            sleep(0.05)
        except NodeNotConnected(w3.provider.endpoint_uri, w3):
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit()
        except NodeConnectionError(w3) as e:
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit() 
        except NodeTooManyRequests(w3) as e:
            print('Too many requests')
            urls = get_next_node(urls)
            try:
                w3 = connect_to_node(urls, node_retries)
            except NodeNoAvailableNodes as e:
                print(e)
                sys.exit()

if __name__ == '__main__':
    main()

【问题讨论】:

语法是except NodeTooManyRequests,而不是except NodeTooManyRequests(...)。你需要一个类,而不是那个类的实例 【参考方案1】:

您应该能够捕获任何异常,即使它不直接从 BaseException 继承。

但是,从except 子句中删除类参数,并且只保留类名:

try:
    raise NodeNotConnected('abc')
except NodeNotConnected as e:
    print(e)

结果:

A web3 connection could not be made to URL abc.

当您指定类参数时,将实例化一个类,其计算结果为实例而不是类型。

【讨论】:

错误信息相当具有误导性。我发现像try: 1/0; except 42 as e: pass 这样的东西产生完全相同的输出非常令人惊讶。为什么错误消息没有简单说明参数不是类 这行得通。我在想异常错误。 raise 是我想传递任何参数的地方,except 只是捕获异常。如果我想使用任何参数,我只需添加“as e”部分,我可以在其中使用任何东西。谢谢! @ekhumoro 错误信息不会误导 IMO,但是当使用参数时,整个事情(NodeNotConnected('abc'))是一个类的实例,而不是类本身 是的 - 所以它显然 误导,因为类型错误应该说明参数 不是类,正如我之前所说的。 那也不错

以上是关于自定义异常 - 捕获不继承自 BaseException 的类的主要内容,如果未能解决你的问题,请参考以下文章

自定义异常

Object-异常声明及捕获-自定义异常

java中自定义异常为何要继承exception类

通过寄生组合式继承创建js的异常类

java中Debug调试异常的概念异常体系处理异常的关键字try/catch/finally/throw/throws多个异常使用捕获并处理的方式继承关系中处理异常自定义异常类

java中Debug调试异常的概念异常体系处理异常的关键字try/catch/finally/throw/throws多个异常使用捕获并处理的方式继承关系中处理异常自定义异常类