<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def signature_string(posargs, optargs, sep=', ', mod='!r'):
 """Return a stringified signature from given arguments. Parameters posargs : sequence Positional argument values, always included in the returned string. They appear in the string as (roughly):
: sep.join(str(arg) for arg in posargs) optargs : sequence of 3-tuples Optional arguments with names and defaults, given in the form:: Only those parameters that are different from the given default are included as ``name=value`` keyword pairs. **Note:** The comparison is done by using ``if value == default:``, which is not valid for, e.g., NumPy arrays. sep : string or sequence of strings, optional Separator(s) for the argument strings. A provided single string is used for all joining operations. A given sequence must have 3 entries ``pos_sep, opt_sep, part_sep``. The ``pos_sep`` and ``opt_sep`` strings are used for joining the respective sequences of argument strings, and ``part_sep`` joins these two joined strings. mod : string or callable or sequence, optional Format modifier(s) for the argument strings. In its most general form, ``mod`` is a sequence of 2 sequences ``pos_mod, opt_mod`` with ``len(pos_mod) == len(posargs)`` and ``len(opt_mod) == len(optargs)``. Each entry ``m`` in those sequences can be eiter a string, resulting in the following stringification of ``arg``:: arg_fmt = {{{}}}.format(m) arg_str = arg_fmt.format(arg) For a callable ``to_str``, the stringification is simply ``arg_str = to_str(arg)``. The entries ``pos_mod, opt_mod`` of ``mod`` can also be strings or callables instead of sequences, in which case the modifier applies to all corresponding arguments. Finally, if ``mod`` is a string or callable, it is applied to all arguments. The default behavior is to apply the "{!r}" (``repr``) conversion. For floating point scalars, the number of digits printed is determined by the ``precision`` value in NumPy's printing options, which can be temporarily modified with `npy_printoptions`. Returns ------- signature : string Stringification of a signature, typically used in the form:: '{}({})'.format(self.__class__.__name__, signature) Examples -------- Usage with non-trivial entries in both sequences, with a typical use case: "1, 'hello', None, dtype='float32'" "MyClass(1, 'hello', None, dtype='float32')" Empty sequences and optargs values equal to default are omitted: "'hello'" 'size=2' '' Using a different separator, globally or per argument "category": "1, 'hello', None, dtype='float32', order='F'" "1,'hello',None, dtype='float32',order='F'" Using format modifiers: "'hello', 2.345, extent=1.442, spacing=0.0151" 'he, 2.3, extent=1.4, spacing=0.015' "'hello', 2.345, extent=1.44, spacing=0.015" Using callables for stringification: "'arg1', [ 1., 1., 1.]" The number of printed digits in floating point numbers can be changed with `npy_printoptions`: "'hello', 0.12345679, extent=1.2345679" "'hello', 0.12, extent=1.2" """

    # Define the separators for the two possible cases
    if is_string(sep):
        pos_sep = opt_sep = part_sep = sep
    else:
        pos_sep, opt_sep, part_sep = sep

    # Get the stringified parts
    posargs_conv, optargs_conv = signature_string_parts(posargs, optargs, mod)

    # Join the arguments using the separators
    parts = []
    if posargs_conv:
        parts.append(pos_sep.join(argstr for argstr in posargs_conv))
    if optargs_conv:
        parts.append(opt_sep.join(optargs_conv))

    return part_sep.join(parts)