diff --git a/jedi/api.py b/jedi/api.py index 248991b4..5e7c1109 100644 --- a/jedi/api.py +++ b/jedi/api.py @@ -442,7 +442,7 @@ class Script(object): return match.groups() def __del__(self): - evaluate.clear_caches() + api_classes._clear_caches() def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, diff --git a/jedi/api_classes.py b/jedi/api_classes.py index 367615a9..793f806a 100644 --- a/jedi/api_classes.py +++ b/jedi/api_classes.py @@ -3,6 +3,9 @@ import re import os +import cache +import dynamic +import helpers import settings import evaluate import imports @@ -10,6 +13,20 @@ import parsing import keywords +def _clear_caches(): + """ + Clears all caches of this and related modules. The only cache that will not + be deleted is the module cache. + """ + cache.clear_caches() + dynamic.search_param_cache.clear() + helpers.ExecutionRecursionDecorator.reset() + + evaluate.follow_statement.reset() + + imports.imports_processed = 0 + + class BaseDefinition(object): _mapping = {'posixpath': 'os.path', 'riscospath': 'os.path', @@ -193,7 +210,7 @@ class Completion(BaseDefinition): self._followed_definitions = \ [BaseDefinition(d, d.start_pos) for d in defs] - evaluate.clear_caches() + _clear_caches() return self._followed_definitions diff --git a/jedi/cache.py b/jedi/cache.py new file mode 100644 index 00000000..5d882fd9 --- /dev/null +++ b/jedi/cache.py @@ -0,0 +1,53 @@ +memoize_caches = [] +faked_scopes = [] + + +def clear_caches(): + """ Jedi caches many things, that should be completed after each completion + finishes. + """ + global memoize_caches, faked_scopes + + # memorize_caches must never be deleted, because the dicts will get lost in + # the wrappers. + for m in memoize_caches: + m.clear() + + faked_scopes = [] # TODO really needed anymore? weakrefs are gone... + + +def memoize_default(default=None): + """ This is a typical memoization decorator, BUT there is one difference: + To prevent recursion it sets defaults. + + Preventing recursion is in this case the much bigger use than speed. I + don't think, that there is a big speed difference, but there are many cases + where recursion could happen (think about a = b; b = a). + """ + def func(function): + memo = {} + memoize_caches.append(memo) + + def wrapper(*args, **kwargs): + key = (args, frozenset(kwargs.items())) + if key in memo: + return memo[key] + else: + memo[key] = default + rv = function(*args, **kwargs) + memo[key] = rv + return rv + return wrapper + return func + + +class CachedMetaClass(type): + """ This is basically almost the same than the decorator above, it just + caches class initializations. I haven't found any other way, so I do it + with meta classes. + """ + @memoize_default() + def __call__(self, *args, **kwargs): + return super(CachedMetaClass, self).__call__(*args, **kwargs) + + diff --git a/jedi/docstrings.py b/jedi/docstrings.py index 29bf0d29..b960b606 100644 --- a/jedi/docstrings.py +++ b/jedi/docstrings.py @@ -5,7 +5,7 @@ import re import evaluate -#@evaluate.memoize_default() # TODO add +#@cache.memoize_default() # TODO add def follow_param(param): func = param.parent_function #print func, param, param.parent_function diff --git a/jedi/dynamic.py b/jedi/dynamic.py index 2e775462..83476186 100644 --- a/jedi/dynamic.py +++ b/jedi/dynamic.py @@ -9,6 +9,7 @@ from __future__ import with_statement import os +import cache import parsing import modules import evaluate @@ -93,7 +94,7 @@ class ParamListener(object): self.param_possibilities.append(params) -@evaluate.memoize_default([]) +@cache.memoize_default([]) def search_params(param): """ This is a dynamic search for params. If you try to complete a type: @@ -223,7 +224,7 @@ def dec(func): #@dec -@evaluate.memoize_default([]) +@cache.memoize_default([]) def _check_array_additions(compare_array, module, is_list): """ Checks if a `parsing.Array` has "add" statements: diff --git a/jedi/evaluate.py b/jedi/evaluate.py index 5b3d2126..13e2f319 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -18,6 +18,7 @@ import sys import itertools import copy +import cache import parsing import debug import builtin @@ -26,9 +27,6 @@ import helpers import dynamic import docstrings -memoize_caches = [] -faked_scopes = [] - class DecoratorNotFound(LookupError): """ @@ -63,66 +61,6 @@ class MultiLevelAttributeError(Exception): return 'Original:\n\n' + ''.join(tb) -def clear_caches(): - """ - Clears all caches of this and related modules. Jedi caches many things, - that should be completed after each completion finishes. The only things - that stays is the module cache (which is not deleted here). - """ - global memoize_caches, faked_scopes - - for m in memoize_caches: - m.clear() - - dynamic.search_param_cache.clear() - helpers.ExecutionRecursionDecorator.reset() - - # memorize_caches must never be deleted, because the dicts will get lost in - # the wrappers. - faked_scopes = [] - - follow_statement.reset() - - imports.imports_processed = 0 - - -def memoize_default(default=None): - """ - This is a typical memoization decorator, BUT there is one difference: - To prevent recursion it sets defaults. - - Preventing recursion is in this case the much bigger use than speed. I - don't think, that there is a big speed difference, but there are many cases - where recursion could happen (think about a = b; b = a). - """ - def func(function): - memo = {} - memoize_caches.append(memo) - - def wrapper(*args, **kwargs): - key = (args, frozenset(kwargs.items())) - if key in memo: - return memo[key] - else: - memo[key] = default - rv = function(*args, **kwargs) - memo[key] = rv - return rv - return wrapper - return func - - -class CachedMetaClass(type): - """ - This is basically almost the same than the decorator above, it just caches - class initializations. I haven't found any other way, so I do it with meta - classes. - """ - @memoize_default() - def __call__(self, *args, **kwargs): - return super(CachedMetaClass, self).__call__(*args, **kwargs) - - class Executable(parsing.Base): """ An instance is also an executable - because __init__ is called """ def __init__(self, base, var_args=None): @@ -140,7 +78,7 @@ class Executable(parsing.Base): return self.base.parent -class Instance(use_metaclass(CachedMetaClass, Executable)): +class Instance(use_metaclass(cache.CachedMetaClass, Executable)): """ This class is used to evaluate instances. """ def __init__(self, base, var_args=None): super(Instance, self).__init__(base, var_args) @@ -159,7 +97,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): # (No var_args) used. self.is_generated = False - @memoize_default() + @cache.memoize_default() def get_init_execution(self, func): func = InstanceElement(self, func, True) return Execution(func, self.var_args) @@ -227,7 +165,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): args = helpers.generate_param_array(v) return self.execute_subscope_by_name('__get__', args) - @memoize_default([]) + @cache.memoize_default([]) def get_defined_names(self): """ Get the instance vars of a class. This includes the vars of all @@ -273,7 +211,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): (type(self).__name__, self.base, len(self.var_args or [])) -class InstanceElement(use_metaclass(CachedMetaClass)): +class InstanceElement(use_metaclass(cache.CachedMetaClass)): """ InstanceElement is a wrapper for any object, that is used as an instance variable (e.g. self.variable or class methods). @@ -288,7 +226,7 @@ class InstanceElement(use_metaclass(CachedMetaClass)): self.is_class_var = is_class_var @property - @memoize_default() + @cache.memoize_default() def parent(self): par = self.var.parent if isinstance(par, Class) and par == self.instance.base \ @@ -317,8 +255,8 @@ class InstanceElement(use_metaclass(CachedMetaClass)): par = InstanceElement(self.instance, origin.parent_stmt, self.is_class_var) new.parent_stmt = par - faked_scopes.append(par) - faked_scopes.append(new) + cache.faked_scopes.append(par) + cache.faked_scopes.append(new) return new def __getattr__(self, name): @@ -331,7 +269,7 @@ class InstanceElement(use_metaclass(CachedMetaClass)): return "<%s of %s>" % (type(self).__name__, self.var) -class Class(use_metaclass(CachedMetaClass, parsing.Base)): +class Class(use_metaclass(cache.CachedMetaClass, parsing.Base)): """ This class is not only important to extend `parsing.Class`, it is also a important for descriptors (if the descriptor methods are evaluated or not). @@ -339,7 +277,7 @@ class Class(use_metaclass(CachedMetaClass, parsing.Base)): def __init__(self, base): self.base = base - @memoize_default(default=[]) + @cache.memoize_default(default=[]) def get_super_classes(self): supers = [] # TODO care for mro stuff (multiple super classes). @@ -355,7 +293,7 @@ class Class(use_metaclass(CachedMetaClass, parsing.Base)): supers += get_scopes_for_name(builtin.Builtin.scope, 'object') return supers - @memoize_default(default=[]) + @cache.memoize_default(default=[]) def get_defined_names(self): def in_iterable(name, iterable): """ checks if the name is in the variable 'iterable'. """ @@ -397,7 +335,7 @@ class Class(use_metaclass(CachedMetaClass, parsing.Base)): return "" % (type(self).__name__, self.base) -class Function(use_metaclass(CachedMetaClass, parsing.Base)): +class Function(use_metaclass(cache.CachedMetaClass, parsing.Base)): """ Needed because of decorators. Decorators are evaluated here. """ @@ -408,7 +346,7 @@ class Function(use_metaclass(CachedMetaClass, parsing.Base)): self.is_decorated = is_decorated @property - @memoize_default() + @cache.memoize_default() def _decorated_func(self): """ Returns the function, that is to be executed in the end. @@ -432,7 +370,7 @@ class Function(use_metaclass(CachedMetaClass, parsing.Base)): # Create param array. old_func = Function(f, is_decorated=True) params = helpers.generate_param_array([old_func], old_func) - faked_scopes.append(old_func) + cache.faked_scopes.append(old_func) wrappers = Execution(decorator, params).get_return_types() if not len(wrappers): @@ -481,7 +419,7 @@ class Execution(Executable): multiple calls to functions and recursion has to be avoided. But this is responsibility of the decorators. """ - @memoize_default(default=[]) + @cache.memoize_default(default=[]) @helpers.ExecutionRecursionDecorator def get_return_types(self, evaluate_generator=False): """ Get the return types of a function. """ @@ -555,7 +493,7 @@ class Execution(Executable): stmts += follow_statement(r) return stmts - @memoize_default(default=[]) + @cache.memoize_default(default=[]) def get_params(self): """ This returns the params for an Execution/Instance and is injected as a @@ -581,7 +519,7 @@ class Execution(Executable): new_param.is_generated = True name = copy.copy(param.get_name()) name.parent = new_param - faked_scopes.append(new_param) + cache.faked_scopes.append(new_param) return name result = [] @@ -737,7 +675,7 @@ class Execution(Executable): if isinstance(copied, parsing.Function): copied = Function(copied) objects.append(copied) - faked_scopes.append(copied) + cache.faked_scopes.append(copied) return objects def __getattr__(self, name): @@ -745,7 +683,7 @@ class Execution(Executable): raise AttributeError('Tried to access %s: %s. Why?' % (name, self)) return getattr(self.base, name) - @memoize_default() + @cache.memoize_default() def _scope_copy(self, scope): try: """ Copies a scope (e.g. if) in an execution """ @@ -758,28 +696,28 @@ class Execution(Executable): else: copied = helpers.fast_parent_copy(scope) copied.parent = self._scope_copy(copied.parent) - faked_scopes.append(copied) + cache.faked_scopes.append(copied) return copied except AttributeError: raise MultiLevelAttributeError(sys.exc_info()) @property - @memoize_default() + @cache.memoize_default() def returns(self): return self.copy_properties('returns') @property - @memoize_default() + @cache.memoize_default() def asserts(self): return self.copy_properties('asserts') @property - @memoize_default() + @cache.memoize_default() def statements(self): return self.copy_properties('statements') @property - @memoize_default() + @cache.memoize_default() def subscopes(self): return self.copy_properties('subscopes') @@ -791,7 +729,7 @@ class Execution(Executable): (type(self).__name__, self.base) -class Generator(use_metaclass(CachedMetaClass, parsing.Base)): +class Generator(use_metaclass(cache.CachedMetaClass, parsing.Base)): """ Cares for `yield` statements. """ def __init__(self, func, var_args): super(Generator, self).__init__() @@ -830,7 +768,7 @@ class Generator(use_metaclass(CachedMetaClass, parsing.Base)): return "<%s of %s>" % (type(self).__name__, self.func) -class Array(use_metaclass(CachedMetaClass, parsing.Base)): +class Array(use_metaclass(cache.CachedMetaClass, parsing.Base)): """ Used as a mirror to parsing.Array, if needed. It defines some getter methods which are important in this module. @@ -1380,7 +1318,7 @@ def assign_tuples(tup, results, seek_name): @helpers.RecursionDecorator -@memoize_default(default=[]) +@cache.memoize_default(default=[]) def follow_statement(stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, diff --git a/jedi/helpers.py b/jedi/helpers.py index b1f6e4c7..91a567e0 100644 --- a/jedi/helpers.py +++ b/jedi/helpers.py @@ -1,6 +1,7 @@ import copy import contextlib +import cache import parsing import evaluate import debug @@ -195,7 +196,7 @@ def generate_param_array(args_tuple, parent_stmt=None): values.append([arg]) pos = None arr = parsing.Array(pos, parsing.Array.TUPLE, parent_stmt, values=values) - evaluate.faked_scopes.append(arr) + cache.faked_scopes.append(arr) return arr diff --git a/jedi/imports.py b/jedi/imports.py index 03a76ca9..3226270b 100644 --- a/jedi/imports.py +++ b/jedi/imports.py @@ -6,6 +6,7 @@ import imp import sys import time +import cache import builtin import modules import debug @@ -91,7 +92,7 @@ class ImportPath(parsing.Base): n = parsing.Name(i.namespace.names[1:], zero, zero, self.import_stmt) new = parsing.Import(zero, zero, n) new.parent = parent - evaluate.faked_scopes.append(new) + cache.faked_scopes.append(new) debug.dbg('Generated a nested import: %s' % new) return new diff --git a/jedi/modules.py b/jedi/modules.py index 2421e913..71e300ab 100644 --- a/jedi/modules.py +++ b/jedi/modules.py @@ -8,6 +8,7 @@ import sys import os import time +import cache import parsing import builtin import debug @@ -215,7 +216,7 @@ class ModuleWithCursor(Module): return self._part_parser -@evaluate.memoize_default([]) +@cache.memoize_default([]) def sys_path_with_modifications(module): def execute_code(code): c = "import os; from os.path import *; result=%s"