From 0139ecf3713e6f190fde5e6efd59cb880cec3301 Mon Sep 17 00:00:00 2001 From: markm Date: Tue, 25 Apr 2006 20:45:17 +0000 Subject: [PATCH] Some caching for optimization --- pywinauto/controls/HwndWrapper.py | 68 +++++++++++++++-------- pywinauto/findbestmatch.py | 89 ++++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 47 deletions(-) diff --git a/pywinauto/controls/HwndWrapper.py b/pywinauto/controls/HwndWrapper.py index b095f4c..fc0c301 100644 --- a/pywinauto/controls/HwndWrapper.py +++ b/pywinauto/controls/HwndWrapper.py @@ -130,7 +130,7 @@ class HwndWrapper(object): C function - and it will get converted to a Long with the value of it's handle (see ctypes, _as_parameter_)""" - friendlyclassname = '' + friendlyclassname = None handle = None #----------------------------------------------------------- @@ -143,6 +143,7 @@ class HwndWrapper(object): If the handle is not valid then an InvalidWindowHandle error is raised. """ + # handle if hwnd is actually a HwndWrapper try: self.handle = hwnd.handle @@ -156,6 +157,8 @@ class HwndWrapper(object): # make it so that ctypes conversion happens correctly self._as_parameter_ = self.handle + #win32functions.WaitGuiThreadIdle(self) + # specify whether we need to grab an image of ourselves # when asked for properties self._NeedsImageProp = False @@ -165,6 +168,8 @@ class HwndWrapper(object): self.appdata = None + self._cache = {} + # build the list of default properties to be written # Derived classes can either modify this list or override # GetProperties depending on how much control they need. @@ -200,15 +205,16 @@ class HwndWrapper(object): For example Checkboxes are implemented as Buttons - so the class of a CheckBox is "Button" - but the friendly class is "CheckBox" """ - if not self.friendlyclassname: - return handleprops.classname(self) - else: - return self.friendlyclassname + if self.friendlyclassname is None: + self.friendlyclassname = handleprops.classname(self) + return self.friendlyclassname #----------------------------------------------------------- def Class(self): """Return the class name of the window""" - return handleprops.classname(self) + if not self._cache.has_key("class"): + self._cache['class'] = handleprops.classname(self) + return self._cache['class'] #----------------------------------------------------------- def WindowText(self): @@ -377,7 +383,11 @@ class HwndWrapper(object): #----------------------------------------------------------- def IsDialog(self): "Return true if the control is a top level window" - return handleprops.is_toplevel_window(self) + + if not self._cache.has_key("isdialog"): + self._cache['isdialog'] = handleprops.is_toplevel_window(self) + + return self._cache['isdialog'] #----------------------------------------------------------- def Parent(self): @@ -390,12 +400,19 @@ class HwndWrapper(object): To get the main (or top level) window then use HwndWrapper.TopLevelParent(). """ - parent_hwnd = handleprops.parent(self) - if parent_hwnd: - return WrapHandle(parent_hwnd) - else: - return None + if not self._cache.has_key("parent"): + + parent_hwnd = handleprops.parent(self) + + if parent_hwnd: + #return WrapHandle(parent_hwnd) + + self._cache["parent"] = WrapHandle(parent_hwnd) + else: + self._cache["parent"] = None + + return self._cache["parent"] #----------------------------------------------------------- def TopLevelParent(self): @@ -410,18 +427,27 @@ class HwndWrapper(object): no top level parent then the control itself is returned - as it is a top level window already!) """ - if self.IsDialog(): - return self - parent = self.Parent() + if not self._cache.has_key("top_level_parent"): - if not parent: - return self + parent = self.Parent() - if not parent.IsDialog(): - return parent.TopLevelParent() - else: - return parent + if self.IsDialog(): + self._cache["top_level_parent"] = self + #return self + + elif not parent: + self._cache["top_level_parent"] = self + #return self + + elif not parent.IsDialog(): + self._cache["top_level_parent"] = parent.TopLevelParent() + #return parent.TopLevelParent() + else: + self._cache["top_level_parent"] = parent + #return parent + + return self._cache["top_level_parent"] #----------------------------------------------------------- def Texts(self): diff --git a/pywinauto/findbestmatch.py b/pywinauto/findbestmatch.py index df82483..4e2aa61 100644 --- a/pywinauto/findbestmatch.py +++ b/pywinauto/findbestmatch.py @@ -51,7 +51,7 @@ class MatchError(IndexError): "Could not find '%s' in '%s'"% (tofind, self.items)) - +_cache = {} # given a list of texts return the match score for each # and the best score and text with best score @@ -67,17 +67,32 @@ def _get_match_ratios(texts, match_against): best_ratio = 0 best_text = '' + global cache + for text in texts: - # set up the SequenceMatcher with other text - ratio_calc.set_seq2(text) - # try using the levenshtein distance instead - #lev_dist = levenshtein_distance(unicode(match_against), unicode(text)) - #ratio = 1 - lev_dist / 10.0 - #ratios[text] = ratio + if 0: + pass - # calculate ratio and store it - ratios[text] = ratio_calc.ratio() + if (text, match_against) in _cache: + ratios[text] = _cache[(text, match_against)] + + elif(match_against, text) in _cache: + ratios[text] = _cache[(match_against, text)] + + else: + # set up the SequenceMatcher with other text + ratio_calc.set_seq2(text) + + # try using the levenshtein distance instead + #lev_dist = levenshtein_distance(unicode(match_against), unicode(text)) + #ratio = 1 - lev_dist / 10.0 + #ratios[text] = ratio + + # calculate ratio and store it + ratios[text] = ratio_calc.ratio() + + _cache[(match_against, text)] = ratios[text] # if this is the best so far then update best stats if ratios[text] > best_ratio: @@ -325,6 +340,13 @@ class UniqueDict(dict): best_ratio = 0 best_texts = [] + ratio_offset = 1 + if clean: + ratio_offset *= .9 + + if ignore_case: + ratio_offset *= .9 + for text_ in self: # make a copy of the text as we need the original later @@ -336,12 +358,31 @@ class UniqueDict(dict): if ignore_case: text = text.lower() - # set up the SequenceMatcher with other text - ratio_calc.set_seq2(text) + # check if this item is in the cache - if yes, then retrieve it + if (text, search_text) in _cache: + ratios[text_] = _cache[(text, search_text)] - # calculate ratio and store it - ratios[text_] = ratio_calc.ratio() + elif(search_text, text) in _cache: + ratios[text_] = _cache[(search_text, text)] + # not in the cache - calculate it and add it to the cache + else: + # set up the SequenceMatcher with other text + ratio_calc.set_seq2(text) + + # if a very quick check reveals that this is not going + # to match then + ratio = ratio_calc.real_quick_ratio() + + if ratio * ratio_offset > find_best_control_match_cutoff: + ratio = ratio_calc.quick_ratio() * ratio_offset + + if ratio * ratio_offset > find_best_control_match_cutoff: + ratio = ratio_calc.ratio() + + # save the match we got and store it in the cache + ratios[text_] = ratio + _cache[(text, search_text)] = ratio # try using the levenshtein distance instead #lev_dist = levenshtein_distance(unicode(search_text), unicode(text)) @@ -357,11 +398,7 @@ class UniqueDict(dict): elif ratios[text_] == best_ratio: best_texts.append(text_) - if clean: - best_ratio *= .9 - - if ignore_case: - best_ratio *= .9 + best_ratio *= ratio_offset return best_ratio, best_texts @@ -404,14 +441,14 @@ def find_best_control_matches(search_text, controls): name_control_map = build_unique_dict(controls) - # collect all the possible names for all controls - # and build a list of them - for ctrl in controls: - ctrl_names = get_control_names(ctrl, controls) - - # for each of the names - for name in ctrl_names: - name_control_map[name] = ctrl +# # collect all the possible names for all controls +# # and build a list of them +# for ctrl in controls: +# ctrl_names = get_control_names(ctrl, controls) +# +# # for each of the names +# for name in ctrl_names: +# name_control_map[name] = ctrl best_ratio, best_texts = name_control_map.FindBestMatches(search_text)