刷新装饰器

1 投票
3 回答
2855 浏览
提问于 2025-04-15 20:55

我正在尝试写一个装饰器,它在被调用后会“刷新”,但是这个刷新只会在最后一个函数执行完后进行一次。下面是一个例子:

@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 参数意味着默认 refreshFalse。有没有人想到办法可以改变这个,使得在不指定时 refreshTrue?改变

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 个回答

0

我不太清楚你想用这个设计做什么,所以也不知道这样做是否合适。你可以考虑一下这是不是正确的方法。

不过,我觉得这个设计可以满足你的要求。一个公共对象(在这里叫做 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
1

我觉得维护一个“嵌套刷新计数”可能会更简单。这个计数在每次调用带有刷新功能的代码之前加一,在之后减一(可以放在一个最终执行的代码块里,这样计数就不会出错)。当这个计数变为零的时候,就运行刷新程序。

5

要计算“嵌套”的数量,并且确保在多线程环境下安全,这就是一个使用 线程本地存储 的好例子:

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

大致按照 文档 中的建议来做(除了文档中用了一种奇怪的“私有”名称来表示导入的结果,而这样做其实没有什么特别的理由,所以我就用了一个普通的名称;-)。

撰写回答