使用块

时间:2016-04-11 20:07:30

标签: python

是否可以在退出块之前重新分配带块的对象?

特定用例

我与FTP服务器进行交互,偶尔会在传输过程中丢弃连接,IT部门不愿意对此做任何事情。作为我自己的工具的解决方法,我使用的是一个包装器,它会在放弃之前重试传输几次:

def retry(conn, max_tries=3, **kwargs):
    this_try = 1
    while (this_try <= max_tries):
        try:
            # upload / download / whatever
            return conn
        except ftplib.all_errors:
            conn.quit()
            time.sleep(60)
            conn = ftplib.FTP(**kwargs)
            this_try += 1

这个包装工作正常,但似乎不能在with块中使用,就像可以使用普通的FTP连接一样。如果except子句被命中,则将重新建立连接,但在退出with块时,python将尝试关闭原始conn,而不是新的:{ / p>

with ftplib.FTP(**kwargs) as conn:
    conn = retry(conn, **kwargs)

这可以通过自定义上下文管理器来演示,显示python从原始对象调用__exit__(),即使该变量在块中重新分配:

>>> class Echo(object):
...     def __enter__(self):
...             print('entering ' + repr(self))
...             return self
...     def __exit__(self, *args):
...             print('exiting ' + repr(self))
...
>>> with Echo() as e:
...     e = Echo()
...
entering <__main__.Echo object at 0x026C14F0>
exiting <__main__.Echo object at 0x026C14F0>
>>> e
<__main__.Echo object at 0x026C1410>

如何在conn块中重新分配with,以便python在最新对象上调用__exit__()方法,而不是原始对象?这样的事情是否可能,或者我被迫离开with块并且不得不记得在任何地方打电话给conn.quit()

如果重要的话,我想要兼容python 2和3的东西。如果解决方案与两者兼容,那么我宁愿选择python 3特定的解决方案而不是python 2特定的解决方案

4 个答案:

答案 0 :(得分:2)

回答你的一般性问题,不,你不能,如PEP 343&#34;规范:with声明&#34;所示。上下文中的变量e将保存到内部变量中,该变量在拆卸时使用。 对于特定的FTP连接,在其他答案中提出了一些其他选项。

答案 1 :(得分:1)

__enter__不仅可以返回任何对象self。即e是一个普通变量,__exit__是同一个对象的方法,称为__enter__

至于您的问题,您可以调用connectlogin以使用相同的FTP实例重新连接到服务器:

def retry(conn, user, passwd):
    conn.connect()
    conn.login(user, passwd)

答案 2 :(得分:1)

您无法更改调用哪个__exit__方法。您必须重新构建代码。

尝试重新分配with目标时存在两个问题。首先,with旨在拆除它设置的资源,并且设计没有太多理由让你干扰它。其次,with目标甚至不必是实际的上下文管理器。例如,如果您使用with contextlib.closing(something) as thingthing很可能甚至不是有效的上下文管理器。调用__exit__方法的对象完全是其他对象。

重新分配with是错误的方法。相反,重构您的重试逻辑。例如,不是让retry接受连接并且可能返回另一个连接,而是让它创建初始连接,并使用retry作为上下文管理器。 (我不熟悉FTP或ftplib,所以这可能不是这个特定用例的最佳设计):

def retry(max_tries=3, **kwargs):
    for try in range(max_tries):
        conn = ftplib.FTP(**kwargs)
        try:
            # upload / download / whatever
            return conn
        except ftplib.all_errors:
            conn.quit()
            time.sleep(60)
    raise AppropriateError

with retry(...) as conn:
    ...

答案 3 :(得分:0)

使用其他几个答案中的部分,我将重试逻辑重组为一个可用作上下文管理器的包装类。这样我就可以根据需要重新创建FTP实例,而无需更改with块中使用的对象。来自__enter__()块的正常__exit__()with事件将传递给FTP实例,并且在重新创建过程中会显式调用这些方法。以下是基本框架;我已经删除了很多实际的FTP内容,以便该片段不会按原样运行,但上下文管理器的内容就在这里:

class RetryClient():
    def __init__(self, **kwargs):
        self.kwargs = kwargs
        self.conn = get_conn(**kwargs)

    def __enter__(self):
        self.conn.__enter__()
        return self

    def __exit__(self, *args):
        self.conn.__exit__(*args)

    def _reconnect(self):
        self.conn.__exit__()
        time.sleep(60)
        self.conn = get_conn(**self.kwargs)
        self.conn.__enter__()

    def upload(self, src, ...):
        this_try = 1
        while (this_try <= max_tries):
            try:
                with open(src, 'rb') as fh:
                    self.conn.storbinary('STOR ' + src, fh.read)
            except ftplib.all_errors:
                this_try += 1
                self._reconnect()
with RetryClient() as rc:
    for inode in os.listdir('.'):
        rc.upload(inode)

我已经完成了一些基本测试,它似乎表现得如预期,至少对于由于无效权限或打开文件句柄而导致的异常。我没有办法测试丢弃的FTP连接,所以我只需要等待它再次发生,看看它是如何处理的。

相关问题