【问题标题】:Refresh decorator刷新装饰器
【发布时间】:2010-03-27 14:22:05
【问题描述】:

我正在尝试编写一个在被调用后“刷新”的装饰器,但在最后一个函数退出后刷新只发生一次。这是一个例子:

@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 参数意味着刷新默认为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

因为 refresh 在第一种和第二种情况下被调用多次,在最后一种情况下被调用一次(在第一种和第二种情况下应该调用一次,而不是在最后一种情况下)。

【问题讨论】:

  • “刷新一个装饰器”?为什么你有有状态的装饰器?这非常非常令人困惑。您是否正在尝试创建某种可调用对象?
  • 刷新可以是例如用于显示 - 所以你可以想象一个 GUI 程序,你不想在每次函数调用后刷新,但你想在函数的“树”被调用后刷新,无论启动函数是什么。
  • 这不是装饰器的用途。这就是适当的 GUI 对象的用途。有状态的装饰器是一个非常令人困惑的想法,它与装饰器的普通用途根本不同,以至于成为维护的噩梦。
  • 在我的情况下,我无法控制 GUI 代码 - 我必须强制 GUI 刷新,但要防止每个函数调用多次刷新。

标签: python algorithm decorator


【解决方案1】:

以线程安全的方式计算“嵌套”的数量是使用 thread-local storage 的一个很好的例子:

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

大致按照the docs 中的建议(除了文档对导入结果使用特殊的“私有”名称,并且没有真正的原因,所以我使用的是普通名称;-)。

【讨论】:

  • 如果不使用反风险工具,例如如果用户从 ipython 运行这些函数,并且 control-C 执行,那么计数器不会归零的风险不是吗?
  • 'finally' 会处理过早的中断吗?
  • @Morgoth, finally 保证终结块的执行(除非 Python 崩溃,例如由于某些其他进程执行 kill,或者以其他方式突然终止,例如使用 os._exit)是否try 块正常执行或引发或传播异常(包括KeyboardInterrupt)。
【解决方案2】:

我认为维护一个“嵌套刷新计数”可能更简单,该计数在每次刷新装饰调用之前递增并在之后递减(在 finally 块中,以免计数混乱);每当计数为零时运行刷新例程。

【讨论】:

  • 在上面的例子中,函数 a() 和 b() 的装饰器实际上是两个独立的实例,所以一个装饰器的计数器对另一个装饰器是不可用的。你会如何建议这样做?
  • 将计数器对象传递给装饰器?
【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-07-17
    • 2015-11-08
    • 1970-01-01
    • 2017-04-22
    • 1970-01-01
    • 2011-09-03
    • 2015-11-18
    • 1970-01-01
    相关资源
    最近更新 更多