*args / **kwargs remodelled

This commit is contained in:
David Halter
2012-05-07 22:41:41 +02:00
parent 40e59c0476
commit 57128bf17f
6 changed files with 131 additions and 72 deletions

View File

@@ -3,5 +3,14 @@
try: try:
next = next next = next
except NameError: except NameError:
def next(obj): _raiseStopIteration = object()
return obj.next() 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

View File

@@ -25,12 +25,16 @@ import builtin
memoize_caches = [] memoize_caches = []
class MultiLevelStopIteration(Exception):
pass
def clear_caches(): def clear_caches():
for m in memoize_caches: for m in memoize_caches:
m.clear() m.clear()
def memoize(default=None): def memoize_default(default=None):
""" """
This is a typical memoization decorator, BUT there is one difference: This is a typical memoization decorator, BUT there is one difference:
To prevent recursion it sets defaults. To prevent recursion it sets defaults.
@@ -78,7 +82,7 @@ class Executable(object):
pass pass
raise IndexError('No params available') raise IndexError('No params available')
@memoize(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
@@ -86,6 +90,21 @@ class Executable(object):
This needs to be here, because Instance can have __init__ functions, This needs to be here, because Instance can have __init__ functions,
which act the same way as normal 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 = [] result = []
start_offset = 0 start_offset = 0
#print '\n\nfunc_params', self.func, self.func.parent, self.func #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 # There may be calls, which don't fit all the params, this just ignores
# it. # it.
var_arg_iterator = self.get_var_args_iterator() 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:
try:
param = self.func.params[i]
except IndexError:
debug.warning('Too many arguments given.', value)
if param: non_matching_keys = []
calls = parsing.Array(parsing.Array.NOARRAY, for param in self.func.params[start_offset:]:
self.var_args.parent_stmt) # 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:
key_param = param_dict[key]
except IndexError:
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))
#debug.warning('Too many arguments given.', value)
assignment = param.get_assignment_calls().values[0] assignment = param.get_assignment_calls().values[0]
keys = []
values = []
array_type = None
if assignment[0] == '*': if assignment[0] == '*':
# *args param # *args param
print '\n\n3*', value, assignment print '\n\n3*', value, assignment
calls.type = parsing.Array.TUPLE
if key: array_type = parsing.Array.TUPLE
var_arg_iterator.push_back(key, value) if value:
else: values.append(value)
calls.values = [value]
for key, value in var_arg_iterator: for key, value in var_arg_iterator:
# iterate until a key argument is found
if key: if key:
var_arg_iterator.push_back(key, value) var_arg_iterator.push_back(key, value)
break break
calls.values.append(value) values.append(value)
elif assignment[0] == '**': elif assignment[0] == '**':
# **kwargs param # **kwargs param
calls.type = parsing.Array.DICT array_type = parsing.Array.DICT
calls.values = [value] if non_matching_keys:
calls.keys = [key] keys, values = zip(*non_matching_keys)
for value in var_arg_iterator: print '\n\n**', keys, values, assignment
calls.values.append(value)
calls.keys.append(key)
# it is a **args param
print '\n\n**', key, value, assignment
else: else:
# normal param # normal param
calls.values = [value] print 'normal', value
if value:
values = [value]
new_param = copy.copy(param) result.append(gen_param_name_copy(param, keys=keys, values=values,
new_param._assignment_calls = calls array_type=array_type))
name = copy.copy(param.get_name())
name.parent = new_param
#print 'insert', i, name, calls.values, value, self.func.params
result.append(name)
return result return result
def get_var_args_iterator(self): def get_var_args_iterator(self):
@@ -345,7 +366,7 @@ class Execution(Executable):
""" """
cache = {} cache = {}
@memoize(default=[]) @memoize_default(default=[])
def get_return_types(self): def get_return_types(self):
""" """
Get the return vars of a function. 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 # class variables/functions are only available
if (not isinstance(scope, Class) or scope == start_scope) \ if (not isinstance(scope, Class) or scope == start_scope) \
and not isinstance(scope, parsing.Flow): and not isinstance(scope, parsing.Flow):
try:
yield scope, get_defined_names_for_position(scope, position) yield scope, get_defined_names_for_position(scope, position)
except StopIteration:
raise MultiLevelStopIteration('StopIteration raised somewhere')
scope = scope.parent scope = scope.parent
# add star imports # add star imports
@@ -638,7 +662,7 @@ def assign_tuples(tup, results, seek_name):
return result return result
@memoize(default=[]) @memoize_default(default=[])
def follow_statement(stmt, scope=None, seek_name=None): def follow_statement(stmt, scope=None, seek_name=None):
""" """
:param stmt: contains a statement :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 It is used to evaluate a two dimensional object, that has calls, arrays and
operators in it. 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 # Tuples can stand just alone without any braces. These would be
# recognized as separate calls, but actually are a tuple. # recognized as separate calls, but actually are a tuple.
print 'inpu', scope, call_list
result = follow_call(scope, call_list) result = follow_call(scope, call_list)
else: else:
result = [] result = []
@@ -729,15 +756,18 @@ def follow_call(scope, call):
def follow_paths(path, results, position=None): def follow_paths(path, results, position=None):
results_new = [] results_new = []
try:
if results: if results:
if len(results) > 1: if len(results) > 1:
iter_paths = itertools.tee(path, len(results)) iter_paths = itertools.tee(path, len(results))
else: else:
iter_paths = [path] iter_paths = [path]
for i, r in enumerate(results): for i, r in enumerate(results):
results_new += follow_path(iter_paths[i], r, position=position) fp = follow_path(iter_paths[i], r, position=position)
except StopIteration: if fp is not None:
results_new += fp
else:
# this means stop iteration
return results return results
return results_new return results_new
@@ -747,7 +777,10 @@ def follow_path(path, scope, position=None):
Takes a generator and tries to complete the path. Takes a generator and tries to complete the path.
""" """
# current is either an Array or a Scope # current is either an Array or a Scope
try:
current = next(path) current = next(path)
except StopIteration:
return None
debug.dbg('follow', current, scope) debug.dbg('follow', current, scope)
result = [] result = []

