清理嵌套的Try/Except
我刚写了一段代码,感觉它的嵌套层次太多了,不太理想。我想请教一下,怎么才能让这段代码的风格更好,特别是让它更符合“扁平化比嵌套好”的原则。
for app in apps:
if app.split('.', 1)[0] == 'zc': #only look for cron in zc apps
try:
a = app + '.cron'
__import__(a)
m = sys.modules[a]
try:
min = m.cron_minute()
for job in min:
k.add_interval_task(job[0], 'minute task', r(M_LB, M_UB),
60*job[1],
kronos.method.threaded, (), ())
except AttributeError: #no minute tasks
pass
try:
hour = m.cron_hour()
for job in hour:
k.add_daytime_task(job[0], 'day task', range(1, 8), None,
(job[1], r(H_LB, H_UB)),
kronos.method.threaded, (), ())
except AttributeError: #no hour tasks
pass
except ImportError: #no cron jobs for this module
pass
编辑:结合下面的建议,这是我重新写的版本。
for app in apps:
if app.split('.', 1)[0] != 'zc': #only look for cron in zc apps
continue
try:
a = app + '.cron'
__import__(a)
except ImportError: #no cron jobs for this module, continue to next one
continue
m = sys.modules[a]
if hasattr(m, 'cron_minute'):
min = m.cron_minute()
for job in min:
k.add_interval_task(job[0], 'minute task', r(M_LB, M_UB),
60*job[1],
kronos.method.threaded, (), ())
if hasattr(m, 'cron_hour'):
hour = m.cron_hour()
for job in hour:
k.add_daytime_task(job[0], 'day task', range(1, 8), None,
(job[1], r(H_LB, H_UB)),
kronos.method.threaded, (), ())
4 个回答
这里的关键是要弄清楚它们是否出错了。这就是异常处理中的处理部分。我的意思是,至少要打印一个警告,说明评论的假设是什么。在实际处理之前就担心过多的嵌套,感觉有点超前了。先关注处理是否正确,再考虑其他的花样。
我在想,如果每个时间单位的任务真的出现了问题,会不会引发一个属性错误(AttributeError)或者其他的异常呢?特别是,如果某个任务真的坏掉了,你可能就不应该去捕捉这些错误。
还有一个可以帮助你的方法是,只对出问题的代码块使用try-catch,也就是把错误处理的代码放得尽量靠近出错的地方。这里有个例子:
for app in apps:
if app.split('.', 1)[0] == 'zc': #only look for cron in zc apps
try:
a = app + '.cron'
__import__(a)
m = sys.modules[a]
except ImportError: #no cron jobs for this module
#exception is silently ignored
#since no jobs is not an error
continue
if hasattr(m, "cron_minute"):
min = m.cron_minute()
for job in min:
k.add_interval_task(job[0], 'minute task', r(M_LB, M_UB),
60*job[1],
kronos.method.threaded, (), ())
if hasattr(m, "cron_hour"):
hour = m.cron_hour()
for job in hour:
k.add_daytime_task(job[0], 'day task', range(1, 8), None,
(job[1], r(H_LB, H_UB)),
kronos.method.threaded, (), ())
注意这里只有一个错误处理器,我们通过正确地忽略它来处理这个问题。因为我们可以预测到可能会缺少某个属性,所以我们明确地检查一下,这样可以让代码看起来更清晰。否则,你就不太明白为什么要捕捉这个属性错误,或者到底是什么导致了这个错误。
主要问题是你的try语句写得太宽泛了,特别是最外层的那一个。这样写的话,迟早会遇到一些神秘的错误,因为你的某个try/except可能会意外地把其他函数抛出的异常给隐藏掉了。
所以我建议你可以这样做:
for app in apps:
if app.split('.', 1)[0] != 'zc': #only look for cron in zc apps
continue
try:
a = app + '.cron'
__import__(a)
except ImportError: #no cron jobs for this module
continue
# etc etc
顺便提一下,我在另一个方面也在应用“扁平化比嵌套好”的原则(这和try/except没有关系),就是“如果我在这个循环的这一段没有其他事情要做,就继续(也就是跳到下一个循环)”,而不是“如果我有事情要做:”后面跟着一大堆嵌套的代码。我一直更喜欢这种风格(用if/continue或if/return)而不是嵌套的if,这在现代编程语言中都能找到类似的功能,比如continue
(几乎所有现代语言都有,因为C语言就有这个功能;-)。
不过这只是一个简单的“扁平化与嵌套”的风格偏好,真正的问题是:保持你的try语句简短!最糟糕的情况是,当你在except语句中无法简单地继续或返回时,可以使用try/except/else:在try语句中只放入那些绝对必须的代码——也就是那些很可能会抛出异常的小段代码——而把后面的其他代码(那些不应该也不期待抛出异常的部分)放在else语句中。这并不会改变嵌套的结构,但确实能大大降低意外隐藏不该被隐藏的异常的风险!