当你从Python对象中抛出异常时会发生什么

2 投票
4 回答
678 浏览
提问于 2025-04-15 14:35

我的类里面有一个socket,它用来连接服务器。这个类的一些方法可能会抛出异常。我运行的脚本有一个外层循环,可以捕捉到这些异常,记录错误信息,然后创建一个新的类实例,尝试重新连接服务器。

问题是服务器一次只能处理一个连接(这是设计使然),而“旧”的socket仍然保持连接状态。所以新的连接尝试会让脚本卡住。我可以通过强制关闭旧的socket来解决这个问题,但我想知道:为什么socket不会自动关闭呢?

当它“卡住”时,使用netstat命令可以看到有两个socket连接到同一个端口。不过,服务器只在等待第一个socket的输入,它还没有处理新的连接。

我是在一个虚拟服务器上运行这个,它对每一行输入都会回复“error\n”。

补充说明:请查看我对Mark Rushakoff的回答的评论。在异常处理程序内部,如果我执行assert(False) [随后捕捉到这个异常],似乎会强制关闭socket。

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()

4 个回答

0

你可以通过把相关的对象设置为 None 来告诉垃圾回收器去清理不再需要的东西。

1

你真的能在connect()里处理异常吗?

我觉得你应该提供一个MyClient.close()的方法,然后把connect()写成这样:

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

这和文件对象(还有with语句)是完全一样的。

5

这是垃圾回收的一个现象。即使一个对象已经超出作用域,它并不一定会被回收,也就是说不会立即销毁,直到垃圾回收的过程发生。这和C++不一样,在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__,然后使用with语句

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

这实际上和try-finally是一样的效果。

撰写回答