pywinauto核心代码理解

    def __getattribute__(self, attr_name):
        """
        Attribute access for this class

        If we already have criteria for both dialog and control then
        resolve the control and return the requested attribute.

        If we have only criteria for the dialog but the attribute
        requested is an attribute of DialogWrapper then resolve the
        dialog and return the requested attribute.

        Otherwise delegate functionality to :func:`__getitem__` - which
        sets the appropriate criteria for the control.
        """
        allow_magic_lookup = object.__getattribute__(self, "allow_magic_lookup")  # Beware of recursions here!
        if not allow_magic_lookup:
            try:
                return object.__getattribute__(self, attr_name)
            except AttributeError:
                wrapper_object = self.wrapper_object() # 这里是重点,需要看对应函数的实现代码
                try:
                    return getattr(wrapper_object, attr_name)
                except AttributeError:
                    message = (
                        'Attribute "%s" exists neither on %s object nor on'
                        'targeted %s element wrapper (typo? or set allow_magic_lookup to True?)' %
                        (attr_name, self.__class__, wrapper_object.__class__))
                    raise AttributeError(message)

        if attr_name in ['__dict__', '__members__', '__methods__', '__class__', '__name__']:
            return object.__getattribute__(self, attr_name)

        if attr_name in dir(self.__class__):
            return object.__getattribute__(self, attr_name)

        if attr_name in self.__dict__:
            return self.__dict__[attr_name]

        # if we already have 2 levels of criteria (dlg, control)
        # this third must be an attribute so resolve and get the
        # attribute and return it
        if len(self.criteria) >= 2:  # FIXME - this is surprising

            ctrls = self.__resolve_control(self.criteria)

            try:
                return getattr(ctrls[-1], attr_name)
            except AttributeError:
                return self.child_window(best_match=attr_name)
        else:
            # FIXME - I don't get this part at all, why is it win32-specific and why not keep the same logic as above?
            # if we have been asked for an attribute of the dialog
            # then resolve the window and return the attribute
            desktop_wrapper = self.backend.generic_wrapper_class(self.backend.element_info_class())
            need_to_resolve = (len(self.criteria) == 1 and hasattr(desktop_wrapper, attr_name))
            if hasattr(self.backend, 'dialog_class'):
                need_to_resolve = need_to_resolve and hasattr(self.backend.dialog_class, attr_name)
            # Probably there is no DialogWrapper for another backend

            if need_to_resolve:
                ctrls = self.__resolve_control(self.criteria)
                return getattr(ctrls[-1], attr_name)

        # It is a dialog/control criterion so let getitem
        # deal with it
        return self[attr_name]
    def __get_ctrl(self, criteria_):
        """Get a control based on the various criteria"""
        # make a copy of the criteria
        criteria = [crit.copy() for crit in criteria_]
        # find the dialog
        if 'backend' not in criteria[0]:
            criteria[0]['backend'] = self.backend.name
        if self.app is not None:
            # find_elements(...) accepts only "process" argument
            criteria[0]['process'] = self.app.process
            del criteria[0]['app']
        dialog = self.backend.generic_wrapper_class(findwindows.find_element(**criteria[0]))

        ctrls = []
        # if there is only criteria for a dialog then return it
        if len(criteria) > 1:
            # so there was criteria for a control, add the extra criteria
            # that are required for child controls
            previous_parent = dialog.element_info
            for ctrl_criteria in criteria[1:]:
                ctrl_criteria["top_level_only"] = False
                if "parent" not in ctrl_criteria:
                    ctrl_criteria["parent"] = previous_parent

                if isinstance(ctrl_criteria["parent"], WindowSpecification):
                    ctrl_criteria["parent"] = ctrl_criteria["parent"].wrapper_object()

                # resolve the control and return it
                if 'backend' not in ctrl_criteria:
                    ctrl_criteria['backend'] = self.backend.name
                ctrl = self.backend.generic_wrapper_class(findwindows.find_element(**ctrl_criteria))
                previous_parent = ctrl.element_info
                ctrls.append(ctrl)

        if ctrls:
            return (dialog, ) + tuple(ctrls)
        else:
            return (dialog, )

    def __resolve_control(self, criteria, timeout=None, retry_interval=None):
        """
        Find a control using criteria

        * **criteria** - a list that contains 1 or 2 dictionaries

             1st element is search criteria for the dialog

             2nd element is search criteria for a control of the dialog

        * **timeout** -  maximum length of time to try to find the controls (default 5)
        * **retry_interval** - how long to wait between each retry (default .2)
        """
        if timeout is None:
            timeout = Timings.window_find_timeout
        if retry_interval is None:
            retry_interval = Timings.window_find_retry

        try:
            ctrl = wait_until_passes(
                timeout,
                retry_interval,
                self.__get_ctrl, # 传入了一个函数接口
                (findwindows.ElementNotFoundError,
                 findbestmatch.MatchError,
                 controls.InvalidWindowHandle,
                 controls.InvalidElement),
                criteria)

        except TimeoutError as e:
            raise e.original_exception

        return ctrl

    def wrapper_object(self):
        """Allow the calling code to get the HwndWrapper object"""
        ctrls = self.__resolve_control(self.criteria)
        return ctrls[-1]
