def repr_string(outer_string, inner_strings, allow_mixed_seps=True):
    r"""Return a pretty string for ``repr``.

    The returned string is formatted such that it does not extend
    beyond the line boundary if avoidable. The line width is taken from
    NumPy's printing options that can be retrieved with
    `numpy.get_printoptions`. They can be temporarily overridden
    using the `npy_printoptions` context manager. See Examples for details.

    Parameters
    ----------
    outer_str : str
        Name of the class or function that should be printed outside
        the parentheses.
    inner_strings : sequence of sequence of str
        Stringifications of the positional and optional arguments.
        This is usually the return value of `signature_string_parts`.
    allow_mixed_seps : bool, optional
        If ``False`` and the string does not fit on one line, use
        ``',\n'`` to separate all strings.
        By default, a mixture of ``', '`` and ``',\n'`` is used to fit
        as much on one line as possible.

        In case some of the ``inner_strings`` span multiple lines, it is
        usually advisable to set ``allow_mixed_seps`` to ``False`` since
        the result tends to be more readable that way.

    Returns
    -------
    repr_string : str
        Full string that can be returned by a class' ``__repr__`` method.

    Examples
    --------
    Things that fit into one line are printed on one line:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('1', "'hello'", 'None'),
    ...                  ("dtype='float32'",)]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(1, 'hello', None, dtype='float32')

    Otherwise, if a part of ``inner_strings`` fits on a line of its own,
    it is printed on one line, but separated from the other part with
    a line break:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('2.0', "'this_is_a_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",)]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(
        2.0, 'this_is_a_very_long_argument_string',
        long_opt_arg='another_quite_long_string'
    )

    If those parts are themselves too long, they are broken down into
    several lines:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [("'this_is_a_very_long_argument_string'",
    ...                   "'another_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",
    ...                   "long_opt2_arg='this_wont_fit_on_one_line_either'")]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(
        'this_is_a_very_long_argument_string',
        'another_very_long_argument_string',
        long_opt_arg='another_quite_long_string',
        long_opt2_arg='this_wont_fit_on_one_line_either'
    )

    The usage of mixed separators to optimally use horizontal space can
    be disabled by setting ``allow_mixed_seps=False``:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('2.0', "'this_is_a_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",)]
    >>> print(repr_string(outer_string, inner_strings, allow_mixed_seps=False))
    MyClass(
        2.0,
        'this_is_a_very_long_argument_string',
        long_opt_arg='another_quite_long_string'
    )

    With the ``npy_printoptions`` context manager, the available line
    width can be changed:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('1', "'hello'", 'None'),
    ...                  ("dtype='float32'",)]
    >>> with npy_printoptions(linewidth=20):
    ...     print(repr_string(outer_string, inner_strings))
    MyClass(
        1, 'hello',
        None,
        dtype='float32'
    )
    """
    linewidth = np.get_printoptions()['linewidth']
    pos_strings, opt_strings = inner_strings
    # Length of the positional and optional argument parts of the signature,
    # including separators `', '`
    pos_sig_len = (sum(len(pstr) for pstr in pos_strings) +
                   2 * max((len(pos_strings) - 1), 0))
    opt_sig_len = (sum(len(pstr) for pstr in opt_strings) +
                   2 * max((len(opt_strings) - 1), 0))

    # Length of the one-line string, including 2 for the parentheses and
    # 2 for the joining ', '
    repr_len = len(outer_string) + 2 + pos_sig_len + 2 + opt_sig_len

    if repr_len <= linewidth and not any('\n' in s
                                         for s in pos_strings + opt_strings):
        # Everything fits on one line
        fmt = '{}({})'
        pos_str = ', '.join(pos_strings)
        opt_str = ', '.join(opt_strings)
        parts_sep = ', '
    else:
        # Need to split lines in some way
        fmt = '{}(\n{}\n)'

        if not allow_mixed_seps:
            pos_separators = [',\n'] * (len(pos_strings) - 1)
        else:
            pos_separators = _separators(pos_strings, linewidth)
        if len(pos_strings) == 0:
            pos_str = ''
        else:
            pos_str = pos_strings[0]
            for s, sep in zip(pos_strings[1:], pos_separators):
                pos_str = sep.join([pos_str, s])

        if not allow_mixed_seps:
            opt_separators = [',\n'] * (len(opt_strings) - 1)
        else:
            opt_separators = _separators(opt_strings, linewidth)
        if len(opt_strings) == 0:
            opt_str = ''
        else:
            opt_str = opt_strings[0]
            for s, sep in zip(opt_strings[1:], opt_separators):
                opt_str = sep.join([opt_str, s])

        # Check if we can put both parts on one line. This requires their
        # concatenation including 4 for indentation and 2 for ', ' to
        # be less than the line width. And they should contain no newline.
        if pos_str and opt_str:
            inner_len = 4 + len(pos_str) + 2 + len(opt_str)
        elif (pos_str and not opt_str) or (opt_str and not pos_str):
            inner_len = 4 + len(pos_str) + len(opt_str)
        else:
            inner_len = 0

        if (not allow_mixed_seps or
                any('\n' in s for s in [pos_str, opt_str]) or
                inner_len > linewidth):
            parts_sep = ',\n'
            pos_str = indent(pos_str)
            opt_str = indent(opt_str)
        else:
            parts_sep = ', '
            pos_str = indent(pos_str)
            # Don't indent `opt_str`

    parts = [s for s in [pos_str, opt_str] if s.strip()]  # ignore empty
    inner_string = parts_sep.join(parts)
    return fmt.format(outer_string, inner_string)
