装饰器替换重复的面向对象的代码?

时间:2013-06-17 11:33:58

标签: python decorator pyside

我正在使用Qt / PySide开发一个GUI,其中包含许多处理各种小部件的独立类。每个小部件管理按钮和其他用户输入之间的信号。我发现自己必须重复使用代码来阻止方法函数开始时的窗口小部件信号,然后在结束时释放信号。我决定尝试编写一个通用装饰器为我做这个。

我搜索过SO并尝试使用装饰器的经验很少实现这一点,所以我对我的解决方案不满意。

我的问题是,编写可以访问和运行该类中遵循明确格式的方法的通用装饰器的最佳方法是什么?无论如何,我的方法是一个好方法吗?

为清楚起见,这里是我的代码与重复代码的相似之处(为简洁起见我删除了一些代码):

class WidgetController(...):

    def __init__(...):
       self.widget.myWidget.currentIndexChanged.connect(reactToChange)

    def reactToChange(...):
        self.widget.myWidget.blockSignals(True) # Repetetive line...
        ...
        self.widget.myWidget.blockSignals(False)

    def anotherFunction(...):
        self.widget.anotherWidget.blockSignals(True)
        ...
        self.widget.anotherWidget.blockSignals(False)

我想要以下内容:

class WidgetController(...):

   @blockSignals(myWidget)
   def reactToChange(...):
      ...

   @blockSignals(anotherWidget, alsoBlockThisWidget)
   def anotherFunction(...):
      ...

我开发了一个装饰器(在hereherehere的帮助下)但是我很犹豫,因为它感觉非常笨重。它使用self我不理解它和嵌套函数中的exec,并且需要将小部件名称作为字符串传递,但它似乎确实有效。这是:

class BlockSignals(object):

    def __init__(self, *toBeBlocked):
        self.toBeBlocked = toBeBlocked

    def __call__(self, f):
        toBeBlocked = self.toBeBlocked
        def wrapped_f(self, *args):
            for b in toBeBlocked:
                exec 'self.widget.' + b + '.blockSignals(False)' in locals()
            f(self, *args)
            for b in toBeBlocked:
                exec 'self.widget.' + b + '.blockSignals(False)' in locals()
        return wrapped_f

用法:

class WidgetController(...):

   @BlockSignals("myWidget")
   def reactToChange(...):
      ...

   @BlockSignals("anotherWidget", "alsoBlockThisWidget")
   def anotherFunction(...):

正如你所看到的,它并不漂亮。我希望能够摆脱字符串解析,摆脱exec,理清令人困惑的self并能够通过传递实际的widget对象来实现它{ {1}}。不幸的是,我已经达到了我的能力极限,是否有人能够提供帮助?

1 个答案:

答案 0 :(得分:2)

您正在寻找getattr

import functools

def blockSignals(*widgetnames):
    def decorator(func):
        @functool.wraps(func)
        def method(self, *args, **kwargs):
            widgets = [getattr(self.widget, name) for name in widgetnames]
            for widget in widgets:
                widget.blockSignals(True) 
            result = func(self, *args, **kwargs)
            for widget in widgets:
                widget.blockSignals(False)
            return result
        return method
    return decorator

class WidgetController(...):

    def __init__(...):
       self.widget.myWidget.currentIndexChanged.connect(reactToChange)

    @blockSignals('myWidget')
    def reactToChange(...):
        ...

    @blockSignals('anotherWidget', 'alsoBlockThisWidget')
    def anotherFunction(...):
        ...

您必须传递窗口小部件的名称,而不是窗口小部件本身,因为这些方法是在定义时定义的,而不是在时>实例被实例化。实例selfself.widget中的实际小部件在类定义时不存在。

functools.wraps装饰器将原始函数及其docstring的名称复制到装饰函数。