def normalized_index_expression(indices, shape, int_to_slice=False):
    """Enable indexing with almost Numpy-like capabilities.

    Implements the following features:

    - Usage of general slices and sequences of slices
    - Conversion of `Ellipsis` into an adequate number of ``slice(None)``
      objects
    - Fewer indices than axes by filling up with an `Ellipsis`
    - Error checking with respect to a given shape
    - Conversion of integer indices into corresponding slices

    Parameters
    ----------
    indices : int, `slice`, `Ellipsis` or sequence of these
        Index expression to be normalized.
    shape : sequence of ints
        Target shape for error checking of out-of-bounds indices.
        Also needed to determine the number of axes.
    int_to_slice : bool, optional
        If ``True``, turn integers into corresponding slice objects.

    Returns
    -------
    normalized : tuple of ints or `slice`'s
        Normalized index expression

    Examples
    --------
    Sequences are turned into tuples. We can have at most as many entries
    as the length of ``shape``, but fewer are allowed - the remaining
    list places are filled up by ``slice(None)``:

    >>> normalized_index_expression([1, 2, 3], shape=(3, 4, 5))
    (1, 2, 3)
    >>> normalized_index_expression([1, 2], shape=(3, 4, 5))
    (1, 2, slice(None, None, None))
    >>> normalized_index_expression([slice(2), 2], shape=(3, 4, 5))
    (slice(None, 2, None), 2, slice(None, None, None))
    >>> normalized_index_expression([1, Ellipsis], shape=(3, 4, 5))
    (1, slice(None, None, None), slice(None, None, None))

    By default, integer indices are kept. If they should be converted
    to slices, use ``int_to_slice=True``. This can be useful to guarantee
    that the result of slicing with the returned object is of the same
    type as the array into which is sliced and has the same number of
    axes:

    >>> x = np.zeros(shape=(3, 4, 5))
    >>> idx1 = normalized_index_expression([1, 2, 3], shape=(3, 4, 5),
    ...                                   int_to_slice=True)
    >>> idx1
    (slice(1, 2, None), slice(2, 3, None), slice(3, 4, None))
    >>> x[idx1]
    array([[[ 0.]]])
    >>> idx2 = normalized_index_expression([1, 2, 3], shape=(3, 4, 5),
    ...                                   int_to_slice=False)
    >>> idx2
    (1, 2, 3)
    >>> x[idx2]
    0.0
    """
    ndim = len(shape)
    # Support indexing with fewer indices as indexing along the first
    # corresponding axes. In the other cases, normalize the input.
    if np.isscalar(indices):
        indices = [indices, Ellipsis]
    elif (isinstance(indices, slice) or indices is Ellipsis):
        indices = [indices]

    indices = list(indices)
    if len(indices) < ndim and Ellipsis not in indices:
        indices.append(Ellipsis)

    # Turn Ellipsis into the correct number of slice(None)
    if Ellipsis in indices:
        if indices.count(Ellipsis) > 1:
            raise ValueError('cannot use more than one Ellipsis.')

        eidx = indices.index(Ellipsis)
        extra_dims = ndim - len(indices) + 1
        indices = (indices[:eidx] + [slice(None)] * extra_dims +
                   indices[eidx + 1:])

    # Turn single indices into length-1 slices if desired
    for (i, idx), n in zip(enumerate(indices), shape):
        if np.isscalar(idx):
            if idx < 0:
                idx += n

            if idx >= n:
                raise IndexError('Index {} is out of bounds for axis '
                                 '{} with size {}.'
                                 ''.format(idx, i, n))
            if int_to_slice:
                indices[i] = slice(idx, idx + 1)

    # Catch most common errors
    if any(s.start == s.stop and s.start is not None or
           s.start == n
           for s, n in zip(indices, shape) if isinstance(s, slice)):
        raise ValueError('Slices with empty axes not allowed.')
    if None in indices:
        raise ValueError('creating new axes is not supported.')
    if len(indices) > ndim:
        raise IndexError('too may indices: {} > {}.'
                         ''.format(len(indices), ndim))

    return tuple(indices)
Enable indexing with almost Numpy-like capabilities.

    Implements the following features:

    - Usage of general slices and sequences of slices
    - Conversion of `Ellipsis` into an adequate number of ``slice(None)``
      objects
    - Fewer indices than axes by filling up with an `Ellipsis`
    - Error checking with respect to a given shape
    - Conversion of integer indices into corresponding slices

    Parameters
    ----------
    indices : int, `slice`, `Ellipsis` or sequence of these
        Index expression to be normalized.
    shape : sequence of ints
        Target shape for error checking of out-of-bounds indices.
        Also needed to determine the number of axes.
    int_to_slice : bool, optional
        If ``True``, turn integers into corresponding slice objects.

    Returns
    -------
    normalized : tuple of ints or `slice`'s
        Normalized index expression

    Examples
    --------
    Sequences are turned into tuples. We can have at most as many entries
    as the length of ``shape``, but fewer are allowed - the remaining
    list places are filled up by ``slice(None)``:

    >>> normalized_index_expression([1, 2, 3], shape=(3, 4, 5))
    (1, 2, 3)
    >>> normalized_index_expression([1, 2], shape=(3, 4, 5))
    (1, 2, slice(None, None, None))
    >>> normalized_index_expression([slice(2), 2], shape=(3, 4, 5))
    (slice(None, 2, None), 2, slice(None, None, None))
    >>> normalized_index_expression([1, Ellipsis], shape=(3, 4, 5))
    (1, slice(None, None, None), slice(None, None, None))

    By default, integer indices are kept. If they should be converted
    to slices, use ``int_to_slice=True``. This can be useful to guarantee
    that the result of slicing with the returned object is of the same
    type as the array into which is sliced and has the same number of
    axes:

    >>> x = np.zeros(shape=(3, 4, 5))
    >>> idx1 = normalized_index_expression([1, 2, 3], shape=(3, 4, 5),
    ...                                   int_to_slice=True)
    >>> idx1
    (slice(1, 2, None), slice(2, 3, None), slice(3, 4, None))
    >>> x[idx1]
    array([[[ 0.]]])
    >>> idx2 = normalized_index_expression([1, 2, 3], shape=(3, 4, 5),
    ...                                   int_to_slice=False)
    >>> idx2
    (1, 2, 3)
    >>> x[idx2]
    0.0