mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
*args / **kwargs remodelled
This commit is contained in:
@@ -3,5 +3,14 @@
|
||||
try:
|
||||
next = next
|
||||
except NameError:
|
||||
def next(obj):
|
||||
return obj.next()
|
||||
_raiseStopIteration = object()
|
||||
def next(iterator, default=_raiseStopIteration):
|
||||
if not hasattr(iterator, 'next'):
|
||||
raise TypeError("not an iterator")
|
||||
try:
|
||||
return iterator.next()
|
||||
except StopIteration:
|
||||
if default is _raiseStopIteration:
|
||||
raise
|
||||
else:
|
||||
return default
|
||||
|
||||
147
evaluate.py
147
evaluate.py
@@ -25,12 +25,16 @@ import builtin
|
||||
memoize_caches = []
|
||||
|
||||
|
||||
class MultiLevelStopIteration(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def clear_caches():
|
||||
for m in memoize_caches:
|
||||
m.clear()
|
||||
|
||||
|
||||
def memoize(default=None):
|
||||
def memoize_default(default=None):
|
||||
"""
|
||||
This is a typical memoization decorator, BUT there is one difference:
|
||||
To prevent recursion it sets defaults.
|
||||
@@ -78,7 +82,7 @@ class Executable(object):
|
||||
pass
|
||||
raise IndexError('No params available')
|
||||
|
||||
@memoize(default=[])
|
||||
@memoize_default(default=[])
|
||||
def get_params(self):
|
||||
"""
|
||||
This returns the params for an Execution/Instance and is injected as a
|
||||
@@ -86,6 +90,21 @@ class Executable(object):
|
||||
This needs to be here, because Instance can have __init__ functions,
|
||||
which act the same way as normal functions
|
||||
"""
|
||||
def gen_param_name_copy(param, keys=[], values=[], array_type=None):
|
||||
calls = parsing.Array(parsing.Array.NOARRAY,
|
||||
self.var_args.parent_stmt)
|
||||
calls.values = values
|
||||
calls.keys = keys
|
||||
print 'arr_t', array_type
|
||||
calls.type = array_type
|
||||
new_param = copy.copy(param)
|
||||
new_param._assignment_calls_calculated = True
|
||||
new_param._assignment_calls = calls
|
||||
name = copy.copy(param.get_name())
|
||||
name.parent = new_param
|
||||
#print 'insert', i, name, calls.values, value, self.func.params
|
||||
return name
|
||||
|
||||
result = []
|
||||
start_offset = 0
|
||||
#print '\n\nfunc_params', self.func, self.func.parent, self.func
|
||||
@@ -104,54 +123,56 @@ class Executable(object):
|
||||
# There may be calls, which don't fit all the params, this just ignores
|
||||
# it.
|
||||
var_arg_iterator = self.get_var_args_iterator()
|
||||
for i, (key, value) in enumerate(var_arg_iterator, start_offset):
|
||||
param = None
|
||||
try:
|
||||
param = param_dict[key]
|
||||
except:
|
||||
|
||||
non_matching_keys = []
|
||||
for param in self.func.params[start_offset:]:
|
||||
# The value and key can both be null. There, the defaults apply.
|
||||
# args / kwargs will just be empty arrays / dicts, respectively.
|
||||
key, value = next(var_arg_iterator, (None, None))
|
||||
print '\n\nlala', key, value
|
||||
while key:
|
||||
try:
|
||||
param = self.func.params[i]
|
||||
key_param = param_dict[key]
|
||||
except IndexError:
|
||||
debug.warning('Too many arguments given.', value)
|
||||
non_matching_keys.append((key, value))
|
||||
else:
|
||||
result.append(gen_param_name_copy(key_param,
|
||||
values=[value]))
|
||||
key, value = next(var_arg_iterator, (None, None))
|
||||
|
||||
if param:
|
||||
calls = parsing.Array(parsing.Array.NOARRAY,
|
||||
self.var_args.parent_stmt)
|
||||
#debug.warning('Too many arguments given.', value)
|
||||
|
||||
assignment = param.get_assignment_calls().values[0]
|
||||
if assignment[0] == '*':
|
||||
# *args param
|
||||
print '\n\n3*', value, assignment
|
||||
calls.type = parsing.Array.TUPLE
|
||||
assignment = param.get_assignment_calls().values[0]
|
||||
keys = []
|
||||
values = []
|
||||
array_type = None
|
||||
if assignment[0] == '*':
|
||||
# *args param
|
||||
print '\n\n3*', value, assignment
|
||||
|
||||
array_type = parsing.Array.TUPLE
|
||||
if value:
|
||||
values.append(value)
|
||||
for key, value in var_arg_iterator:
|
||||
# iterate until a key argument is found
|
||||
if key:
|
||||
var_arg_iterator.push_back(key, value)
|
||||
else:
|
||||
calls.values = [value]
|
||||
for key, value in var_arg_iterator:
|
||||
if key:
|
||||
var_arg_iterator.push_back(key, value)
|
||||
break
|
||||
calls.values.append(value)
|
||||
elif assignment[0] == '**':
|
||||
# **kwargs param
|
||||
calls.type = parsing.Array.DICT
|
||||
calls.values = [value]
|
||||
calls.keys = [key]
|
||||
for value in var_arg_iterator:
|
||||
calls.values.append(value)
|
||||
calls.keys.append(key)
|
||||
# it is a **args param
|
||||
print '\n\n**', key, value, assignment
|
||||
else:
|
||||
# normal param
|
||||
calls.values = [value]
|
||||
break
|
||||
values.append(value)
|
||||
elif assignment[0] == '**':
|
||||
# **kwargs param
|
||||
array_type = parsing.Array.DICT
|
||||
if non_matching_keys:
|
||||
keys, values = zip(*non_matching_keys)
|
||||
print '\n\n**', keys, values, assignment
|
||||
else:
|
||||
# normal param
|
||||
print 'normal', value
|
||||
if value:
|
||||
values = [value]
|
||||
|
||||
new_param = copy.copy(param)
|
||||
new_param._assignment_calls = calls
|
||||
name = copy.copy(param.get_name())
|
||||
name.parent = new_param
|
||||
#print 'insert', i, name, calls.values, value, self.func.params
|
||||
result.append(name)
|
||||
result.append(gen_param_name_copy(param, keys=keys, values=values,
|
||||
array_type=array_type))
|
||||
return result
|
||||
|
||||
def get_var_args_iterator(self):
|
||||
@@ -345,7 +366,7 @@ class Execution(Executable):
|
||||
"""
|
||||
cache = {}
|
||||
|
||||
@memoize(default=[])
|
||||
@memoize_default(default=[])
|
||||
def get_return_types(self):
|
||||
"""
|
||||
Get the return vars of a function.
|
||||
@@ -465,7 +486,10 @@ def get_names_for_scope(scope, position=None, star_search=True):
|
||||
# class variables/functions are only available
|
||||
if (not isinstance(scope, Class) or scope == start_scope) \
|
||||
and not isinstance(scope, parsing.Flow):
|
||||
yield scope, get_defined_names_for_position(scope, position)
|
||||
try:
|
||||
yield scope, get_defined_names_for_position(scope, position)
|
||||
except StopIteration:
|
||||
raise MultiLevelStopIteration('StopIteration raised somewhere')
|
||||
scope = scope.parent
|
||||
|
||||
# add star imports
|
||||
@@ -638,7 +662,7 @@ def assign_tuples(tup, results, seek_name):
|
||||
return result
|
||||
|
||||
|
||||
@memoize(default=[])
|
||||
@memoize_default(default=[])
|
||||
def follow_statement(stmt, scope=None, seek_name=None):
|
||||
"""
|
||||
:param stmt: contains a statement
|
||||
@@ -673,9 +697,12 @@ def follow_call_list(scope, call_list):
|
||||
It is used to evaluate a two dimensional object, that has calls, arrays and
|
||||
operators in it.
|
||||
"""
|
||||
if parsing.Array.is_type(call_list, parsing.Array.TUPLE):
|
||||
print 'inpu', scope, call_list
|
||||
if parsing.Array.is_type(call_list, parsing.Array.TUPLE,
|
||||
parsing.Array.DICT):
|
||||
# Tuples can stand just alone without any braces. These would be
|
||||
# recognized as separate calls, but actually are a tuple.
|
||||
print 'inpu', scope, call_list
|
||||
result = follow_call(scope, call_list)
|
||||
else:
|
||||
result = []
|
||||
@@ -729,16 +756,19 @@ def follow_call(scope, call):
|
||||
|
||||
def follow_paths(path, results, position=None):
|
||||
results_new = []
|
||||
try:
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
iter_paths = itertools.tee(path, len(results))
|
||||
if results:
|
||||
if len(results) > 1:
|
||||
iter_paths = itertools.tee(path, len(results))
|
||||
else:
|
||||
iter_paths = [path]
|
||||
|
||||
for i, r in enumerate(results):
|
||||
fp = follow_path(iter_paths[i], r, position=position)
|
||||
if fp is not None:
|
||||
results_new += fp
|
||||
else:
|
||||
iter_paths = [path]
|
||||
for i, r in enumerate(results):
|
||||
results_new += follow_path(iter_paths[i], r, position=position)
|
||||
except StopIteration:
|
||||
return results
|
||||
# this means stop iteration
|
||||
return results
|
||||
return results_new
|
||||
|
||||
|
||||
@@ -747,7 +777,10 @@ def follow_path(path, scope, position=None):
|
||||
Takes a generator and tries to complete the path.
|
||||
"""
|
||||
# current is either an Array or a Scope
|
||||
current = next(path)
|
||||
try:
|
||||
current = next(path)
|
||||
except StopIteration:
|
||||
return None
|
||||
debug.dbg('follow', current, scope)
|
||||
|
||||
result = []
|
||||
|
||||
@@ -205,6 +205,7 @@ def complete(source, row, column, source_path):
|
||||
for s in scopes:
|
||||
# TODO is this reall the right way? just ignore the functions? \
|
||||
# do the magic functions first? and then recheck here?
|
||||
print 'scope, '
|
||||
if not isinstance(s, parsing.Function):
|
||||
completions += s.get_defined_names()
|
||||
|
||||
|
||||
18
parsetest.py
18
parsetest.py
@@ -160,10 +160,6 @@ a.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class C(object):
|
||||
def c_a(self):
|
||||
self.c_b = 1
|
||||
@@ -171,10 +167,14 @@ class C(object):
|
||||
test = [1,2]
|
||||
|
||||
|
||||
def args_func(arg1, *args, **kwargs):
|
||||
return (arg1, args)
|
||||
def args_func(arg1, arg2=1, *args, **kwargs):
|
||||
return arg1, arg2, args, kwargs
|
||||
|
||||
exe = args_func(list,"", 3)
|
||||
args_func(1,"", a=list)[0].
|
||||
exe = args_func(1,"")
|
||||
args_func() #1,"", a=list)[0].
|
||||
args_func(arg1=0, *test + [3], *[4,5], **{'a': 'b'}).
|
||||
exe[1].
|
||||
|
||||
|
||||
|
||||
|
||||
exe[3].
|
||||
|
||||
10
parsing.py
10
parsing.py
@@ -325,7 +325,7 @@ class Function(Scope):
|
||||
|
||||
def get_set_vars(self):
|
||||
n = super(Function, self).get_set_vars()
|
||||
if self.param_cb or False:
|
||||
if self.param_cb:
|
||||
# this is the really ugly part, where the functional style of this
|
||||
# get methods is broken, it executes a callback.
|
||||
# This is important, because something has to inject the params
|
||||
@@ -533,6 +533,8 @@ class Statement(Simple):
|
||||
# cache
|
||||
self._assignment_calls = None
|
||||
self._assignment_details = None
|
||||
# this is important for other scripts
|
||||
self._assignment_calls_calculated = False
|
||||
|
||||
def get_code(self, new_line=True):
|
||||
if new_line:
|
||||
@@ -566,7 +568,7 @@ class Statement(Simple):
|
||||
This is not really nice written, sorry for that. If you plan to replace
|
||||
it and make it nicer, that would be cool :-)
|
||||
"""
|
||||
if self._assignment_calls:
|
||||
if self._assignment_calls_calculated:
|
||||
return self._assignment_calls
|
||||
self._assignment_details = []
|
||||
result = Array(Array.NOARRAY, self)
|
||||
@@ -827,13 +829,13 @@ class Array(Call):
|
||||
return self.values[0][0]
|
||||
|
||||
@staticmethod
|
||||
def is_type(instance, typ):
|
||||
def is_type(instance, *types):
|
||||
"""
|
||||
This is not only used for calls on the actual object, but for
|
||||
ducktyping, to invoke this function with anything as `self`.
|
||||
"""
|
||||
if isinstance(instance, Array):
|
||||
if instance.type == typ:
|
||||
if instance.type in types:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -109,7 +109,21 @@ exe[1][1].append
|
||||
# -----------------
|
||||
# ** kwargs
|
||||
# -----------------
|
||||
def kwargs_func(**kwargs):
|
||||
return kwargs
|
||||
|
||||
exe = kwargs_func(a=3,b=4)
|
||||
#? []
|
||||
exe.
|
||||
|
||||
# -----------------
|
||||
# *args / ** kwargs
|
||||
# -----------------
|
||||
|
||||
def fu(a=1, b="", *args, **kwargs):
|
||||
return a,b,args,kwargs
|
||||
|
||||
exe = fu(list, 1, "", c=set)
|
||||
|
||||
##? ['append']
|
||||
exe[0].
|
||||
|
||||
Reference in New Issue
Block a user