def get_default_args_dict(function): ''' Get ordered dict from arguments which have a default to their default. Example: >>> def f(a, b, c=1, d='meow'): pass >>> get_default_args_dict(f) OrderedDict([('c', 1), ('d', 'meow')]) ''' arg_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = arg_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more consistent # `()`. We fix that here: if s_defaults is None: s_defaults = () # The number of args which have default values: n_defaultful_args = len(s_defaults) defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] return OrderedDict(list(zip(defaultful_args, s_defaults)))
def __init__(self, argument_control, step_function): self.argument_control = argument_control wx.StaticBox.__init__(self, argument_control, label='Additional arguments') self.SetMinSize(argument_control.box_size) self.SetMaxSize(argument_control.box_size) self.sizer = wx.StaticBoxSizer(self, wx.VERTICAL) self.sizer.SetMinSize(argument_control.box_size) self.step_function = step_function arg_spec = cute_inspect.getargspec(step_function) star_arg_list = \ argument_control.step_profile_dialog.step_functions_to_star_args[ step_function ] self.star_args = [] for star_arg_value in star_arg_list: star_arg = StarArg(argument_control, self, star_arg_value) self.star_args.append(star_arg) self.sizer.Add(star_arg, 0, wx.EXPAND | wx.ALL, border=5) self.star_adder = StarAdder(argument_control) self.sizer.Add(self.star_adder, 0, wx.EXPAND | wx.ALL, border=5) self.Parent.Bind(EVT_STAR_ADDER_PRESSED, self.on_star_adder_pressed, source=self.star_adder)
def get_default_args_dict(function): ''' Get ordered dict from arguments which have a default to their default. Example: >>> def f(a, b, c=1, d='meow'): pass >>> get_default_args_dict(f) OrderedDict([('c', 1), ('d', 'meow')]) ''' arg_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = arg_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more consistent # `()`. We fix that here: if s_defaults is None: s_defaults = () # The number of args which have default values: n_defaultful_args = len(s_defaults) defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] return OrderedDict(zip(defaultful_args, s_defaults))
def __init__(self, argument_control, step_function): self.argument_control = argument_control wx.StaticBox.__init__(self, argument_control, label='Arguments', size=argument_control.box_size) self.SetMinSize(argument_control.box_size) self.SetMaxSize(argument_control.box_size) self.sizer = wx.StaticBoxSizer(self, wx.VERTICAL) self.sizer.SetMinSize(argument_control.box_size) self.step_function = step_function arg_spec = cute_inspect.getargspec(step_function) arg_dict = argument_control.step_profile_dialog.\ step_functions_to_argument_dicts[ step_function ] self.args = [] for i, arg_name in list(enumerate(arg_spec.args))[1:]: value = arg_dict[arg_name] if not value and (arg_name in arg_spec.defaults): value = arg_dict[arg_name] = repr(arg_spec.defaults[i]) arg = Arg(argument_control, arg_name, value) self.args.append(arg) self.sizer.Add(arg, 0, wx.EXPAND | wx.ALL, border=5)
def create_from_dld_format(cls, function, args_dict, star_args_list, star_kwargs_dict): ''' Create an arguments profile from data given in "dict-list-dict" format. The "dict-list-dict" format means that in addition to a function, we get a `dict` of arguments, a `list` of `*args`, and a `dict` of `**kwargs`. ''' args_spec = cute_inspect.getargspec(function) new_args = [args_dict[name] for name in args_spec.args] + \ list(star_args_list) return cls(function, *new_args, **star_kwargs_dict)
def __init__(self, containing_dict, function, *args, **kwargs): ''' Construct the `SleekCallArgs`. `containing_dict` is the `dict` we'll try to remove ourselves from when one of our sleekrefs dies. `function` is the function for which we calculate call args from `*args` and `**kwargs`. ''' self.containing_dict = containing_dict ''' `dict` we'll try to remove ourselves from when 1 of our sleekrefs dies. ''' args_spec = cute_inspect.getargspec(function) star_args_name, star_kwargs_name = \ args_spec.varargs, args_spec.keywords call_args = cute_inspect.getcallargs(function, *args, **kwargs) del args, kwargs self.star_args_refs = [] '''Sleekrefs to star-args.''' if star_args_name: star_args = call_args.pop(star_args_name, None) if star_args: self.star_args_refs = [SleekRef(star_arg, self.destroy) for star_arg in star_args] self.star_kwargs_refs = {} '''Sleerefs to star-kwargs.''' if star_kwargs_name: star_kwargs = call_args.pop(star_kwargs_name, {}) if star_kwargs: self.star_kwargs_refs = CuteSleekValueDict(self.destroy, star_kwargs) self.args_refs = CuteSleekValueDict(self.destroy, call_args) '''Mapping from argument name to value, sleek-style.''' # In the future the `.args`, `.star_args` and `.star_kwargs` attributes # may change, so we must record the hash now: self._hash = cheat_hashing.cheat_hash( ( self.args, self.star_args, self.star_kwargs ) )
def __init__(self, containing_dict, function, *args, **kwargs): ''' Construct the `SleekCallArgs`. `containing_dict` is the `dict` we'll try to remove ourselves from when one of our sleekrefs dies. `function` is the function for which we calculate call args from `*args` and `**kwargs`. ''' self.containing_dict = containing_dict ''' `dict` we'll try to remove ourselves from when 1 of our sleekrefs dies. ''' args_spec = cute_inspect.getargspec(function) star_args_name, star_kwargs_name = \ args_spec.varargs, args_spec.keywords call_args = cute_inspect.getcallargs(function, *args, **kwargs) del args, kwargs self.star_args_refs = [] '''Sleekrefs to star-args.''' if star_args_name: star_args = call_args.pop(star_args_name, None) if star_args: self.star_args_refs = [ SleekRef(star_arg, self.destroy) for star_arg in star_args ] self.star_kwargs_refs = {} '''Sleerefs to star-kwargs.''' if star_kwargs_name: star_kwargs = call_args.pop(star_kwargs_name, {}) if star_kwargs: self.star_kwargs_refs = CuteSleekValueDict( self.destroy, star_kwargs) self.args_refs = CuteSleekValueDict(self.destroy, call_args) '''Mapping from argument name to value, sleek-style.''' # In the future the `.args`, `.star_args` and `.star_kwargs` attributes # may change, so we must record the hash now: self._hash = cheat_hashing.cheat_hash( (self.args, self.star_args, self.star_kwargs))
def set_step_function(self, step_function): '''Set the step function for which we are specifying arguments.''' if self.step_function == step_function: return if self.step_function is not None: try: self.save() except ResolveFailed: pass self.step_function = step_function self.main_h_sizer.Clear(deleteWindows=True) arg_spec = cute_inspect.getargspec(step_function) step_profile_dialog = self.step_profile_dialog arg_dict = step_profile_dialog.\ step_functions_to_argument_dicts[step_function] star_arg_list = step_profile_dialog.\ step_functions_to_star_args[step_function] star_kwarg_dict = step_profile_dialog.\ step_functions_to_star_kwargs[step_function] if arg_spec.args[1:]: # Filtering the state which is always present self.arg_box = ArgBox(self, step_function) self.main_h_sizer.Add(self.arg_box.sizer, 0, wx.ALL, border=10) else: self.arg_box = None self.main_h_sizer.Add(Placeholder(self, '(No named arguments)'), 0, wx.ALL, border=10) if arg_spec.varargs: self.star_arg_box = StarArgBox(self, step_function) self.main_h_sizer.Add(self.star_arg_box.sizer, 0, wx.ALL, border=10) else: self.star_arg_box = None self.main_h_sizer.Add(Placeholder( self, '(No additional positional arguments)'), 0, wx.ALL, border=10) if arg_spec.keywords: self.star_kwarg_box = StarKwargBox(self, step_function) self.main_h_sizer.Add(self.star_kwarg_box.sizer, 0, wx.ALL, border=10) else: self.star_kwarg_box = None self.main_h_sizer.Add(Placeholder( self, '(No additional keyword arguments)'), 0, wx.ALL, border=10) self.main_h_sizer.Fit(self) self.Layout() self.step_profile_dialog.main_v_sizer.Fit(self.step_profile_dialog) self.step_profile_dialog.Layout() self.step_profile_dialog.Refresh()
def __init__(self, function, *args, **kwargs): ''' Construct the arguments profile. `*args` and `**kwargs` are the arguments that go into the `function`. ''' if not callable(function): raise Exception('%s is not a callable object.' % function) self.function = function raw_args = args raw_kwargs = kwargs del args, kwargs self.args = () '''Tuple of positional arguments.''' self.kwargs = OrderedDict() '''Ordered dict of keyword arguments.''' args_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = args_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more # consistent `()`. We fix that here: if s_defaults is None: s_defaults = () getcallargs_result = cute_inspect.getcallargs(function, *raw_args, **raw_kwargs) self.getcallargs_result = getcallargs_result # The number of args which have default values: n_defaultful_args = len(s_defaults) # The word "defaultful" means "something which has a default." ####################################################################### ####################################################################### # Now we'll create the arguments profile, using a 4-phases algorithm. # # # ####################################################################### # Phase 1: We specify all the args that don't have a default as # positional args: defaultless_args = s_args[:-n_defaultful_args] if n_defaultful_args \ else s_args[:] self.args += tuple( dict_tools.get_list(getcallargs_result, defaultless_args) ) ####################################################################### # Phase 2: We now have to deal with args that have a default. Some of # them, possibly none and possibly all of them, should be given # positionally. Some of them, possibly none, should be given by # keyword. And some of them, possibly none and possibly all of them, # should not be given at all. It is our job to figure out in which way # each argument should be given. # In this variable: n_defaultful_args_to_specify_positionally = None # We will put the number of defaultful arguments that should be # specified positionally. defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] # `dict` that maps from argument name to default value: defaults = OrderedDict(zip(defaultful_args, s_defaults)) defaultful_args_differing_from_defaults = set(( defaultful_arg for defaultful_arg in defaultful_args if defaults[defaultful_arg] != getcallargs_result[defaultful_arg] )) if s_star_args and getcallargs_result[s_star_args]: # We have some arguments that go into `*args`! This means that we # don't even need to think hard, we can already be sure that we're # going to have to specify *all* of the defaultful arguments # positionally, otherwise it will be impossible to put arguments in # `*args`. n_defaultful_args_to_specify_positionally = n_defaultful_args else: # `dict` mapping from each defaultful arg to the "price" of # specifying its value: prices_of_values = OrderedDict(( (defaultful_arg, len(repr(getcallargs_result[defaultful_arg]))) for defaultful_arg in defaultful_args )) # The price is simply the string length of the value's `repr`. # `dict` mapping from each defaultful arg to the "price" of # specifying it as a keyword (not including the length of the # value): prices_of_keyword_prefixes = OrderedDict( ((defaultful_arg, len(defaultful_arg)+1) for defaultful_arg in defaultful_args) ) # For example, if we have a defaultful arg "gravity_strength", then # specifiying it by keyword will require using the string # "gravity_strength=", which is 17 characters long, therefore the # price is 17. # Now we need to decide just how many defaultful args we are going # to specify positionally. The options are anything from `0` to # `n_defaultful_args`. We're going to go one by one, and calcluate # the price for each candidate, and put it in this dict: total_price_for_n_dasp_candidate = OrderedDict() # (The `n_dasp` here is an abbreivation of the # `n_defaultful_args_to_specify_positionally` variable defined # before.) # # After we have the price for each option, we'll select the one # with the lowest price. # One thing to do before iterating on the candidates is to find out # whether the "lonely comma discount" is in effect. # # The "lonely comma discount" is given when there's nothing but # defaultful arguments to this function, and therefore the number # of ", " strings needed here is not `candidate`, but `candidate - # 1`, unless of course candidate is zero. if not defaultless_args and \ (not s_star_args or not getcallargs_result[s_star_args]) and \ (not s_star_kwargs or not getcallargs_result[s_star_kwargs]): lonely_comma_discount_may_be_given = True else: lonely_comma_discount_may_be_given = False # Now we iterate on the candidates to find out which one has the # lowest price: for candidate in xrange(n_defaultful_args + 1): defaultful_args_to_specify_positionally = \ defaultful_args[:candidate] price_for_positionally_specified_defaultful_args = \ 2 * candidate + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_positionally ) ) # The `2 * candidate` addend is to account for the ", " parts # between the arguments. defaultful_args_to_specify_by_keyword = filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[candidate:] ) price_for_defaultful_args_specified_by_keyword = \ 2 * len(defaultful_args_to_specify_by_keyword) + \ sum( dict_tools.get_list( prices_of_keyword_prefixes, defaultful_args_to_specify_by_keyword ) ) + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_by_keyword ) ) # The `2 * len(...)` addend is to account for the ", " parts # between the arguments. # Now we need to figure out if this candidate gets the "lonely # comma discount". if lonely_comma_discount_may_be_given and \ (defaultful_args_to_specify_by_keyword or \ defaultful_args_to_specify_positionally): lonely_comma_discount = -2 else: lonely_comma_discount = 0 price = price_for_positionally_specified_defaultful_args + \ price_for_defaultful_args_specified_by_keyword + \ lonely_comma_discount total_price_for_n_dasp_candidate[candidate] = price # Finished iterating on candidates! Time to pick our winner. minimum_price = min(total_price_for_n_dasp_candidate.itervalues()) leading_candidates = [ candidate for candidate in total_price_for_n_dasp_candidate.iterkeys() if total_price_for_n_dasp_candidate[candidate] == minimum_price ] if len(leading_candidates) == 1: # We finished with one candidate which has the minimum price. # This is our winner. (winner,) = leading_candidates n_defaultful_args_to_specify_positionally = winner else: # We have a draw! We're gonna have to settle it by picking the # lowest candidate, because in our definition of "canonical # arguments profile", our second priority after "as few # characters as possible" is "as many keyword arguments as # possible". winner = leading_candidates[0] n_defaultful_args_to_specify_positionally = winner # We have a winner! Now we know exactly which defaultful args should # be specified positionally and which should be specified by # keyword. # First we add the positionally specified: defaultful_args_to_specify_positionally = \ defaultful_args[:n_defaultful_args_to_specify_positionally] self.args += tuple( (getcallargs_result[defaultful_arg] for defaultful_arg in defaultful_args_to_specify_positionally) ) # Now we add those specified by keyword: defaultful_args_to_specify_by_keyword = filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[n_defaultful_args_to_specify_positionally:] ) for defaultful_arg in defaultful_args_to_specify_by_keyword: self.kwargs[defaultful_arg] = getcallargs_result[defaultful_arg] ####################################################################### # Phase 3: Add the star args: if s_star_args and getcallargs_result[s_star_args]: assert not self.kwargs # Just making sure that no non-star args were specified by keyword, # which would make it impossible for us to put stuff in `*args`. self.args += getcallargs_result[s_star_args] ####################################################################### # Phase 4: Add the star kwargs: if s_star_kwargs and getcallargs_result[s_star_kwargs]: # We can't just add the `**kwargs` as is; we need to add them # according to canonical ordering. So we need to sort them first. unsorted_star_kwargs_names = \ getcallargs_result[s_star_kwargs].keys() sorted_star_kwargs_names = sorted( unsorted_star_kwargs_names, cmp=cmp_tools.underscore_hating_cmp ) sorted_star_kwargs = OrderedDict( zip( sorted_star_kwargs_names, dict_tools.get_list( getcallargs_result[s_star_kwargs], sorted_star_kwargs_names ) ) ) self.kwargs.update(sorted_star_kwargs) # Our 4-phases algorithm is done! The argument profile is canonical. # ####################################################################### ####################################################################### ####################################################################### # Now a bit of post-processing: _arguments = OrderedDict() dict_of_positional_arguments = OrderedDict( dict_tools.filter_items( getcallargs_result, lambda key, value: ((key not in self.kwargs) and \ (key != s_star_args) and \ (key != s_star_kwargs)) ) ) dict_of_positional_arguments.sort(key=s_args.index) _arguments.update(dict_of_positional_arguments) if s_star_args: _arguments['*'] = getcallargs_result[s_star_args] _arguments.update(self.kwargs) self._arguments = _arguments '''Ordered dict of arguments, both positional- and keyword-.''' # Caching the hash, since its computation can take a long time: self._hash = cheat_hashing.cheat_hash( ( self.function, self.args, tuple(self.kwargs) ) )
def __init__(self, step_profiles_controls, step_profile=None, and_fork=False): ''' Construct the `StepProfileDialog`. If given a `step_profile`, use it as a template. If it's `None`, start from scratch. Set `and_fork=True` if you intend to fork right after getting the step profile, though note it will only affect the labels; the actual forking is not done here. ''' self.step_profiles_controls = step_profiles_controls self.gui_project = step_profiles_controls.gui_project assert isinstance(self.gui_project, garlicsim_wx.GuiProject) self.frame = step_profiles_controls.frame self.and_fork = and_fork self.simpack = self.gui_project.simpack self.simpack_grokker = simpack_grokker = \ self.gui_project.simpack_grokker title = 'Create a new step profile' if not and_fork else \ 'Create a new step profile and fork with it' CuteDialog.__init__(self, step_profiles_controls.GetTopLevelParent(), title=title) self.SetDoubleBuffered(True) self.original_step_profile = original_step_profile = step_profile del step_profile self.hue = self.gui_project.step_profiles_to_hues.default_factory() self.step_functions_to_argument_dicts = \ StepFunctionsToArgumentDicts(self.describe) self.step_functions_to_star_args = \ collections.defaultdict(lambda: []) self.step_functions_to_star_kwargs = \ collections.defaultdict(lambda: {}) if original_step_profile: original_step_function = original_step_profile.step_function self.step_function = original_step_function initial_step_function_address = self.describe( original_step_function ) original_argument_dict = collections.defaultdict( lambda: '', original_step_profile.getcallargs_result ) self.step_functions_to_argument_dicts[original_step_function] = \ dict((key, self.describe(value)) for (key, value) in original_argument_dict.iteritems()) original_arg_spec = cute_inspect.getargspec(original_step_function) if original_arg_spec.varargs: star_args_value = original_step_profile.getcallargs_result[ original_arg_spec.varargs ] self.step_functions_to_star_args[original_step_function] = \ [self.describe(value) for value in star_args_value] if original_arg_spec.keywords: star_kwargs_value = original_step_profile.getcallargs_result[ original_arg_spec.keywords ] self.step_functions_to_star_kwargs[original_step_function] = \ dict((key, self.describe(value)) for (key, value) in star_kwargs_value.iteritems()) else: self.step_function = None if len(simpack_grokker.all_step_functions) >= 2: initial_step_function_address = '' else: # len(simpack_grokker.all_step_functions) == 1 initial_step_function_address = self.describe( simpack_grokker.default_step_function ) ####################################################################### # Setting up widgets and sizers: self.main_v_sizer = wx.BoxSizer(wx.VERTICAL) self.static_text = wx.StaticText(self, label="Choose a step function:") self.main_v_sizer.Add(self.static_text, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10) self.h_sizer = wx.BoxSizer(wx.HORIZONTAL) self.main_v_sizer.Add( self.h_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, border=10 ) self.hue_control = \ garlicsim_wx.widgets.general_misc.hue_control.HueControl( self, lambda: getattr(self, 'hue'), lambda hue: setattr(self, 'hue', hue), emitter=None, lightness=0.8, saturation=1, dialog_title='Select hue for new step profile', size=(25, 20) ) self.h_sizer.Add( self.hue_control, 0, wx.ALIGN_CENTER_VERTICAL ) self.h_sizer.AddSpacer(5) self.step_function_input = StepFunctionInput( self, value=initial_step_function_address ) self.h_sizer.Add( self.step_function_input, 0, wx.ALIGN_CENTER_VERTICAL, ) self.static_function_text = StaticFunctionText( self, step_function=original_step_function if original_step_profile \ else None ) self.h_sizer.Add( self.static_function_text, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15 ) self.argument_control = ArgumentControl( self, original_step_function if original_step_profile else None ) self.main_v_sizer.Add( self.argument_control, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=0 ) self.dialog_button_sizer = wx.StdDialogButtonSizer() self.main_v_sizer.Add( self.dialog_button_sizer, 0, wx.ALIGN_CENTER | wx.ALL, border=10 ) ok_title = 'Create step profile' if not and_fork else \ 'Create step profile and fork with it' self.ok_button = wx.Button(self, wx.ID_OK, title) self.dialog_button_sizer.AddButton(self.ok_button) self.ok_button.SetDefault() self.dialog_button_sizer.SetAffirmativeButton(self.ok_button) self.Bind(wx.EVT_BUTTON, self.on_ok, source=self.ok_button) self.cancel_button = wx.Button(self, wx.ID_CANCEL, 'Cancel') self.dialog_button_sizer.AddButton(self.cancel_button) self.Bind(wx.EVT_BUTTON, self.on_cancel, source=self.cancel_button) self.dialog_button_sizer.Realize() self.SetSizer(self.main_v_sizer) self.main_v_sizer.Fit(self)
def assert_same_signature(*callables): '''Assert that all the `callables` have the same function signature.''' arg_specs = [cute_inspect.getargspec(callable_) for callable_ in callables] if not logic_tools.all_equal(arg_specs, exhaustive=True): raise Failure('Not all the callables have the same signature.')
def set_step_function(self, step_function): '''Set the step function for which we are specifying arguments.''' if self.step_function == step_function: return if self.step_function is not None: try: self.save() except ResolveFailed: pass self.step_function = step_function self.main_h_sizer.Clear(deleteWindows=True) arg_spec = cute_inspect.getargspec(step_function) step_profile_dialog = self.step_profile_dialog arg_dict = step_profile_dialog.\ step_functions_to_argument_dicts[step_function] star_arg_list = step_profile_dialog.\ step_functions_to_star_args[step_function] star_kwarg_dict = step_profile_dialog.\ step_functions_to_star_kwargs[step_function] if arg_spec.args[1:]: # Filtering the state which is always present self.arg_box = ArgBox(self, step_function) self.main_h_sizer.Add(self.arg_box.sizer, 0, wx.ALL, border=10) else: self.arg_box = None self.main_h_sizer.Add( Placeholder(self, '(No named arguments)'), 0, wx.ALL, border=10 ) if arg_spec.varargs: self.star_arg_box = StarArgBox(self, step_function) self.main_h_sizer.Add(self.star_arg_box.sizer, 0, wx.ALL, border=10) else: self.star_arg_box = None self.main_h_sizer.Add( Placeholder(self, '(No additional positional arguments)'), 0, wx.ALL, border=10 ) if arg_spec.keywords: self.star_kwarg_box = StarKwargBox(self, step_function) self.main_h_sizer.Add(self.star_kwarg_box.sizer, 0, wx.ALL, border=10) else: self.star_kwarg_box = None self.main_h_sizer.Add( Placeholder(self, '(No additional keyword arguments)'), 0, wx.ALL, border=10 ) self.main_h_sizer.Fit(self) self.Layout() self.step_profile_dialog.main_v_sizer.Fit(self.step_profile_dialog) self.step_profile_dialog.Layout() self.step_profile_dialog.Refresh()
def __init__(self, function, *args, **kwargs): ''' Construct the arguments profile. `*args` and `**kwargs` are the arguments that go into the `function`. ''' if not callable(function): raise Exception('%s is not a callable object.' % function) self.function = function raw_args = args raw_kwargs = kwargs del args, kwargs self.args = () '''Tuple of positional arguments.''' self.kwargs = OrderedDict() '''Ordered dict of keyword arguments.''' args_spec = cute_inspect.getargspec(function) (s_args, s_star_args, s_star_kwargs, s_defaults) = args_spec # `getargspec` has a weird policy, when inspecting a function with no # defaults, to give a `defaults` of `None` instead of the more # consistent `()`. We fix that here: if s_defaults is None: s_defaults = () getcallargs_result = cute_inspect.getcallargs(function, *raw_args, **raw_kwargs) self.getcallargs_result = getcallargs_result # The number of args which have default values: n_defaultful_args = len(s_defaults) # The word "defaultful" means "something which has a default." ####################################################################### ####################################################################### # Now we'll create the arguments profile, using a 4-phases algorithm. # # # ####################################################################### # Phase 1: We specify all the args that don't have a default as # positional args: defaultless_args = s_args[:-n_defaultful_args] if n_defaultful_args \ else s_args[:] self.args += tuple( dict_tools.get_list(getcallargs_result, defaultless_args)) ####################################################################### # Phase 2: We now have to deal with args that have a default. Some of # them, possibly none and possibly all of them, should be given # positionally. Some of them, possibly none, should be given by # keyword. And some of them, possibly none and possibly all of them, # should not be given at all. It is our job to figure out in which way # each argument should be given. # In this variable: n_defaultful_args_to_specify_positionally = None # We will put the number of defaultful arguments that should be # specified positionally. defaultful_args = s_args[-n_defaultful_args:] if n_defaultful_args \ else [] # `dict` that maps from argument name to default value: defaults = OrderedDict(zip(defaultful_args, s_defaults)) defaultful_args_differing_from_defaults = set(( defaultful_arg for defaultful_arg in defaultful_args if defaults[defaultful_arg] != getcallargs_result[defaultful_arg])) if s_star_args and getcallargs_result[s_star_args]: # We have some arguments that go into `*args`! This means that we # don't even need to think hard, we can already be sure that we're # going to have to specify *all* of the defaultful arguments # positionally, otherwise it will be impossible to put arguments in # `*args`. n_defaultful_args_to_specify_positionally = n_defaultful_args else: # `dict` mapping from each defaultful arg to the "price" of # specifying its value: prices_of_values = OrderedDict( ((defaultful_arg, len(repr(getcallargs_result[defaultful_arg]))) for defaultful_arg in defaultful_args)) # The price is simply the string length of the value's `repr`. # `dict` mapping from each defaultful arg to the "price" of # specifying it as a keyword (not including the length of the # value): prices_of_keyword_prefixes = OrderedDict( ((defaultful_arg, len(defaultful_arg) + 1) for defaultful_arg in defaultful_args)) # For example, if we have a defaultful arg "gravity_strength", then # specifiying it by keyword will require using the string # "gravity_strength=", which is 17 characters long, therefore the # price is 17. # Now we need to decide just how many defaultful args we are going # to specify positionally. The options are anything from `0` to # `n_defaultful_args`. We're going to go one by one, and calcluate # the price for each candidate, and put it in this dict: total_price_for_n_dasp_candidate = OrderedDict() # (The `n_dasp` here is an abbreivation of the # `n_defaultful_args_to_specify_positionally` variable defined # before.) # # After we have the price for each option, we'll select the one # with the lowest price. # One thing to do before iterating on the candidates is to find out # whether the "lonely comma discount" is in effect. # # The "lonely comma discount" is given when there's nothing but # defaultful arguments to this function, and therefore the number # of ", " strings needed here is not `candidate`, but `candidate - # 1`, unless of course candidate is zero. if not defaultless_args and \ (not s_star_args or not getcallargs_result[s_star_args]) and \ (not s_star_kwargs or not getcallargs_result[s_star_kwargs]): lonely_comma_discount_may_be_given = True else: lonely_comma_discount_may_be_given = False # Now we iterate on the candidates to find out which one has the # lowest price: for candidate in xrange(n_defaultful_args + 1): defaultful_args_to_specify_positionally = \ defaultful_args[:candidate] price_for_positionally_specified_defaultful_args = \ 2 * candidate + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_positionally ) ) # The `2 * candidate` addend is to account for the ", " parts # between the arguments. defaultful_args_to_specify_by_keyword = filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[candidate:]) price_for_defaultful_args_specified_by_keyword = \ 2 * len(defaultful_args_to_specify_by_keyword) + \ sum( dict_tools.get_list( prices_of_keyword_prefixes, defaultful_args_to_specify_by_keyword ) ) + \ sum( dict_tools.get_list( prices_of_values, defaultful_args_to_specify_by_keyword ) ) # The `2 * len(...)` addend is to account for the ", " parts # between the arguments. # Now we need to figure out if this candidate gets the "lonely # comma discount". if lonely_comma_discount_may_be_given and \ (defaultful_args_to_specify_by_keyword or \ defaultful_args_to_specify_positionally): lonely_comma_discount = -2 else: lonely_comma_discount = 0 price = price_for_positionally_specified_defaultful_args + \ price_for_defaultful_args_specified_by_keyword + \ lonely_comma_discount total_price_for_n_dasp_candidate[candidate] = price # Finished iterating on candidates! Time to pick our winner. minimum_price = min(total_price_for_n_dasp_candidate.itervalues()) leading_candidates = [ candidate for candidate in total_price_for_n_dasp_candidate.iterkeys() if total_price_for_n_dasp_candidate[candidate] == minimum_price ] if len(leading_candidates) == 1: # We finished with one candidate which has the minimum price. # This is our winner. (winner, ) = leading_candidates n_defaultful_args_to_specify_positionally = winner else: # We have a draw! We're gonna have to settle it by picking the # lowest candidate, because in our definition of "canonical # arguments profile", our second priority after "as few # characters as possible" is "as many keyword arguments as # possible". winner = leading_candidates[0] n_defaultful_args_to_specify_positionally = winner # We have a winner! Now we know exactly which defaultful args should # be specified positionally and which should be specified by # keyword. # First we add the positionally specified: defaultful_args_to_specify_positionally = \ defaultful_args[:n_defaultful_args_to_specify_positionally] self.args += tuple( (getcallargs_result[defaultful_arg] for defaultful_arg in defaultful_args_to_specify_positionally)) # Now we add those specified by keyword: defaultful_args_to_specify_by_keyword = filter( defaultful_args_differing_from_defaults.__contains__, defaultful_args[n_defaultful_args_to_specify_positionally:]) for defaultful_arg in defaultful_args_to_specify_by_keyword: self.kwargs[defaultful_arg] = getcallargs_result[defaultful_arg] ####################################################################### # Phase 3: Add the star args: if s_star_args and getcallargs_result[s_star_args]: assert not self.kwargs # Just making sure that no non-star args were specified by keyword, # which would make it impossible for us to put stuff in `*args`. self.args += getcallargs_result[s_star_args] ####################################################################### # Phase 4: Add the star kwargs: if s_star_kwargs and getcallargs_result[s_star_kwargs]: # We can't just add the `**kwargs` as is; we need to add them # according to canonical ordering. So we need to sort them first. unsorted_star_kwargs_names = \ getcallargs_result[s_star_kwargs].keys() sorted_star_kwargs_names = sorted( unsorted_star_kwargs_names, cmp=cmp_tools.underscore_hating_cmp) sorted_star_kwargs = OrderedDict( zip( sorted_star_kwargs_names, dict_tools.get_list(getcallargs_result[s_star_kwargs], sorted_star_kwargs_names))) self.kwargs.update(sorted_star_kwargs) # Our 4-phases algorithm is done! The argument profile is canonical. # ####################################################################### ####################################################################### ####################################################################### # Now a bit of post-processing: _arguments = OrderedDict() dict_of_positional_arguments = OrderedDict( dict_tools.filter_items( getcallargs_result, lambda key, value: ((key not in self.kwargs) and \ (key != s_star_args) and \ (key != s_star_kwargs)) ) ) dict_of_positional_arguments.sort(key=s_args.index) _arguments.update(dict_of_positional_arguments) if s_star_args: _arguments['*'] = getcallargs_result[s_star_args] _arguments.update(self.kwargs) self._arguments = _arguments '''Ordered dict of arguments, both positional- and keyword-.''' # Caching the hash, since its computation can take a long time: self._hash = cheat_hashing.cheat_hash( (self.function, self.args, tuple(self.kwargs)))
def __init__(self, step_profiles_controls, step_profile=None, and_fork=False): ''' Construct the `StepProfileDialog`. If given a `step_profile`, use it as a template. If it's `None`, start from scratch. Set `and_fork=True` if you intend to fork right after getting the step profile, though note it will only affect the labels; the actual forking is not done here. ''' self.step_profiles_controls = step_profiles_controls self.gui_project = step_profiles_controls.gui_project assert isinstance(self.gui_project, garlicsim_wx.GuiProject) self.frame = step_profiles_controls.frame self.and_fork = and_fork self.simpack = self.gui_project.simpack self.simpack_grokker = simpack_grokker = \ self.gui_project.simpack_grokker title = 'Create a new step profile' if not and_fork else \ 'Create a new step profile and fork with it' CuteDialog.__init__(self, step_profiles_controls.GetTopLevelParent(), title=title) self.SetDoubleBuffered(True) self.original_step_profile = original_step_profile = step_profile del step_profile self.hue = self.gui_project.step_profiles_to_hues.default_factory() self.step_functions_to_argument_dicts = \ StepFunctionsToArgumentDicts(self.describe) self.step_functions_to_star_args = \ collections.defaultdict(lambda: []) self.step_functions_to_star_kwargs = \ collections.defaultdict(lambda: {}) if original_step_profile: original_step_function = original_step_profile.step_function self.step_function = original_step_function initial_step_function_address = self.describe( original_step_function) original_argument_dict = collections.defaultdict( lambda: '', original_step_profile.getcallargs_result) self.step_functions_to_argument_dicts[original_step_function] = \ dict((key, self.describe(value)) for (key, value) in original_argument_dict.iteritems()) original_arg_spec = cute_inspect.getargspec(original_step_function) if original_arg_spec.varargs: star_args_value = original_step_profile.getcallargs_result[ original_arg_spec.varargs] self.step_functions_to_star_args[original_step_function] = \ [self.describe(value) for value in star_args_value] if original_arg_spec.keywords: star_kwargs_value = original_step_profile.getcallargs_result[ original_arg_spec.keywords] self.step_functions_to_star_kwargs[original_step_function] = \ dict((key, self.describe(value)) for (key, value) in star_kwargs_value.iteritems()) else: self.step_function = None if len(simpack_grokker.all_step_functions) >= 2: initial_step_function_address = '' else: # len(simpack_grokker.all_step_functions) == 1 initial_step_function_address = self.describe( simpack_grokker.default_step_function) ####################################################################### # Setting up widgets and sizers: self.main_v_sizer = wx.BoxSizer(wx.VERTICAL) self.static_text = wx.StaticText(self, label="Choose a step function:") self.main_v_sizer.Add(self.static_text, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10) self.h_sizer = wx.BoxSizer(wx.HORIZONTAL) self.main_v_sizer.Add(self.h_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, border=10) self.hue_control = \ garlicsim_wx.widgets.general_misc.hue_control.HueControl( self, lambda: getattr(self, 'hue'), lambda hue: setattr(self, 'hue', hue), emitter=None, lightness=0.8, saturation=1, dialog_title='Select hue for new step profile', size=(25, 20) ) self.h_sizer.Add(self.hue_control, 0, wx.ALIGN_CENTER_VERTICAL) self.h_sizer.AddSpacer(5) self.step_function_input = StepFunctionInput( self, value=initial_step_function_address) self.h_sizer.Add( self.step_function_input, 0, wx.ALIGN_CENTER_VERTICAL, ) self.static_function_text = StaticFunctionText( self, step_function=original_step_function if original_step_profile \ else None ) self.h_sizer.Add(self.static_function_text, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, border=15) self.argument_control = ArgumentControl( self, original_step_function if original_step_profile else None) self.main_v_sizer.Add(self.argument_control, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, border=0) self.dialog_button_sizer = wx.StdDialogButtonSizer() self.main_v_sizer.Add(self.dialog_button_sizer, 0, wx.ALIGN_CENTER | wx.ALL, border=10) ok_title = 'Create step profile' if not and_fork else \ 'Create step profile and fork with it' self.ok_button = wx.Button(self, wx.ID_OK, title) self.dialog_button_sizer.AddButton(self.ok_button) self.ok_button.SetDefault() self.dialog_button_sizer.SetAffirmativeButton(self.ok_button) self.Bind(wx.EVT_BUTTON, self.on_ok, source=self.ok_button) self.cancel_button = wx.Button(self, wx.ID_CANCEL, 'Cancel') self.dialog_button_sizer.AddButton(self.cancel_button) self.Bind(wx.EVT_BUTTON, self.on_cancel, source=self.cancel_button) self.dialog_button_sizer.Realize() self.SetSizer(self.main_v_sizer) self.main_v_sizer.Fit(self)
return try: self.argument_control.save() except ResolveFailed, resolve_failed_exception: error_dialog = ErrorDialog(self, resolve_failed_exception.message) try: error_dialog.ShowModal() finally: error_dialog.Destroy() resolve_failed_exception.widget.SetFocus() return step_function = self.step_function arg_spec = cute_inspect.getargspec(step_function) step_profile = StepProfile.create_from_dld_format( step_function, dict((key, self.resolve(value_string)) for (key, value_string) in self.\ step_functions_to_argument_dicts[step_function].iteritems() if key in arg_spec.args), [self.resolve(value_string) for value_string in self.step_functions_to_star_args[step_function]], dict((key, self.resolve(value_string)) for (key, value_string) in