我正在编写一个需要使用外部库中的类的脚本,做一些 对该类的实例进行操作,然后再重复一些实例。
这样的事情:
import some_library
work_queue = get_items()
for item in work_queue:
some_object = some_library.SomeClass(item)
operation_1(some_object)
# ...
operation_N(some_object)
但是,循环体中的每个操作都可能引发一些不同的异常。 当这些发生时,我需要记录它们并跳到下一个项目。如果他们加薪 在崩溃之前我需要记录一些意外的异常。
我可以捕获主循环中的所有异常,但这会掩盖它的作用。 所以我发现自己编写了一堆看似类似的包装函数:
def wrapper_op1(some_object):
try:
some_object.method_1()
except (some_library.SomeOtherError, ValueError) as error_message:
logger.error("Op1 error on {}".format(some_object.friendly_name))
return False
except Exception as error_message:
logger.error("Unknown error during op1 on {} - crashing: {}".format(some_object.friendly_name, error_message))
raise
else:
return True
# Notice there is a different tuple of anticipated exceptions
# and the message formatting is different
def wrapper_opN(some_object):
try:
some_function(some_object.some_attr)
except (RuntimeError, AttributeError) as error_message:
logger.error("OpN error on {} with {}".format(some_object.friendly_name, some_object.some_attr, error_message))
return False
except Exception as error_message:
logger.error("Unknown error during opN on {} with {} - crashing: {}".(some_object.friendly_name, some_object.some_attr, error_message))
raise
else:
return True
将主循环修改为:
for item in work_queue:
some_object = some_library.SomeClass(item)
if not wrapper_op1(some_object):
continue
# ...
if not wrapper_opN(some_object):
continue
这可以完成这项工作,但感觉就像很多复制和粘贴编程一样 包装纸。什么是伟大的是写一个可以的装饰器功能 做所有尝试...除了......别的东西,所以我可以做:
@ logged_call(known_exception, known_error_message, unknown_error_message)
def wrapper_op1(some_object):
some_object.method_1()
如果操作成功,包装器将返回True,捕获已知的异常 并以指定的格式记录,并在重新提升之前捕获任何未知的日志记录异常。
但是,我似乎无法理解如何使错误消息发挥作用 - 我可以使用固定字符串来实现:
def logged_call(known_exceptions, s_err, s_fatal):
def decorate(f):
@wraps(f)
def wrapper(*args, **kwargs):
try:
f(*args, **kwargs)
# How to get parameters from args in the message?
except known_exceptions as error:
print(s_err.format(error))
return False
except Exception as error:
print(s_fatal.format(error))
raise
else:
return True
return wrapper
return decorate
但是,我的错误消息需要获取属于修饰函数的属性。
是否有一些Pythonic方法可以完成这项工作?或者使用不同的模式 在处理may-fail-in-known-way函数时?
答案 0 :(得分:1)
以下tryblock
函数可用于此目的,以实现您的logged_call
概念并减少代码总量,假设您有足够的检查来克服装饰器实现。不习惯Python中的函数式编程的人实际上可能发现这比仅仅写出try
块更难以理解。像许多事情一样,简单性在旁观者的眼中。
Python 2.7不使用导入。这使用exec
语句创建一个以功能模式工作的自定义try
块。
def tryblock(tryf, *catchclauses, **otherclauses):
u'return a general try-catch-else-finally block as a function'
elsef = otherclauses.get('elsef', None)
finallyf = otherclauses.get('finallyf', None)
namespace = {'tryf': tryf, 'elsef': elsef, 'finallyf': finallyf, 'func': []}
for pair in enumerate(catchclauses):
namespace['e%s' % (pair[0],)] = pair[1][0]
namespace['f%s' % (pair[0],)] = pair[1][1]
source = []
add = lambda indent, line: source.append(' ' * indent + line)
add(0, 'def block(*args, **kwargs):')
add(1, "u'generated function executing a try block'")
add(1, 'try:')
add(2, '%stryf(*args, **kwargs)' % ('return ' if otherclauses.get('returnbody', elsef is None) else '',))
for index in xrange(len(catchclauses)):
add(1, 'except e%s as ex:' % (index,))
add(2, 'return f%s(ex, *args, **kwargs)' % (index,))
if elsef is not None:
add(1, 'else:')
add(2, 'return elsef(*args, **kwargs)')
if finallyf is not None:
add(1, 'finally:')
add(2, '%sfinallyf(*args, **kwargs)' % ('return ' if otherclauses.get('returnfinally', False) else '',))
add(0, 'func.append(block)')
exec '\n'.join(source) in namespace
return namespace['func'][0]
这个tryblock
函数足以进入公共库,因为它不是特定于检查逻辑的。添加你的logged_call
装饰器,实现为(此处导入一个):
import functools
resultof = lambda func: func() # @ token must be followed by an identifier
@resultof
def logged_call():
truism = lambda *args, **kwargs: True
def raisef(ex, *args, **kwargs):
raise ex
def impl(exlist, err, fatal):
return lambda func: \
functools.wraps(func)(tryblock(func,
(exlist, lambda ex, *args, **kwargs: err(ex, *args, **kwargs) and False),
(Exception, lambda ex, *args, **kwargs: fatal(ex, *args, **kwargs) and raisef(ex))),
elsef=truism)
return impl # impl therefore becomes logged_call
使用logged_call
实施,您的两个完整性检查如下:
op1check = logged_call((some_library.SomeOtherError, ValueError),
lambda _, obj: logger.error("Op1 error on {}".format(obj.friendly_name)),
lambda ex, obj: logger.error("Unknown error during op1 on {} - crashing: {}".format(obj.friendly_name, ex.message)))
opNcheck = logged_call((RuntimeError, AttributeError),
lambda ex, obj: logger.error("OpN error on {} with {}".format(obj.friendly_name, obj.some_attr, ex.message)),
lambda ex, obj: logger.error("Unknown error during opN on {} with {} - crashing: {}".format(obj.friendly_name, obj.some_attr, ex.message)))
@op1check
def wrapper_op1(obj):
return obj.method_1()
@opNcheck
def wrapper_opN(obj):
return some_function(obj.some_attr)
忽略空白行,这比原始代码更多 compact 10行,但是tryblock
和logged_call
实现的沉没成本;是否现在更多可读是一个意见问题。
您还可以选择在单独的模块中定义logged_call
本身以及从中派生的所有不同装饰器,如果这对您的代码是明智的;因此要多次使用每个派生的装饰器。
通过调整实际检查,您还可以找到更多可以考虑logged_call
的逻辑结构。
但是在最糟糕的情况下,每个检查都有其他没有的逻辑,你可能会发现只要像你已经那样写出每个检查,它就更具可读性。这真的取决于。
为了完整性,这是tryblock
函数的单元测试:
import examplemodule as ex
from unittest import TestCase
class TestTryblock(TestCase):
def test_tryblock(self):
def tryf(a, b):
if a % 2 == 0:
raise ValueError
return a + b
def proc_ve(ex, a, b):
self.assertIsInstance(ex, ValueError)
if a % 3 == 0:
raise ValueError
return a + b + 10
def elsef(a, b):
return a + b + 20
def finallyf(a, b):
return a + b + 30
block = ex.tryblock(tryf, (ValueError, proc_ve))
self.assertRaises(ValueError, block, 0, 4)
self.assertRaises(ValueError, block, 6, 4)
self.assertEqual([5, 16, 7, 18, 9], map(lambda v: block(v, 4), xrange(1, 6)))
block = ex.tryblock(tryf, (ValueError, proc_ve), elsef=elsef)
self.assertEqual([25, 16, 27, 18, 29], map(lambda v: block(v, 4), xrange(1, 6)))
block = ex.tryblock(tryf, (ValueError, proc_ve), elsef=elsef, returnbody=True)
self.assertEqual([5, 16, 7, 18, 9], map(lambda v: block(v, 4), xrange(1, 6)))
block = ex.tryblock(tryf, (ValueError, proc_ve), finallyf=finallyf)
self.assertEqual([5, 16, 7, 18, 9], map(lambda v: block(v, 4), xrange(1, 6)))
block = ex.tryblock(tryf, (ValueError, proc_ve), finallyf=finallyf, returnfinally=True)
self.assertEqual([35, 36, 37, 38, 39], map(lambda v: block(v, 4), xrange(1, 6)))