是否可以在退出块之前重新分配带块的对象?
我与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特定的解决方案
答案 0 :(得分:2)
回答你的一般性问题,不,你不能,如PEP 343&#34;规范:with声明&#34;所示。上下文中的变量e
将保存到内部变量中,该变量在拆卸时使用。
对于特定的FTP连接,在其他答案中提出了一些其他选项。
答案 1 :(得分:1)
__enter__
不仅可以返回任何对象self
。即e
是一个普通变量,__exit__
是同一个对象的方法,称为__enter__
。
至于您的问题,您可以调用connect
和login
以使用相同的FTP实例重新连接到服务器:
def retry(conn, user, passwd):
conn.connect()
conn.login(user, passwd)
答案 2 :(得分:1)
您无法更改调用哪个__exit__
方法。您必须重新构建代码。
尝试重新分配with
目标时存在两个问题。首先,with
旨在拆除它设置的资源,并且设计没有太多理由让你干扰它。其次,with
目标甚至不必是实际的上下文管理器。例如,如果您使用with contextlib.closing(something) as thing
,thing
很可能甚至不是有效的上下文管理器。调用__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连接,所以我只需要等待它再次发生,看看它是如何处理的。