刷新装饰器
我正在尝试写一个装饰器,它在被调用后会“刷新”,但是这个刷新只会在最后一个函数执行完后进行一次。下面是一个例子:
@auto_refresh
def a():
print "In a"
@auto_refresh
def b():
print "In b"
a()
如果调用了 a()
,我希望在退出 a()
后运行刷新函数。如果调用了 b()
,我希望在退出 b()
后运行刷新函数,但在调用 b()
时不希望在 a()
后运行刷新函数。这里有一个类可以实现这个功能:
class auto_refresh(object):
def __init__(self, f):
print "Initializing decorator"
self.f = f
def __call__(self, *args, **kwargs):
print "Before function"
if 'refresh' in kwargs:
refresh = kwargs.pop('refresh')
else:
refresh = False
self.f(*args, **kwargs)
print "After function"
if refresh:
print "Refreshing"
使用这个装饰器,如果我运行
b()
print '---'
b(refresh=True)
print '---'
b(refresh=False)
我会得到以下输出:
Initializing decorator
Initializing decorator
Before function
In b
Before function
In a
After function
After function
---
Before function
In b
Before function
In a
After function
After function
Refreshing
---
Before function
In b
Before function
In a
After function
After function
所以这样写的话,不指定 refresh
参数意味着默认 refresh
是 False
。有没有人想到办法可以改变这个,使得在不指定时 refresh
是 True
?改变
refresh = False
为
refresh = True
在装饰器中并不能实现:
Initializing decorator
Initializing decorator
Before function
In b
Before function
In a
After function
Refreshing
After function
Refreshing
---
Before function
In b
Before function
In a
After function
Refreshing
After function
Refreshing
---
Before function
In b
Before function
In a
After function
Refreshing
After function
因为这样的话,刷新函数在第一和第二种情况下会被多次调用,而在最后一种情况下只会被调用一次(实际上在第一和第二种情况下应该只调用一次,而在最后一种情况下不应该调用)。
3 个回答
我不太清楚你想用这个设计做什么,所以也不知道这样做是否合适。你可以考虑一下这是不是正确的方法。
不过,我觉得这个设计可以满足你的要求。一个公共对象(在这里叫做 auto_refresh
)被所有“装饰过”的方法共享,这个对象会记录调用的深度,也就是调用栈的层数。
需要注意的是,这个设计不是线程安全的。
class AutoRefresh(object):
nesting = 0
def __call__(self, f):
def wrapper(*args, **kwargs):
return self.proxied_call(f, args, kwargs)
return wrapper
def refresh(self):
print 'refresh'
def proxied_call(self, func, args, kwargs):
self.nesting += 1
result = func(*args, **kwargs)
self.nesting -= 1
if self.nesting == 0:
self.refresh()
return result
auto_refresh = AutoRefresh()
测试用:
@auto_refresh
def a():
print "In a"
@auto_refresh
def b():
print "In b"
a()
a()
print '---'
b()
结果是:
In a
refresh
---
In b
In a
refresh
我觉得维护一个“嵌套刷新计数”可能会更简单。这个计数在每次调用带有刷新功能的代码之前加一,在之后减一(可以放在一个最终执行的代码块里,这样计数就不会出错)。当这个计数变为零的时候,就运行刷新程序。
要计算“嵌套”的数量,并且确保在多线程环境下安全,这就是一个使用 线程本地存储 的好例子:
import threading
mydata = threading.local()
mydata.nesting = 0
class auto_refresh(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
mydata.nesting += 1
try: return self.f(*args, **kwargs)
finally:
mydata.nesting -= 1
if mydata.nesting == 0:
print 'refreshing'
如果你不关心线程的问题,只要你的 Python 安装是支持线程的(现在几乎所有的安装都是这样),这段代码依然能正常工作。如果你担心有些特殊的 Python 安装不支持线程,可以把 import
语句改成
try:
import threading
except ImportError:
import dummy_threading as threading
大致按照 文档 中的建议来做(除了文档中用了一种奇怪的“私有”名称来表示导入的结果,而这样做其实没有什么特别的理由,所以我就用了一个普通的名称;-)。