r"""Return a pretty string for ``repr``.

    The returned string is formatted such that it does not extend
    beyond the line boundary if avoidable. The line width is taken from
    NumPy's printing options that can be retrieved with
    `numpy.get_printoptions`. They can be temporarily overridden
    using the `npy_printoptions` context manager. See Examples for details.

    Parameters
    ----------
    outer_str : str
        Name of the class or function that should be printed outside
        the parentheses.
    inner_strings : sequence of sequence of str
        Stringifications of the positional and optional arguments.
        This is usually the return value of `signature_string_parts`.
    allow_mixed_seps : bool, optional
        If ``False`` and the string does not fit on one line, use
        ``',\n'`` to separate all strings.
        By default, a mixture of ``', '`` and ``',\n'`` is used to fit
        as much on one line as possible.

        In case some of the ``inner_strings`` span multiple lines, it is
        usually advisable to set ``allow_mixed_seps`` to ``False`` since
        the result tends to be more readable that way.

    Returns
    -------
    repr_string : str
        Full string that can be returned by a class' ``__repr__`` method.

    Examples
    --------
    Things that fit into one line are printed on one line:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('1', "'hello'", 'None'),
    ...                  ("dtype='float32'",)]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(1, 'hello', None, dtype='float32')

    Otherwise, if a part of ``inner_strings`` fits on a line of its own,
    it is printed on one line, but separated from the other part with
    a line break:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('2.0', "'this_is_a_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",)]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(
        2.0, 'this_is_a_very_long_argument_string',
        long_opt_arg='another_quite_long_string'
    )

    If those parts are themselves too long, they are broken down into
    several lines:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [("'this_is_a_very_long_argument_string'",
    ...                   "'another_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",
    ...                   "long_opt2_arg='this_wont_fit_on_one_line_either'")]
    >>> print(repr_string(outer_string, inner_strings))
    MyClass(
        'this_is_a_very_long_argument_string',
        'another_very_long_argument_string',
        long_opt_arg='another_quite_long_string',
        long_opt2_arg='this_wont_fit_on_one_line_either'
    )

    The usage of mixed separators to optimally use horizontal space can
    be disabled by setting ``allow_mixed_seps=False``:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('2.0', "'this_is_a_very_long_argument_string'"),
    ...                  ("long_opt_arg='another_quite_long_string'",)]
    >>> print(repr_string(outer_string, inner_strings, allow_mixed_seps=False))
    MyClass(
        2.0,
        'this_is_a_very_long_argument_string',
        long_opt_arg='another_quite_long_string'
    )

    With the ``npy_printoptions`` context manager, the available line
    width can be changed:

    >>> outer_string = 'MyClass'
    >>> inner_strings = [('1', "'hello'", 'None'),
    ...                  ("dtype='float32'",)]
    >>> with npy_printoptions(linewidth=20):
    ...     print(repr_string(outer_string, inner_strings))
    MyClass(
        1, 'hello',
        None,
        dtype='float32'
    )