コード例 #1
0
    def change_defaults_(function_, new_defaults_):
        signature = inspect.Signature.from_function(function_)
        defaults = list(function_.__defaults__ or ())
        kwdefaults = function_.__kwdefaults__ or {}
        defaultful_parameters = dict_tools.filter_items(
            signature.parameters,
            lambda name, parameter: parameter.default != inspect._empty,
            force_dict_type=collections.OrderedDict)
        (keyword_only_defaultful_parameters,
         non_keyword_only_defaultful_parameters) = dict_tools.filter_items(
             defaultful_parameters,
             lambda name, parameter: parameter.kind == inspect._KEYWORD_ONLY,
             double=True,
         )

        non_existing_arguments = set(new_defaults) - set(defaultful_parameters)
        if non_existing_arguments:
            raise Exception("Arguments %s are not defined, or do not have a "
                            "default defined. (Can't create default value for "
                            "argument that has no existing default.)" %
                            non_existing_arguments)

        for parameter_name in keyword_only_defaultful_parameters:
            if parameter_name in new_defaults_:
                kwdefaults[parameter_name] = new_defaults_[parameter_name]

        for i, parameter_name in \
                             enumerate(non_keyword_only_defaultful_parameters):
            if parameter_name in new_defaults_:
                defaults[i] = new_defaults_[parameter_name]

        function_.__defaults__ = tuple(defaults)
        function_.__kwdefaults__ = kwdefaults

        return function_
コード例 #2
0
 def change_defaults_(function_, new_defaults_):
     signature = funcsigs.Signature.from_function(function_)
     defaults = list(function_.__defaults__ or ())
     non_keyword_only_defaultful_parameters = defaultful_parameters = \
         dict_tools.filter_items(
         signature.parameters,
         lambda name, parameter: parameter.default != funcsigs._empty,
         force_dict_type=collections.OrderedDict
     )
     
     non_existing_arguments = set(new_defaults) - set(defaultful_parameters)
     if non_existing_arguments:
         raise Exception("Arguments %s are not defined, or do not have a "
                         "default defined. (Can't create default value for "
                         "argument that has no existing default.)"
                         % non_existing_arguments)
     
     for i, parameter_name in \
                          enumerate(non_keyword_only_defaultful_parameters):
         if parameter_name in new_defaults_:
             defaults[i] = new_defaults_[parameter_name]
             
     function_.__defaults__ = tuple(defaults)
     
     return function_
コード例 #3
0
    def change_defaults_(function_, new_defaults_):
        signature = funcsigs.Signature.from_function(function_)
        defaults = list(function_.__defaults__ or ())
        non_keyword_only_defaultful_parameters = defaultful_parameters = \
            dict_tools.filter_items(
            signature.parameters,
            lambda name, parameter: parameter.default != funcsigs._empty,
            force_dict_type=collections.OrderedDict
        )

        non_existing_arguments = set(new_defaults) - set(defaultful_parameters)
        if non_existing_arguments:
            raise Exception("Arguments %s are not defined, or do not have a "
                            "default defined. (Can't create default value for "
                            "argument that has no existing default.)" %
                            non_existing_arguments)

        for i, parameter_name in \
                             enumerate(non_keyword_only_defaultful_parameters):
            if parameter_name in new_defaults_:
                defaults[i] = new_defaults_[parameter_name]

        function_.__defaults__ = tuple(defaults)

        return function_