#=========================================================================
def wait_until_passes(timeout,
                      retry_interval,
                      func,
                      exceptions=(Exception),
                      *args, **kwargs):
    """
    Wait until ``func(*args, **kwargs)`` does not raise one of the exceptions

    * **timeout**  how long the function will try the function
    * **retry_interval**  how long to wait between retries
    * **func** the function that will be executed
    * **exceptions**  list of exceptions to test against (default: Exception)
    * **args** optional arguments to be passed to func when called
    * **kwargs** optional keyword arguments to be passed to func when called

    Returns the return value of the function
    If the operation times out then the original exception raised is in
    the 'original_exception' attribute of the raised exception.

    e.g. ::

        try:
            # wait a maximum of 10.5 seconds for the
            # window to be found in increments of .5 of a second.
            # P.int a message and re-raise the original exception if never found.
            wait_until_passes(10.5, .5, self.Exists, (ElementNotFoundError))
        except TimeoutError as e:
            print("timed out")
            raise e.
    """
    start = timestamp()

    # keep trying until the timeout is passed
    while True:
        try:
            # Call the function with any arguments
            func_val = func(*args, **kwargs)

            # if no exception is raised then we are finished
            break

        # An exception was raised - so wait and try again
        except exceptions as e:

            # find out how much of the time is left
            time_left = timeout - (timestamp() - start)

            # if we have to wait some more
            if time_left > 0:
                # wait either the retry_interval or else the amount of
                # time until the timeout expires (whichever is less)
                time.sleep(min(retry_interval, time_left))

            else:
                # Raise a TimeoutError - and put the original exception
                # inside it
                err = TimeoutError()
                err.original_exception = e
                raise err

    # return the function value
    return func_val
#=========================================================================
def find_element(**kwargs):
    """
    Call find_elements and ensure that only one element is returned

    Calls find_elements with exactly the same arguments as it is called with
    so please see :py:func:`find_elements` for the full parameters description.
    """
    elements = find_elements(**kwargs)

    if not elements:
        raise ElementNotFoundError(kwargs)

    if len(elements) > 1:
        exception = ElementAmbiguousError(
            "There are {0} elements that match the criteria {1}".format(
                len(elements),
                six.text_type(kwargs),
            )
        )

        exception.elements = elements
        raise exception

    return elements[0]

以下是我的理解:
1. 通过child_window()无条件的返回了一个pywinauto.application.WindowSpecification对象,这个对象只是封装了对应的查找条件,拿着这个返回对象还不能知道是否成功查找到窗口;

2. WindowSpecification对象获取其wapper_object()时才开始真正查找。wrapper_object() -> _resolve_control() -> wait_until_passes() -> _get_ctrl() -> findwindows.find_element()
3. wait_until_passes()中使用了总超时时间和重试间隔,当总时间超时时会抛出Timeout异常。如果__get_ctrl()返回了正常值就立即返回;
4.wait_until_passes会捕获_resolve_control()中指定的(findwindows.ElementNotFoundError,
                 findbestmatch.MatchError,
                 controls.InvalidWindowHandle,
                 controls.InvalidElement)几种类型的异常,当出现这些异常时会继续进行重试。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值