我有一个类似于以下内容的Python 3脚本:
def transaction(write=False):
def _transaction_deco(fn):
@wraps(fn)
def _wrapper(*args, **kwargs):
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
ret = fn(*args, **kwargs)
delattr(env, 'timestamp')
return ret
return _wrapper
return _transaction_deco
@transaction(True)
def fn1(x=False):
if x:
return fn2()
do_something()
@transaction(True)
def fn2():
do_something_else()
fn1
和fn2
可以独立调用,并且都包含在transaction
装饰器中。
此外,fn1
在某些情况下可以致电fn2
。这是我遇到问题的地方,因为装饰器代码被调用两次,事务被嵌套,并且时间戳被设置两次并被删除两次(实际上,第二次调用delattr(env, 'timestamp')
时,会引发异常,因为{{ 1}}不再存在)。
我希望用timestamp
装饰的任何东西只能装饰,如果不是这样的话。
我尝试根据separate Q&A:
中的提示修改我的代码transaction
即。如果为函数设置了def transaction(write=False):
def _transaction_deco(fn):
# Avoid double wrap.
if getattr(fn, 'in_txn', False):
return fn(*args, **kwargs)
@wraps(fn)
def _wrapper(*args, **kwargs):
# Mark transaction begin timestamp.
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
ret = fn(*args, **kwargs)
delattr(env, 'timestamp')
return ret
_wrapper.in_txn = True
return _wrapper
return _transaction_deco
属性,则返回裸函数,因为我们已经处于事务中。
但是,如果我使用调试器运行_in_txn
,我会发现对fn1(True)
的检查永远不会成立,因为外部的in_txn
变量设置为_wrapper
调用,并且f1
用于内部调用,因此函数都是单独修饰的。
我可以将时间戳变量设置为上下文管理器的属性,但我仍然最终得到两个击败的上下文管理器,如果两个转换都是R / W,那么坏事就会发生。
有人可以提出更好的解决方案吗?
编辑:根据收到的回复,我相信我省略了一个关键元素:f2
和fn1
是API元数据,必须始终在事务中运行。使用装饰器的原因是为了防止API实现者必须对事务做出决定,或者必须在上下文管理器或装饰器中手动包装方法。但是,只要它保持fn2
和fn1
的实现,我就会支持非装饰器方法。
答案 0 :(得分:4)
而不是装饰器,我只是让另一个上下文管理器明确使用。
@contextlib.contextmanager
def MyTxnManager(write):
env.timestamp = arrow.utcnow()
with TxnManager(write=write) as txn:
yield txn
delattr(env, 'timestamp')
with MyTxnManager(True) as txn:
fn1()
这里,我们不是将事务隐藏在fn1
和fn2
中,而是将时间戳管理包装在新的事务管理器中。这样,只有在我们明确要求它们执行此操作时,才会在事务中调用fn1
和fn2
。
答案 1 :(得分:2)
没有必要在装饰器中处理这个问题。分离{{1}}的包装和展开版本要简单得多,例如:
{{1}}
装饰器语法只是语法糖,因此结果将完全相同。
答案 2 :(得分:0)
您始终只需检查环境是否已有时间戳,在这种情况下不创建时间戳。它使用已存在的属性作为标志,而不是创建一个新标志:
def transaction(write=False): def _transaction_deco(fn): @wraps(fn) def _wrapper(*args, **kwargs): if hasattr(env, 'timestamp'): ret = fn(*args, **kwargs) else: env.timestamp = arrow.utcnow() with TxnManager(write=write) as txn: ret = fn(*args, **kwargs) delattr(env, 'timestamp') return ret return _wrapper return _transaction_deco
这将允许您嵌套任意数量的事务调用,并且只使用最外层调用的时间戳和上下文管理器。