コード例 #4
0
 def change_defaults_(function_, new_defaults_):
     signature = inspect.Signature.from_function(function_)
     defaults = list(function_.__defaults__ or ())
     kwdefaults = function_.__kwdefaults__ or {}
     defaultful_parameters = dict_tools.filter_items(
         signature.parameters,
         lambda name, parameter: parameter.default != inspect._empty,
         force_dict_type=collections.OrderedDict
     )
     (keyword_only_defaultful_parameters,
      non_keyword_only_defaultful_parameters) = dict_tools.filter_items(
          defaultful_parameters,
          lambda name, parameter: parameter.kind == inspect._KEYWORD_ONLY,
          double=True, 
      )
     
     non_existing_arguments = set(new_defaults) - set(defaultful_parameters)
     if non_existing_arguments:
         raise Exception("Arguments %s are not defined, or do not have a "
                         "default defined. (Can't create default value for "
                         "argument that has no existing default.)"
                         % non_existing_arguments)
     
     for parameter_name in keyword_only_defaultful_parameters:
         if parameter_name in new_defaults_:
             kwdefaults[parameter_name] = new_defaults_[parameter_name]
             
     for i, parameter_name in \
                          enumerate(non_keyword_only_defaultful_parameters):
         if parameter_name in new_defaults_:
             defaults[i] = new_defaults_[parameter_name]
             
     function_.__defaults__ = tuple(defaults)
     function_.__kwdefaults__ = kwdefaults
     
     return function_
コード例 #5
0
    def _BindSavvyEvtHandlerType__event_handler_grokkers(cls):
        '''
        The `EventHandlerGrokker` objects for this window.
        
        Each grokker corresponds to an event handler function and its
        responsibilty is to figure out the correct event to handle based on the
        function's name. See documentation of `EventHandlerGrokker` for more
        information.
        '''

        names_to_event_handlers = dict_tools.filter_items(
            vars(cls),
            lambda name, value: cls._BindSavvyEvtHandlerType__name_parser.
            match(name, cls.__name__) and callable(value) and getattr(
                value, '_BindSavvyEvtHandlerType__dont_bind_automatically',
                None) is not True)
        '''Dict mapping names to event handling functions.'''

        return [
            EventHandlerGrokker(name, value, cls)
            for (name, value) in names_to_event_handlers.items()
        ]
コード例 #6
0
 def _BindSavvyEvtHandlerType__event_handler_grokkers(cls):
     '''
     The `EventHandlerGrokker` objects for this window.
     
     Each grokker corresponds to an event handler function and its
     responsibilty is to figure out the correct event to handle based on the
     function's name. See documentation of `EventHandlerGrokker` for more
     information.
     '''
     
     names_to_event_handlers = dict_tools.filter_items(
         vars(cls),
         lambda name, value:
             cls._BindSavvyEvtHandlerType__name_parser.match(name,
                                                         cls.__name__) and
             callable(value) and
             getattr(value, '_BindSavvyEvtHandlerType__dont_bind_automatically',
                     None) is not True
     )
     '''Dict mapping names to event handling functions.'''
     
     return [EventHandlerGrokker(name, value, cls) for (name, value) in
             names_to_event_handlers.items()]
