是否可以在退出块之前重新分配 with-block 的对象?
我与偶尔在传输过程中断开连接的 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
Run Code Online (Sandbox Code Playgroud)
这个包装器工作正常,但似乎不能在with块内使用,就像可以使用普通的 FTP 连接一样。如果该except子句被击中,连接将重新建立,但在退出with块时,python 将尝试关闭原始conn,而不是新的:
with ftplib.FTP(**kwargs) as conn:
conn = retry(conn, **kwargs)
Run Code Online (Sandbox Code Playgroud)
这可以通过自定义上下文管理器来演示,显示 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>
Run Code Online (Sandbox Code Playgroud)
如何conn在with块内重新分配,以便 python 调用__exit__()最新对象上的方法,而不是原始对象?这样的事情甚至是可能的,还是我被迫没有with阻塞并且必须记住conn.quit()到处打电话?
如果重要的话,我想要一些与 python 2 和 3 兼容的东西。如果一个解决方案与两者都不兼容,那么我更喜欢 python 3 特定的解决方案而不是 python 2 特定的解决方案
Using pieces from several other answers, I've restructured the retry logic into a wrapper class that can be used as a context manager. This way I can recreate the FTP instance as needed, without altering the objected used in a with block. Normal __enter__() and __exit__() events from the with block are passed down to the FTP instance, and during recreation these methods are explicitly called. Below is the basic framework; I've cut out a lot of the actual FTP stuff so that piece won't work as-is, but the context manager stuff is all here:
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()
Run Code Online (Sandbox Code Playgroud)
with RetryClient() as rc:
for inode in os.listdir('.'):
rc.upload(inode)
Run Code Online (Sandbox Code Playgroud)
I've done some basic testing and it appears to be behaving as expected, at least for exceptions due to invalid permissions or open file handles. I don't have a way to test the dropped FTP connections, so I'll just need to wait for it to happen again and see how this handles it.
| 归档时间: |
|
| 查看次数: |
192 次 |
| 最近记录: |