首页 > 解决方案 > Python:在具有干净调用堆栈的除外块上重新运行函数

问题描述

我有可能产生无限调用堆栈的代码(简化):

def listen(self, pipeline):
    try:
        for message in self.channel.consume(self.queue_name):
            pipeline.process(message)
            self.channel.basic_ack(delivery_tag=method_frame.delivery_tag)

    except (pika.exceptions.StreamLostError,
            pika.exceptions.ConnectionClosed,
            pika.exceptions.ChannelClosed,
            ConnectionResetError) as e:
        logging.warning(f'Connection dropped for queue {self.queue_name}. Exception: {e}. Reconnecting...')
        self._reconnect()
        self.listen(pipeline)

如果有任何网络问题,它将记录错误,重新连接并进一步移动。但它也会向调用堆栈添加一个额外的调用。所以我的错误堆栈跟踪将是这样的:

...
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 1336, in _flush_output
    self._connection._flush_output(lambda: self.is_closed, *waiters)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 522, in _flush_output
    raise self._closed_result.value.error
pika.exceptions.StreamLostError: Stream connection lost: ConnectionResetError(104, 'Connection reset by peer')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/msworker/queue.py", line 81, in listen
    self.channel.basic_ack(delivery_tag=method_frame.delivery_tag)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 2113, in basic_ack
    self._flush_output()
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 1336, in _flush_output
    self._connection._flush_output(lambda: self.is_closed, *waiters)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 522, in _flush_output
    raise self._closed_result.value.error
pika.exceptions.StreamLostError: Stream connection lost: ConnectionResetError(104, 'Connection reset by peer')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/msworker/queue.py", line 81, in listen
    self.channel.basic_ack(delivery_tag=method_frame.delivery_tag)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 2113, in basic_ack
    self._flush_output()
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 1336, in _flush_output
    self._connection._flush_output(lambda: self.is_closed, *waiters)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/blocking_connection.py", line 522, in _flush_output
    raise self._closed_result.value.error
pika.exceptions.StreamLostError: Stream connection lost: ConnectionResetError(104, 'Connection reset by peer')
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/utils/io_services_utils.py", line 1097, in _on_socket_writable
    self._produce()
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/utils/io_services_utils.py", line 820, in _produce
    self._tx_buffers[0])
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/utils/io_services_utils.py", line 79, in retry_sigint_wrap
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/pika/adapters/utils/io_services_utils.py", line 861, in _sigint_safe_send
    return sock.send(data)
ConnectionResetError: [Errno 104] Connection reset by peer

如何listen在调用堆栈中没有旧调用的情况下从头开始重新运行函数?

更新

为了避免这个问题,操作嵌套函数并重新运行它而不是自身是正确的:

def listen(self, pipeline):
    try:
        self._listen(self, pipeline)
    except (pika.exceptions.StreamLostError,
            pika.exceptions.ConnectionClosed,
            pika.exceptions.ChannelClosed,
            ConnectionResetError) as e:
        logging.warning(f'Connection dropped for queue {self.queue_name}. Exception: {e}. Reconnecting...')
        self._reconnect()
        self._listen(self, pipeline)


def _listen(self, pipeline):
    for message in self.channel.consume(self.queue_name):
            pipeline.process(message)

但是,有没有办法用干净的调用堆栈重新运行递归函数?

标签: pythonexception

解决方案


当您可以使用简单迭代时,为什么要使用递归?

def listen(self, pipeline):
    while True:
        try:
            for message in self.channel.consume(self.queue_name):
                pipeline.process(message)
                self.channel.basic_ack(delivery_tag=method_frame.delivery_tag)
            return

        except (pika.exceptions.StreamLostError,
                pika.exceptions.ConnectionClosed,
                pika.exceptions.ChannelClosed,
                ConnectionResetError) as e:
            logging.warning(f'Connection dropped for queue {self.queue_name}. Exception: {e}. Reconnecting...')
        self._reconnect()

但是,有没有办法用干净的调用堆栈重新运行递归函数?

实际上,您目前拥有的是一个“干净的调用堆栈”——它是真正的调用堆栈,每次调用都有一个不同的帧(递归或非递归)。有些语言确实“优化”了尾递归调用(通过压缩/重用帧),Python 的设计者选择不这样做,以使调试更容易。


推荐阅读