コード例 #7
0
def get_address(obj, shorten=False, root=None, namespace={}):
    '''
    Get the address of a Python object.
    
    This only works for objects that have addresses, like modules, classes,
    functions, methods, etc. It usually doesn't work on instances created
    during the program. (e.g. `[1, 2]` doesn't have an address.)
    '''
    # todo: Support classes inside classes. Currently doesn't work because
    # Python doesn't tell us inside in which class an inner class was defined.
    # We'll probably have to do some kind of search.

    if not (isinstance(obj, types.ModuleType) or hasattr(obj, '__module__')):
        raise TypeError("`%s` is not a module, nor does it have a "
                        "`.__module__` attribute, therefore we can't get its "
                        "address." % (obj, ))

    if isinstance(obj, types.ModuleType):
        address = obj.__name__
    elif isinstance(obj, types.MethodType):
        address = '.'.join(
            (obj.__module__, obj.__self__.__class__.__name__, obj.__name__))
    else:
        address = '.'.join((obj.__module__, obj.__name__))

    # Now our attempt at an address is in `address`. Let's `try` to resolve
    # that address to see if it's right and we get the same object:
    try:
        object_candidate = get_object_by_address(address)
    except Exception:
        confirmed_object_address = False
    else:
        is_same_object = \
            (obj == object_candidate) if isinstance(obj, types.MethodType) \
            else (obj is object_candidate)
        confirmed_object_address = is_same_object

    if not confirmed_object_address:
        # We weren't able to confirm that the `address` we got is the correct
        # one for this object, so we won't even try to shorten it in any way,
        # just return what we got and hoped we didn't disappoint the user too
        # badly:
        return address

    assert confirmed_object_address is True
    # We confirmed we got the right `address`! Now we can try to shorten it
    # some, if the user specified so in the arguments:

    ### Shortening the address using `root` and/or `namespace`: ###############
    #                                                                         #

    if root or namespace:

        # Ensuring `root` and `namespace` are actual objects:
        if isinstance(root, str):
            root = get_object_by_address(root)
        if isinstance(namespace, str):
            namespace = get_object_by_address(namespace)

        if namespace:

            (_useless, original_namespace_dict) = \
                _get_parent_and_dict_from_namespace(namespace)

            def my_filter(key, value):
                name = getattr(value, '__name__', '')
                return isinstance(name, str) and name.endswith(key)

            namespace_dict = dict_tools.filter_items(original_namespace_dict,
                                                     my_filter)

            namespace_dict_keys = namespace_dict.keys()
            namespace_dict_values = namespace_dict.values()

        # Split to address parts:
        address_parts = address.split('.')
        # e.g., `['python_toolbox', 'misc', 'step_copy', 'StepCopy']`.

        heads = [
            '.'.join(address_parts[:i]) for i in range(1,
                                                       len(address_parts) + 1)
        ]
        # `heads` is something like: `['python_toolbox',
        # 'python_toolbox.caching', 'python_toolbox.caching.cached_type',
        # 'python_toolbox.cached_type.CachedType']`

        for head in reversed(heads):
            object_ = get_object_by_address(head)
            if root:
                if object_ is root:
                    root_short_name = root.__name__.rsplit('.', 1)[-1]
                    address = address.replace(head, root_short_name, 1)
                    break
            if namespace:
                if object_ in namespace_dict_values:
                    fitting_keys = [
                        key for key in namespace_dict_keys
                        if namespace_dict[key] is object_
                    ]
                    key = min(fitting_keys, key=len)
                    address = address.replace(head, key, 1)

    #                                                                         #
    ### Finshed shortening address using `root` and/or `namespace`. ###########

    # If user specified `shorten=True`, let the dedicated `shorten_address`
    # function drop redundant intermediate nodes:
    if shorten:
        address = shorten_address(address, root=root, namespace=namespace)

    # A little fix to avoid describing something like `list` as
    # `__builtin__.list`:
    if address.startswith('__builtin__.'):
        shorter_address = address.replace('__builtin__.', '', 1)
        if get_object_by_address(shorter_address) == obj:
            address = shorter_address

    return address
