Python装饰类中的方法和继承

Posted

技术标签:

【中文标题】Python装饰类中的方法和继承【英文标题】:Python decorating a method in a class and inheritance 【发布时间】:2018-01-16 12:51:48 【问题描述】:

我正在为一个 GUI 应用程序编写一个测试自动化框架,我想使用装饰器来捕获由类中的方法(例如登录)生成的弹出窗口

我有一个_BaseWindow 类,它跟踪每个窗口中的GUI 元素(例如:菜单栏、弹出窗口),它由MainWindow 类继承。 MainWindow 类跟踪主菜单上的按钮,以及单击其中一个按钮时生成的对话框。例如,如果您单击主菜单上的登录按钮,则会加载登录对话框。

class _BaseWindow(object):
    def __init__(self):
        self.window = "windowName"
        self.popup = None

    def _catch_popups(self, method):
        from functools import wraps
        @wraps(method)
        def wrapper(*args, **kwargs):
            # get a list of the open windows before the method is run
            before = getwindowlist()

            retval = method(*args, **kwargs)

            # get a list of the open windows after the method is run
            after = getwindowlist()

            for window in after:
                if window not in before:
                    self.popup = window
                    break

            return retval
        return wrapper

class MainWindow(_BaseWindow):
    def __init__(self):
        self.dialog = None
        self.logged_in = False

        # buttons
        self.login_button = "btnLogin"

        super(MainWindow, self).__init__()

    def click_login(self):
        if not self.dialog:
            mouseclick(self.window, self.login_button)
            self.dialog = LoginDialog()

class LoginDialog(_BaseWindow):
    def __init__(self):
        # buttons
        self.button_ok = "btnLoginOK"
        self.button_cancel = "btnLoginCancel"
        # text input
        self.input_username = "txtLoginUsername"
        self.input_password = "txtLoginPassword"

        super(LoginDialog, self).__init__()

    @MainWindow._catch_popups
    def perform_login(self, username, password):
        input_text(self.input_username, username)
        input_text(self.input_password, password)
        mouseclick(self.window, self.button_ok)

当我尝试对此进行测试时,我得到:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "objects/gui.py", line 185, in <module>
    class LoginDialog(_BaseWindow):
  File "objects/gui.py", line 236, in LoginDialog
    @MainWindow._catch_popups
TypeError: unbound method _catch_popups() must be called with 
MainWindow instance as first argument (got function instance instead)

当我尝试将MainWindow instance 指定为:

@MainWindow._catch_popups(self)

我明白了:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "objects/gui.py", line 185, in <module>
    class LoginDialog(_BaseWindow):
  File "objects/gui.py", line 236, in LoginDialog
    @MainWindow._catch_popups(self)
NameError: name 'self' is not defined

对于理解这一点的任何帮助将不胜感激。

【问题讨论】:

您需要将_catch_popups 设为static method。 【参考方案1】:

这里的结构有错误。在类主体中定义的实例方法与类同时创建,因此是未绑定的方法。因此,在创建时,没有 self 可供参考。您的 _catch_popups 方法需要一个实例。

自然的解决方案是将_catch_popups 更改为返回实例方法的staticmethod

@staticmethod
def _catch_popups(method):
    from functools import wraps
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        before = getwindowlist()
        retval = method(self, *args, **kwargs) # notice that self is passed
        after = getwindowlist()
        try: self.popup = next(w for w in after if w not in before)
        return retval
    return wrapper

这里,self 被显式传递给 method,因为它在传递给装饰器时仍然是一个未绑定的实例方法。


正如您在评论中所建议的,您希望将弹出窗口添加到窗口实例。为此,您可以更新以下方法:

def click_login(self):
    if not self.dialog:
        mouseclick(self.window, self.login_button)
        self.dialog = LoginDialog(self) # pass window to dialog __init__

class LoginDialog(_BaseWindow):
    def __init__(self, parent_window):
        # original code
        self.parent = parent_window

@staticmethod
def _catch_popups(method):
    def wrapper(self, *args, **kwargs):
        # original code
        try: self.parent.popup = ... # add to parent rather than self
        return retval
    return wrapper

【讨论】:

谢谢,这成功了。原来的问题是固定的,但是当我尝试访问 self.popup 时,值与初始化(无)相同,但是当我访问 self.dialog.popup 时,弹出窗口已被捕获。 @AllenMoh 这是意料之中的。您的perform_login 方法在您的对话框类上,因此对话框实例被传递给_catch_popups 创建的实例方法。如果要捕获窗口本身的弹出窗口,则必须为对话框提供对窗口的引用,然后将属性分配给窗口。 @AllenMoh 我添加了一些更新的方法,将这个属性分配给主窗口。 非常感谢您花时间帮助我了解我做错了什么并告诉我正确的方法。使用更新的方法,一切都按我的意愿工作。

以上是关于Python装饰类中的方法和继承的主要内容,如果未能解决你的问题,请参考以下文章

Python 装饰器装饰类中的方法

Python 装饰器装饰类中的方法(转)

python 装饰Python类中的每个方法

Python:如何使用父类中的方法装饰子类中的方法?

重写的方法是不是继承python中的装饰器?

python中装饰器装饰类中的方法