函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
常见的如实时检索框,每次输入字符都会导致推荐的结果改变,但是实际上只需要输入完成后的文本。如果不设置防抖,会每次输入一个字符就搜索一次数据库,导致性能浪费。
假设有以下需求:给qt中3个列表的currentRowChanged绑定三个变量的设置,可以通过函数工厂的方式只写一个函数完成三个操作
def row_change(list_idx):
def set_param(row_idx):
param[list_idx] = row_idx
return set_param
# 代码片段
param = [1, 3, 2]
listwidget_0.currentRowChanged.connect(row_change(0))
listwidget_1.currentRowChanged.connect(row_change(1))
listwidget_2.currentRowChanged.connect(row_change(2))
当把上面例子中的list_idx换成函数作为输入,就得到了装饰器。假设需要计算函数运行时间,但是又不想修改函数的内容,可以用到装饰器
import time
def timeit(func):
def decorator(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print('finished in {} seconds'.format(end-start))
return decorator
def test(x):
while 1:
x += 1
if x > 10000:
break
为了实现函数计时,共有两种写法
decorated_test = timeit(func=test)
print(decorated_test)
decorated_test(2)
# 输出
# .decorator at 0x000001F44858C1F0>
# finished in 0.002038717269897461 seconds
第一种最容易理解,将test函数作为timeit函数的输入,timeit函数返回decorator函数,所以decorated_test是decorator,调用decorated_test(2)实际上是调用decorator(2)
@timeit
def test(x):
for i in range(10000):
x += 1
print(test)
test(2)
# 输出
# .decorator at 0x000001F44858C1F0>
# finished in 0.002038717269897461 seconds
第二种在test函数定义时加了一句@timeit,然后在运行时也不需要调用timeit函数,直接用test(2)就可以完成。但是实际上这个和第一种方法是等效的,print(test)可以看出虽然名字是test,但实际上函数还是timeit中的decorator函数
在上述例子中,timeit即为装饰器,可以通过在需要装饰的函数前加@timeit装饰函数,装饰器可以为函数,也可以是类,还可以是类的方法,这里介绍的函数防抖就是用的类的方法作为装饰器。
import threading
class Debounce:
def __init__(self, interval):
self.interval = interval
self.debounced = None
def __call__(self, func):
def decorator(*args, **kwargs):
if self.debounced is not None:
self.debounced.cancel()
self.debounced = threading.Timer(self.interval, func, args, kwargs)
self.debounced.start()
return decorator
@Debounce(0.01)
def test(x,y,z):
print(x,y,z)
for i in range(10):
for j in range(10):
for k in range(10):
test(i,j,k)
# 输出
# 9 9 9
此处,在@Debounce后还加了参数,实际上是@的一个实例化的类,此时调用test(i,j,k)等效于
Debounce(0.01)(i,j,k)
调用的是Debounce的__call__方法,其中这个类的interval是0.01。在每次__call__时,会取消前面的print(x,y,z)线程,并创建一个0.01秒后执行的print(x,y,z)线程。因此,在0.01秒内调用的下一次test函数会取消前面的还未执行的线程,直到最后一次调用test(9,9,9)时,创建线程print(9,9,9),因为后面没有再调用test,因此不会取消这次线程,最终成功执行。