コード例 #8
0
def get_address(obj, shorten=False, root=None, namespace={}):
    '''
    Get the address of a Python object.
    
    This only works for objects that have addresses, like modules, classes,
    functions, methods, etc. It usually doesn't work on instances created
    during the program. (e.g. `[1, 2]` doesn't have an address.)
    '''
    # todo: Support classes inside classes. Currently doesn't work because
    # Python doesn't tell us inside in which class an inner class was defined.
    # We'll probably have to do some kind of search.
    
    if not (isinstance(obj, types.ModuleType) or hasattr(obj, '__module__')):
        raise TypeError("`%s` is not a module, nor does it have a "
                        "`.__module__` attribute, therefore we can't get its "
                        "address." % (obj,))
    
    if isinstance(obj, types.ModuleType):
        address = obj.__name__
    elif isinstance(obj, types.MethodType):
        address = '.'.join((obj.__module__, obj.__self__.__class__.__name__,
                            obj.__name__))
    else:
        address= '.'.join((obj.__module__, obj.__name__))

    # Now our attempt at an address is in `address`. Let's `try` to resolve
    # that address to see if it's right and we get the same object:        
    try:
        object_candidate = get_object_by_address(address)
    except Exception:
        confirmed_object_address = False 
    else:
        is_same_object = \
            (obj == object_candidate) if isinstance(obj, types.MethodType) \
            else (obj is object_candidate)
        confirmed_object_address = is_same_object
        
    if not confirmed_object_address:
        # We weren't able to confirm that the `address` we got is the correct
        # one for this object, so we won't even try to shorten it in any way,
        # just return what we got and hoped we didn't disappoint the user too
        # badly:
        return address

    assert confirmed_object_address is True
    # We confirmed we got the right `address`! Now we can try to shorten it
    # some, if the user specified so in the arguments:

    ### Shortening the address using `root` and/or `namespace`: ###############
    #                                                                         #
    
    if root or namespace:
        
        # Ensuring `root` and `namespace` are actual objects:
        if isinstance(root, str):
            root = get_object_by_address(root)            
        if isinstance(namespace, str):
            namespace = get_object_by_address(namespace)


        if namespace:
            
            (_useless, original_namespace_dict) = \
                _get_parent_and_dict_from_namespace(namespace)
            
            def my_filter(key, value):
                name = getattr(value, '__name__', '')
                return isinstance(name, str) and name.endswith(key)

            namespace_dict = dict_tools.filter_items(
                original_namespace_dict,
                my_filter
            )
                
            namespace_dict_keys = namespace_dict.keys()
            namespace_dict_values = namespace_dict.values()
            
            
        # Split to address parts:
        address_parts = address.split('.')
        # e.g., `['python_toolbox', 'misc', 'step_copy', 'StepCopy']`.
        
        heads = ['.'.join(address_parts[:i]) for i in
                 range(1, len(address_parts) + 1)]
        # `heads` is something like: `['python_toolbox',
        # 'python_toolbox.caching', 'python_toolbox.caching.cached_type',
        # 'python_toolbox.cached_type.CachedType']`

        
        for head in reversed(heads):
            object_ = get_object_by_address(head)
            if root:
                if object_ is root:
                    root_short_name = root.__name__.rsplit('.', 1)[-1]
                    address = address.replace(head, root_short_name, 1)
                    break
            if namespace:
                if object_ in namespace_dict_values:
                    fitting_keys = [key for key in namespace_dict_keys if
                                    namespace_dict[key] is object_]
                    key = min(fitting_keys, key=len)
                    address = address.replace(head, key, 1)

    #                                                                         #
    ### Finshed shortening address using `root` and/or `namespace`. ###########
                    

    # If user specified `shorten=True`, let the dedicated `shorten_address`
    # function drop redundant intermediate nodes:
    if shorten:
        address = shorten_address(address, root=root, namespace=namespace)
        
    
    # A little fix to avoid describing something like `list` as
    # `__builtin__.list`:
    if address.startswith('__builtin__.'):
        shorter_address = address.replace('__builtin__.', '', 1)
        if get_object_by_address(shorter_address) == obj:
            address = shorter_address

            
    return address
コード例 #9
0
    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 = {
            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 range(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 = list(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.values())
            
            leading_candidates = [
                candidate for candidate in 
                total_price_for_n_dasp_candidate.keys() 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 = list(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 = \
                list(getcallargs_result[s_star_kwargs].keys())
            sorted_star_kwargs_names = sorted(
                unsorted_star_kwargs_names,
                key=comparison_tools.underscore_hating_key
            )
            
            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)
            )
        )
コード例 #10
0
    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 = {
            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 range(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 = list(
                    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.values())

            leading_candidates = [
                candidate
                for candidate in total_price_for_n_dasp_candidate.keys()
                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 = list(
            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 = \
                list(getcallargs_result[s_star_kwargs].keys())
            sorted_star_kwargs_names = sorted(
                unsorted_star_kwargs_names,
                key=comparison_tools.underscore_hating_key)

            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)))