【问题标题】:what happens to a python object when you throw an exception from it当您从 python 对象中抛出异常时会发生什么
【发布时间】:2009-09-25 02:49:51
【问题描述】:

我的课程包含一个连接到服务器的套接字。该类的某些方法可能会引发异常。我正在运行的脚本包含一个外部循环,它捕获异常、记录错误并创建一个尝试重新连接到服务器的新类实例。

问题是服务器一次只处理一个连接(按设计),并且“旧”套接字仍处于连接状态。所以新的连接尝试会挂起脚本。我可以通过强制关闭旧套接字来解决这个问题,但我想知道:为什么套接字不会自动关闭?

当它“卡住”时,netstat 显示连接到端口的两个套接字。虽然服务器正在等待来自第一个套接字的输入,但它还没有处理新的。

我在一个虚拟服务器上运行这个,它对每个传入的行都回复“错误\n”。

编辑:请参阅我对 Mark Rushakoff's answer below 的评论。异常处理程序中的 assert(False) [我随后捕获的] 似乎强制关闭套接字。

import socket

class MyException(Exception):
    pass

class MyClient(object):

    def __init__(self, port):
        self.sock = socket.create_connection(('localhost', port))
        self.sockfile = self.sock.makefile()

    def do_stuff(self):
        self._send("do_stuff\n")
        response = self._receive()
        if response != "ok\n":
            raise MyException()
        return response

    def _send(self, cmd):
        self.sockfile.write(cmd)
        self.sockfile.flush()

    def _receive(self):
        return self.sockfile.readline()

def connect():
    c = MyClient(9989)
    # On the second iteration, do_stuff() tries to send data and
    # hangs indefinitely.
    print c.do_stuff()

if __name__ == '__main__':
    for _ in xrange(3):
        try:
            connect()
        except MyException, e:
            print 'Caught:', e
            # This would be the workaround if I had access to the
            # MyClient object:
            #c.sock.close()
            #c.sockfile.close()

编辑:这是(丑陋的)服务器代码:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
s.bind(('localhost', 9989))
s.listen(5)
(c,a) = s.accept()
f = c.makefile()
print f.readline()
f.write('error\n')
f.flush()
(c2,a) = s.accept()
f = c.makefile()
print f.readline()
s.close()

【问题讨论】:

    标签: python exception garbage-collection sockets


    【解决方案1】:

    这是垃圾收集的产物。即使对象超出范围,它也不一定收集,因此在垃圾收集运行发生之前销毁——这不像在 C++ 中,一旦对象失去作用域,就会调用析构函数。

    您可以通过将 connect 更改为

    来解决此特定问题
    def connect():
        try:
            c = MyClient(9989)
            # On the second iteration, do_stuff() tries to send data and
            # hangs indefinitely.
            print c.do_stuff()
        finally:
            c.sock.close()
            c.sockfile.close()
    

    或者,您可以为MyClient 定义__enter____exit__,然后执行a with statement

    def connect():
        with MyClient(9989) as c:
            print c.do_stuff()
    

    这实际上与 try-finally 相同。

    【讨论】:

    • 我不确定这是否成立。我刚刚发现,如果我将 try/except 包装在另一个 try/except AssertionError 和 assert(False) 中的内部 except 块中,那么套接字就会被清理干净。我怀疑它与异常堆栈跟踪有关,该异常堆栈跟踪包含对对象的引用,因此对套接字的引用?
    • 您的示例不是一个完整的程序(未提供服务器代码),因此很难确认这一方面的行为。
    • 感谢您添加解决方法。我没有问题解决它,我只是想了解什么持有对象的引用......现在我必须假设它是回溯。
    • 已编辑以包含服务器。
    • 基本上正如马克所说,您不能仅仅因为它超出范围而依赖被释放的对象。仅仅因为垃圾收集存在并不意味着您应该依赖它来触发清理行为,这些行为是被垃圾收集的对象的副作用。
    【解决方案2】:

    你真的可以处理connect()中的异常吗?

    我认为你应该提供一个 MyClient.close() 方法,并像这样编写 connect():

    def connect():
        try:
            c = MyClient(9989)
            print c.do_stuff()
        finally:
            c.close()
    

    这完全类似于文件类对象(以及 with 语句)

    【讨论】:

      【解决方案3】:

      可以通过将相关对象设置为“无”来标记垃圾收集器以进行清理。

      【讨论】:

        【解决方案4】:

        好的,这是最终版本。当某些东西出现故障时,显式关闭套接字对象。

        import socket
        
        class MyException(Exception):
            pass
        
        class MyClient(object):
        
            def __init__(self, port):
                self.sock = socket.create_connection(('localhost', port))
                self.sockfile = self.sock.makefile()
        
            def close(self):
                self.sock.close()
                self.sockfile.close()
        
            def do_stuff(self):
                self._send("do_stuff\n")
                response = self._receive()
                if response != "ok\n":
                    raise MyException()
                return response
        
            def _send(self, cmd):
                self.sockfile.write(cmd)
                self.sockfile.flush()
        
            def _receive(self):
                return self.sockfile.readline()
        
        def connect():
            try:
                c = MyClient(9989)
                print c.do_stuff()
            except MyException:
                print 'Caught MyException'
            finally:
                c.close()
        
        if __name__ == '__main__':
            for _ in xrange(2):
                connect()
        

        【讨论】:

        • 我不知道你为什么犹豫要不要现在解决这个问题?唯一可靠的行为是使用 'finally' 或 'with' 构造并移动代码以避免编写正确的行为似乎是一个奇怪的选择。
        猜你喜欢
        • 1970-01-01
        • 2010-12-12
        • 2011-03-20
        • 1970-01-01
        • 2011-05-07
        • 2014-01-19
        • 1970-01-01
        • 1970-01-01
        • 2017-02-10
        相关资源
        最近更新 更多