我有一个类似于以下的 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')被调用时,将引发异常,因为 timestamp 不再存在)。
我希望用 transaction 装饰的任何东西都只在没有装饰的情况下才被装饰。
我试图根据 separate Q&A 的提示修改我的代码:
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,对于内部调用,变量设置为 f2,因此这两个函数被分别修饰。
我可以将时间戳变量设置为上下文管理器的属性,但我仍然会得到两个最好的上下文管理器,如果两个事务都是 R/W,就会发生坏事。
有人可以提出更好的解决方案吗?
编辑:根据收到的回复,我相信我省略了一个关键元素:fn1 和 fn2 是 API 方法,必须始终运行在交易中。使用装饰器的原因是为了防止 API 实现者必须对事务做出决定,或者必须在上下文管理器或装饰器中手动包装方法。然而,我会赞成非装饰器方法,只要它保持 fn1 和 fn2 的实现简单。
请您参考如下方法:
我不使用装饰器,而是创建另一个上下文管理器来显式使用。
@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。
