integrate memoize caches into evaluator

This commit is contained in:
Dave Halter
2013-12-24 18:46:40 +01:00
parent 066056fb2b
commit ab2c6bf45d
8 changed files with 83 additions and 80 deletions

View File

@@ -5,9 +5,6 @@ available:
- module caching (`load_module` and `save_module`), which uses pickle and is - module caching (`load_module` and `save_module`), which uses pickle and is
really important to assure low load times of modules like ``numpy``. really important to assure low load times of modules like ``numpy``.
- the popular ``memoize_default`` works like a typical memoize and returns the
default otherwise.
- ``CachedMetaClass`` uses ``memoize_default`` to do the same with classes.
- ``time_cache`` can be used to cache something for just a limited time span, - ``time_cache`` can be used to cache something for just a limited time span,
which can be useful if there's user interaction and the user cannot react which can be useful if there's user interaction and the user cannot react
faster than a certain time. faster than a certain time.
@@ -34,9 +31,6 @@ from jedi import settings
from jedi import common from jedi import common
from jedi import debug from jedi import debug
# memoize caches will be deleted after every action
memoize_caches = []
time_caches = [] time_caches = []
star_import_cache = {} star_import_cache = {}
@@ -60,12 +54,7 @@ def clear_caches(delete_all=False):
:param delete_all: Deletes also the cache that is normally not deleted, :param delete_all: Deletes also the cache that is normally not deleted,
like parser cache, which is important for faster parsing. like parser cache, which is important for faster parsing.
""" """
global memoize_caches, time_caches global time_caches
# memorize_caches must never be deleted, because the dicts will get lost in
# the wrappers.
for m in memoize_caches:
m.clear()
if delete_all: if delete_all:
time_caches = [] time_caches = []
@@ -81,41 +70,6 @@ def clear_caches(delete_all=False):
del tc[key] del tc[key]
def memoize_default(default, cache=memoize_caches):
""" 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 = {}
cache.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(None)
def __call__(self, *args, **kwargs):
return super(CachedMetaClass, self).__call__(*args, **kwargs)
def time_cache(time_add_setting): def time_cache(time_add_setting):
""" This decorator works as follows: Call it with a setting and after that """ This decorator works as follows: Call it with a setting and after that
use the function with a callable that returns the key. use the function with a callable that returns the key.

View File

@@ -16,7 +16,7 @@ annotations.
import re import re
from jedi import cache from jedi.evaluate.cache import memoize_default
from jedi.parser import Parser from jedi.parser import Parser
DOCSTRING_PARAM_PATTERNS = [ DOCSTRING_PARAM_PATTERNS = [
@@ -32,7 +32,7 @@ DOCSTRING_RETURN_PATTERNS = [
REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`')
@cache.memoize_default(None) @memoize_default(None)
def follow_param(param): def follow_param(param):
func = param.parent_function func = param.parent_function
# print func, param, param.parent_function # print func, param, param.parent_function

View File

@@ -63,6 +63,7 @@ from jedi import common
from jedi import debug from jedi import debug
from jedi.parser import fast as fast_parser from jedi.parser import fast as fast_parser
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate.cache import memoize_default
# This is something like the sys.path, but only for searching params. It means # This is something like the sys.path, but only for searching params. It means
# that this is the order in which Jedi searches params. # that this is the order in which Jedi searches params.
@@ -140,7 +141,7 @@ class ParamListener(object):
self.param_possibilities.append(params) self.param_possibilities.append(params)
@cache.memoize_default([]) @memoize_default([])
def search_params(param): def search_params(param):
""" """
This is a dynamic search for params. If you try to complete a type: This is a dynamic search for params. If you try to complete a type:
@@ -299,7 +300,7 @@ def _scan_statement(stmt, search_name, assignment_details=False):
return result return result
@cache.memoize_default([]) @memoize_default([])
def _check_array_additions(compare_array, module, is_list): def _check_array_additions(compare_array, module, is_list):
""" """
Checks if a `pr.Array` has "add" statements: Checks if a `pr.Array` has "add" statements:

View File

@@ -75,13 +75,13 @@ import itertools
from jedi._compatibility import next, hasattr, is_py3k, unicode, reraise, u from jedi._compatibility import next, hasattr, is_py3k, unicode, reraise, u
from jedi import common from jedi import common
from jedi import cache
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import debug from jedi import debug
from jedi.evaluate import representation as er from jedi.evaluate import representation as er
from jedi.evaluate import builtin from jedi.evaluate import builtin
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate.cache import memoize_default
from jedi import docstrings from jedi import docstrings
from jedi import dynamic from jedi import dynamic
@@ -117,7 +117,7 @@ def get_defined_names_for_position(scope, position=None, start_scope=None):
class Evaluator(object): class Evaluator(object):
def __init__(self): def __init__(self):
self.cache = None self.memoize_cache = {}
def get_names_of_scope(self, scope, position=None, star_search=True, def get_names_of_scope(self, scope, position=None, star_search=True,
include_builtin=True): include_builtin=True):
@@ -474,7 +474,7 @@ class Evaluator(object):
return filter_name(scope_generator) return filter_name(scope_generator)
return descriptor_check(remove_statements(filter_name(scope_generator))) return descriptor_check(remove_statements(filter_name(scope_generator)))
@cache.memoize_default(default=()) @memoize_default(default=(), cache_is_in_self=True)
@recursion.RecursionDecorator @recursion.RecursionDecorator
def follow_statement(self, stmt, seek_name=None): def follow_statement(self, stmt, seek_name=None):
""" """

48
jedi/evaluate/cache.py Normal file
View File

@@ -0,0 +1,48 @@
"""
- the popular ``memoize_default`` works like a typical memoize and returns the
default otherwise.
- ``CachedMetaClass`` uses ``memoize_default`` to do the same with classes.
"""
def memoize_default(default, cache_is_in_self=False):
""" 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):
def wrapper(obj, *args, **kwargs):
if cache_is_in_self:
cache = obj.memoize_cache
else:
cache = obj._evaluator.memoize_cache
try:
memo = cache[function]
except KeyError:
memo = {}
cache[function] = function
key = (args, frozenset(kwargs.items()))
if key in memo:
return memo[key]
else:
memo[key] = default
rv = function(obj, *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(None)
def __call__(self, *args, **kwargs):
return super(CachedMetaClass, self).__call__(*args, **kwargs)

View File

@@ -200,7 +200,7 @@ class ImportPath(pr.Base):
return [] return []
scopes = [scope] scopes = [scope]
scopes += remove_star_imports(scope) scopes += remove_star_imports(self._evaluator, scope)
# follow the rest of the import (not FS -> classes, functions) # follow the rest of the import (not FS -> classes, functions)
if len(rest) > 1 or rest and self.is_like_search: if len(rest) > 1 or rest and self.is_like_search:

View File

@@ -16,16 +16,16 @@ import itertools
from jedi._compatibility import use_metaclass, next, hasattr, unicode from jedi._compatibility import use_metaclass, next, hasattr, unicode
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import cache
from jedi import helpers from jedi import helpers
from jedi import debug from jedi import debug
from jedi import common from jedi import common
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import builtin from jedi.evaluate import builtin
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate.cache import memoize_default, CachedMetaClass
from jedi.evaluate.interfaces import Iterable
from jedi import docstrings from jedi import docstrings
from jedi import dynamic from jedi import dynamic
from jedi.evaluate.interfaces import Iterable
class Executable(pr.IsScope): class Executable(pr.IsScope):
@@ -53,7 +53,7 @@ class Executable(pr.IsScope):
return self.base return self.base
class Instance(use_metaclass(cache.CachedMetaClass, Executable)): class Instance(use_metaclass(CachedMetaClass, Executable)):
""" """
This class is used to evaluate instances. This class is used to evaluate instances.
""" """
@@ -72,7 +72,7 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
# (No var_args) used. # (No var_args) used.
self.is_generated = False self.is_generated = False
@cache.memoize_default(None) @memoize_default(None)
def _get_method_execution(self, func): def _get_method_execution(self, func):
func = InstanceElement(self, func, True) func = InstanceElement(self, func, True)
return Execution(func, self.var_args) return Execution(func, self.var_args)
@@ -87,7 +87,7 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
except IndexError: except IndexError:
return None return None
@cache.memoize_default([]) @memoize_default([])
def _get_self_attributes(self): def _get_self_attributes(self):
def add_self_dot_name(name): def add_self_dot_name(name):
""" """
@@ -144,7 +144,7 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
args = [obj, obj.base] if isinstance(obj, Instance) else [None, obj] args = [obj, obj.base] if isinstance(obj, Instance) else [None, obj]
return self.execute_subscope_by_name('__get__', args) return self.execute_subscope_by_name('__get__', args)
@cache.memoize_default([]) @memoize_default([])
def get_defined_names(self): def get_defined_names(self):
""" """
Get the instance vars of a class. This includes the vars of all Get the instance vars of a class. This includes the vars of all
@@ -190,7 +190,7 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
(type(self).__name__, self.base, len(self.var_args or [])) (type(self).__name__, self.base, len(self.var_args or []))
class InstanceElement(use_metaclass(cache.CachedMetaClass, pr.Base)): class InstanceElement(use_metaclass(CachedMetaClass, pr.Base)):
""" """
InstanceElement is a wrapper for any object, that is used as an instance InstanceElement is a wrapper for any object, that is used as an instance
variable (e.g. self.variable or class methods). variable (e.g. self.variable or class methods).
@@ -205,7 +205,7 @@ class InstanceElement(use_metaclass(cache.CachedMetaClass, pr.Base)):
self.is_class_var = is_class_var self.is_class_var = is_class_var
@property @property
@cache.memoize_default(None) @memoize_default(None)
def parent(self): def parent(self):
par = self.var.parent par = self.var.parent
if isinstance(par, Class) and par == self.instance.base \ if isinstance(par, Class) and par == self.instance.base \
@@ -246,7 +246,7 @@ class InstanceElement(use_metaclass(cache.CachedMetaClass, pr.Base)):
return "<%s of %s>" % (type(self).__name__, self.var) return "<%s of %s>" % (type(self).__name__, self.var)
class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)): class Class(use_metaclass(CachedMetaClass, pr.IsScope)):
""" """
This class is not only important to extend `pr.Class`, it is also a This class is not only important to extend `pr.Class`, it is also a
important for descriptors (if the descriptor methods are evaluated or not). important for descriptors (if the descriptor methods are evaluated or not).
@@ -255,7 +255,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
self._evaluator = evaluator self._evaluator = evaluator
self.base = base self.base = base
@cache.memoize_default(default=()) @memoize_default(default=())
def get_super_classes(self): def get_super_classes(self):
supers = [] supers = []
# TODO care for mro stuff (multiple super classes). # TODO care for mro stuff (multiple super classes).
@@ -271,7 +271,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
supers += self._evaluator.find_name(builtin.Builtin.scope, 'object') supers += self._evaluator.find_name(builtin.Builtin.scope, 'object')
return supers return supers
@cache.memoize_default(default=()) @memoize_default(default=())
def instance_names(self): def instance_names(self):
def in_iterable(name, iterable): def in_iterable(name, iterable):
""" checks if the name is in the variable 'iterable'. """ """ checks if the name is in the variable 'iterable'. """
@@ -293,7 +293,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
result += super_result result += super_result
return result return result
@cache.memoize_default(default=()) @memoize_default(default=())
def get_defined_names(self): def get_defined_names(self):
result = self.instance_names() result = self.instance_names()
type_cls = self._evaluator.find_name(builtin.Builtin.scope, 'type')[0] type_cls = self._evaluator.find_name(builtin.Builtin.scope, 'type')[0]
@@ -320,7 +320,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
return "<e%s of %s>" % (type(self).__name__, self.base) return "<e%s of %s>" % (type(self).__name__, self.base)
class Function(use_metaclass(cache.CachedMetaClass, pr.IsScope)): class Function(use_metaclass(CachedMetaClass, pr.IsScope)):
""" """
Needed because of decorators. Decorators are evaluated here. Needed because of decorators. Decorators are evaluated here.
""" """
@@ -330,7 +330,7 @@ class Function(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
self.base_func = func self.base_func = func
self.is_decorated = is_decorated self.is_decorated = is_decorated
@cache.memoize_default(None) @memoize_default(None)
def _decorated_func(self, instance=None): def _decorated_func(self, instance=None):
""" """
Returns the function, that is to be executed in the end. Returns the function, that is to be executed in the end.
@@ -420,7 +420,7 @@ class Execution(Executable):
return [stmt] # just some arbitrary object return [stmt] # just some arbitrary object
@property @property
@cache.memoize_default(None) @memoize_default(None)
def _decorated(self): def _decorated(self):
"""Get the decorated version of the input""" """Get the decorated version of the input"""
base = self.base base = self.base
@@ -428,7 +428,7 @@ class Execution(Executable):
base = base.get_decorated_func() base = base.get_decorated_func()
return base return base
@cache.memoize_default(default=()) @memoize_default(default=())
@recursion.ExecutionRecursionDecorator @recursion.ExecutionRecursionDecorator
def get_return_types(self, evaluate_generator=False): def get_return_types(self, evaluate_generator=False):
""" Get the return types of a function. """ """ Get the return types of a function. """
@@ -514,7 +514,7 @@ class Execution(Executable):
stmts += self._evaluator.follow_statement(r) stmts += self._evaluator.follow_statement(r)
return stmts return stmts
@cache.memoize_default(default=()) @memoize_default(default=())
def _get_params(self): def _get_params(self):
""" """
This returns the params for an Execution/Instance and is injected as a This returns the params for an Execution/Instance and is injected as a
@@ -736,7 +736,7 @@ class Execution(Executable):
raise AttributeError('Tried to access %s: %s. Why?' % (name, self)) raise AttributeError('Tried to access %s: %s. Why?' % (name, self))
return getattr(self._decorated, name) return getattr(self._decorated, name)
@cache.memoize_default(None) @memoize_default(None)
@common.rethrow_uncaught @common.rethrow_uncaught
def _scope_copy(self, scope): def _scope_copy(self, scope):
""" Copies a scope (e.g. if) in an execution """ """ Copies a scope (e.g. if) in an execution """
@@ -752,22 +752,22 @@ class Execution(Executable):
return copied return copied
@property @property
@cache.memoize_default([]) @memoize_default([])
def returns(self): def returns(self):
return self._copy_properties('returns') return self._copy_properties('returns')
@property @property
@cache.memoize_default([]) @memoize_default([])
def asserts(self): def asserts(self):
return self._copy_properties('asserts') return self._copy_properties('asserts')
@property @property
@cache.memoize_default([]) @memoize_default([])
def statements(self): def statements(self):
return self._copy_properties('statements') return self._copy_properties('statements')
@property @property
@cache.memoize_default([]) @memoize_default([])
def subscopes(self): def subscopes(self):
return self._copy_properties('subscopes') return self._copy_properties('subscopes')
@@ -779,7 +779,7 @@ class Execution(Executable):
(type(self).__name__, self._decorated) (type(self).__name__, self._decorated)
class Generator(use_metaclass(cache.CachedMetaClass, pr.Base, Iterable)): class Generator(use_metaclass(CachedMetaClass, pr.Base, Iterable)):
""" Cares for `yield` statements. """ """ Cares for `yield` statements. """
def __init__(self, func, var_args): def __init__(self, func, var_args):
super(Generator, self).__init__() super(Generator, self).__init__()
@@ -825,7 +825,7 @@ class Generator(use_metaclass(cache.CachedMetaClass, pr.Base, Iterable)):
return "<%s of %s>" % (type(self).__name__, self.func) return "<%s of %s>" % (type(self).__name__, self.func)
class Array(use_metaclass(cache.CachedMetaClass, pr.Base, Iterable)): class Array(use_metaclass(CachedMetaClass, pr.Base, Iterable)):
""" """
Used as a mirror to pr.Array, if needed. It defines some getter Used as a mirror to pr.Array, if needed. It defines some getter
methods which are important in this module. methods which are important in this module.

View File

@@ -291,7 +291,7 @@ def get_sys_path():
return [p for p in sys.path if p != ""] return [p for p in sys.path if p != ""]
@cache.memoize_default([]) #@cache.memoize_default([]) TODO add some sort of cache again.
def sys_path_with_modifications(module): def sys_path_with_modifications(module):
def execute_code(code): def execute_code(code):
c = "import os; from os.path import *; result=%s" c = "import os; from os.path import *; result=%s"