View File

@@ -205,6 +205,7 @@ def complete(source, row, column, source_path):
for s in scopes: for s in scopes:
# TODO is this reall the right way? just ignore the functions? \ # TODO is this reall the right way? just ignore the functions? \
# do the magic functions first? and then recheck here? # do the magic functions first? and then recheck here?
print 'scope, '
if not isinstance(s, parsing.Function): if not isinstance(s, parsing.Function):
completions += s.get_defined_names() completions += s.get_defined_names()

View File

@@ -160,10 +160,6 @@ a.
class C(object): class C(object):
def c_a(self): def c_a(self):
self.c_b = 1 self.c_b = 1
@@ -171,10 +167,14 @@ class C(object):
test = [1,2] test = [1,2]
def args_func(arg1, *args, **kwargs): def args_func(arg1, arg2=1, *args, **kwargs):
return (arg1, args) return arg1, arg2, args, kwargs
exe = args_func(list,"", 3) exe = args_func(1,"")
args_func(1,"", a=list)[0]. args_func() #1,"", a=list)[0].
args_func(arg1=0, *test + [3], *[4,5], **{'a': 'b'}). args_func(arg1=0, *test + [3], *[4,5], **{'a': 'b'}).
exe[1].
exe[3].

View File

@@ -325,7 +325,7 @@ class Function(Scope):
def get_set_vars(self): def get_set_vars(self):
n = super(Function, self).get_set_vars() 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 # this is the really ugly part, where the functional style of this
# get methods is broken, it executes a callback. # get methods is broken, it executes a callback.
# This is important, because something has to inject the params # This is important, because something has to inject the params
@@ -533,6 +533,8 @@ class Statement(Simple):
# cache # cache
self._assignment_calls = None self._assignment_calls = None
self._assignment_details = None self._assignment_details = None
# this is important for other scripts
self._assignment_calls_calculated = False
def get_code(self, new_line=True): def get_code(self, new_line=True):
if new_line: if new_line:
@@ -566,7 +568,7 @@ class Statement(Simple):
This is not really nice written, sorry for that. If you plan to replace This is not really nice written, sorry for that. If you plan to replace
it and make it nicer, that would be cool :-) it and make it nicer, that would be cool :-)
""" """
if self._assignment_calls: if self._assignment_calls_calculated:
return self._assignment_calls return self._assignment_calls
self._assignment_details = [] self._assignment_details = []
result = Array(Array.NOARRAY, self) result = Array(Array.NOARRAY, self)
@@ -827,13 +829,13 @@ class Array(Call):
return self.values[0][0] return self.values[0][0]
@staticmethod @staticmethod
def is_type(instance, typ): def is_type(instance, *types):
""" """
This is not only used for calls on the actual object, but for This is not only used for calls on the actual object, but for
ducktyping, to invoke this function with anything as `self`. ducktyping, to invoke this function with anything as `self`.
""" """
if isinstance(instance, Array): if isinstance(instance, Array):
if instance.type == typ: if instance.type in types:
return True return True
return False return False

View File

@@ -109,7 +109,21 @@ exe[1][1].append
# ----------------- # -----------------
# ** kwargs # ** kwargs
# ----------------- # -----------------
def kwargs_func(**kwargs):
return kwargs
exe = kwargs_func(a=3,b=4)
#? []
exe.
# ----------------- # -----------------
# *args / ** kwargs # *args / ** kwargs
# ----------------- # -----------------
def fu(a=1, b="", *args, **kwargs):
return a,b,args,kwargs
exe = fu(list, 1, "", c=set)
##? ['append']
exe[0].