From 6f598b1157069399a817405fe0a6ddea9fc78f24 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sun, 31 Jul 2016 15:02:30 +0200 Subject: [PATCH] Use the memoize function for faked arguments only when needed. It's important to note that memoizing every object would mean that theoretically all objects passed through get_faked would get memoized. This would have been a possible memory leak, which we should avoid. Obviously the previous solution proposed in #649 was still better, but this issue was a new one. Also using str() around keys was not a good idea. Refs #649. --- jedi/cache.py | 12 --------- jedi/evaluate/compiled/__init__.py | 29 +++++++++++----------- jedi/evaluate/compiled/fake.py | 39 +++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/jedi/cache.py b/jedi/cache.py index 5f618375..8dc82544 100644 --- a/jedi/cache.py +++ b/jedi/cache.py @@ -91,18 +91,6 @@ def memoize_method(method): return wrapper -def memoize_function(obj): - """ A normal memoize function for memoizing free functions. """ - cache = obj.cache = {} - - def memoizer(*args, **kwargs): - key = str(args) + str(kwargs) - if key not in cache: - cache[key] = obj(*args, **kwargs) - return cache[key] - return memoizer - - def cache_star_import(func): @time_cache("star_import_cache_validity") def wrapper(self): diff --git a/jedi/evaluate/compiled/__init__.py b/jedi/evaluate/compiled/__init__.py index 7d451e6d..2660734b 100644 --- a/jedi/evaluate/compiled/__init__.py +++ b/jedi/evaluate/compiled/__init__.py @@ -222,10 +222,12 @@ class CompiledObject(Base): module = self.get_parent_until() faked_subscopes = [] for name in dir(self.obj): - f = fake.get_faked(module.obj, self.obj, name) - if f: - f.parent = self - faked_subscopes.append(f) + try: + faked_subscopes.append( + fake.get_faked(module.obj, self.obj, parent=self, name=name) + ) + except fake.FakeDoesNotExist: + pass return faked_subscopes def is_scope(self): @@ -445,16 +447,15 @@ def _parse_function_doc(doc): def _create_from_name(evaluator, module, parent, name): - faked = fake.get_faked(module.obj, parent.obj, name) - # only functions are necessary. - if faked is not None: - faked.parent = parent - return faked + try: + return fake.get_faked(module.obj, parent.obj, parent=parent, name=name) + except fake.FakeDoesNotExist: + pass try: obj = getattr(parent.obj, name) except AttributeError: - # happens e.g. in properties of + # Happens e.g. in properties of # PyQt4.QtGui.QStyleOptionComboBox.currentText # -> just set it to None obj = None @@ -527,9 +528,9 @@ def create(evaluator, obj, parent=None, module=None): if parent is None and obj != _builtins: return create(evaluator, obj, create(evaluator, _builtins)) - faked = fake.get_faked(module and module.obj, obj) - if faked is not None: - faked.parent = parent - return faked + try: + return fake.get_faked(module and module.obj, obj, parent=parent) + except fake.FakeDoesNotExist: + pass return CompiledObject(evaluator, obj, parent) diff --git a/jedi/evaluate/compiled/fake.py b/jedi/evaluate/compiled/fake.py index 9a422c11..0338b962 100644 --- a/jedi/evaluate/compiled/fake.py +++ b/jedi/evaluate/compiled/fake.py @@ -9,7 +9,6 @@ import inspect import types from jedi._compatibility import is_py3, builtins, unicode, is_py34 -from jedi.cache import memoize_function from jedi.parser import ParserWithRecovery, load_grammar from jedi.parser import tree as pt from jedi.evaluate.helpers import FakeName @@ -44,6 +43,10 @@ if is_py3: NOT_CLASS_TYPES += (types.DynamicClassAttribute,) +class FakeDoesNotExist(Exception): + pass + + def _load_faked_module(module): module_name = module.__name__ if module_name == '__builtin__' and not is_py3: @@ -143,13 +146,35 @@ def _faked(module, obj, name): return search_scope(cls, name) -@memoize_function -def get_faked(module, obj, name=None): +def memoize_faked(obj): + """ + A typical memoize function that ignores issues with non hashable results. + """ + cache = obj.cache = {} + + def memoizer(*args, **kwargs): + key = (obj, args, frozenset(kwargs.items())) + try: + result = cache[key] + except TypeError: + return obj(*args, **kwargs) + except KeyError: + result = obj(*args, **kwargs) + if result is not None: + cache[key] = obj(*args, **kwargs) + return result + else: + return result + return memoizer + + +@memoize_faked +def _get_faked(module, obj, name=None): obj = type(obj) if is_class_instance(obj) else obj result = _faked(module, obj, name) if result is None or isinstance(result, pt.Class): # We're not interested in classes. What we want is functions. - return None + raise FakeDoesNotExist else: # Set the docstr which was previously not set (faked modules don't # contain it). @@ -162,6 +187,12 @@ def get_faked(module, obj, name=None): return result +def get_faked(module, obj, name=None, parent=None): + faked = _get_faked(module, obj, name) + faked.parent = parent + return faked + + def is_class_instance(obj): """Like inspect.* methods.""" try: