我已经创建了从ftp站点提取数据的python函数。效果很好。然而,有很多try/except语句。我读过关于使用python"with"语句来做得更好,但我不清楚这将如何改进函数。下面是代码:
HOST = 'ftp.osuosl.org'
DIRN = 'debian/tools'
FILE = 'loadlin.txt'
def func(HOST, DIRN, FILE):
import ftplib
from StringIO import StringIO
import os
import socket
try:
f = ftplib.FTP(HOST)
except (socket.error, socket.gaierror), e:
print 'ERROR: cannot reach "%s"' % HOST
return "None"
print '*** Connected to host "%s"' % HOST
try:
f.login()
except ftplib.error_perm:
print 'ERROR: cannot login anonymously'
f.quit()
return "None"
print '*** Logged in as "anonymous"'
try:
f.cwd(DIRN)
except ftplib.error_perm:
print 'ERROR: cannot CD to "%s"' % DIRN
f.quit()
return "None"
print '*** Changed to "%s" folder' % DIRN
try:
r = StringIO()
f.retrbinary('RETR %s' % FILE, r.write)
except ftplib.error_perm:
print 'ERROR: cannot read file "%s"' % FILE
return "None"
else:
print '*** Downloaded "%s" to CWD' % FILE
f.quit()
return r.getvalue()
print func(HOST, DIRN, FILE)
一般来说,这种风格效果很好。每个有趣的代码段都有一个try/except块,因此您可以将有关错误的具体细节传达给调用者。
代码在几个地方都有f.quit()
。这很好,但是很容易忘记哪些情况应该有quit
,哪些不应该。很容易漏掉一个。
考虑使用finally
块的这种样式。如果RETR
成功或失败,这个块总是被执行。它是更安全的。
try:
r = StringIO()
f.retrbinary('RETR %s' % FILE, r.write)
except ftplib.error_perm:
print 'ERROR: cannot read file "%s"' % FILE
return "None"
else:
print '*** Downloaded "%s" to CWD' % FILE
finally:
f.quit()
使用上下文管理器有一种稍微更好的方法。因为您在except
分支中执行的操作以及在运行命令后立即执行的操作基本上是样板文件,所以您可以将其抽象到上下文管理器的__exit__
部分中。唯一的复杂之处在于,您希望在捕获异常后返回,而异常无法隐藏在上下文管理器中。所以我们必须在上下文对象本身上设置一个标志,并在调用代码中检查(因此if h.err:
检查):
import ftplib
from StringIO import StringIO
import os
import socket
class HandleExc(object):
""" Context manager for exception handling.
exc_types is a tuple of exception types we want to handle
"""
def __init__(self, handle=f, exc_types=(ftplib.error_perm,), msg="", errmsg=""):
self.err = False
self.f = f
self.msg = msg
self.errmsg = errmsg
def __enter__(self):
return self
def __exit__(exc_type, exc_value, traceback):
if exc_type in exc_types:
# Got an exception we want to handle.
print(self.errmsg)
if self.f:
self.f.close()
self.err = True # Set the err flag so the caller can check it.
elif exc_type:
# Unhandled exception, let it raise (though you may want to call f.close() first).
return
else:
# All good print success message
print(self.msg)
def func(HOST, DIRN, FILE):
with HandleExc(exc=(socket.errror, socket.gaierror),
msg='*** Connected to host "%s" ' % HOST,
errmsg='ERROR: cannot reach "%s"' % HOST) as h:
f = ftplib.FTP(HOST)
if h.err:
return "None"
with HandleExc(f, msg='*** Logged in as "anonymous"',
errmsg='ERROR: cannot CD to "%s"' % DIRN) as h:
f.login()
if h.err:
return "None"
with HandleExc(f, msg='*** Changed to "%s" folder' % DIRN,
errmsg='ERROR: cannot login anonymously') as h:
f.cwd(DIRN)
if h.err:
return "None"
with HandleExc(f, msg='*** Downloaded "%s" to CWD' % FILE,
errmsg='ERROR: cannot read file "%s"') as h:
r = StringIO()
f.retrbinary('RETR %s' % FILE, r.write)
if h.err:
return "None"
f.quit()
return r.getvalue()
print func(HOST, DIRN, FILE)
这在一定程度上减少了func
中的样板代码,尽管不是全部。