[Python-ideas] Possible new slice behaviour? Was ( Negative slice discussion.)

Ron Adam ron3200 at gmail.com
Mon Nov 4 18:34:48 CET 2013


This is one solution to what we can do to make slices both easier to 
understand and work in a much more consistent and flexible way.

This matches the slice semantics that Guido likes.
(possibly for python 4.)

The ability to pass callables along with the slice creates an easy and 
clean way to add new indexing modes.  (As Nick suggested.)

Overall, this makes everything simpler and easier to do.  :-)


Cheers,
     Ron


"""

     An improved slice implementation.

     Even the C source says:

         "It's harder to get right than you might think."

  both in code and in understanding
     This requires changing slice indexing so that
     the following relationships are true.

        s[i:j:k]   ==  s[i:j][k] both in code and in understanding

        s[i:j:-1]  ==  s[i:j:1][::-1]


     And it also adds the ability to apply callables to
     slices and index's using the existing slice syntax.

     These alterations would need to be made to the __getitem__
     and __setitem__, methods of built-in types.  Possibly in
     Python 4.0.

     *(I was not able to get a clean version of this behaviour
     with the existing slice semantics.  But the slice and index
     behaviour of this implementation is much simpler and makes
     using callables to adjust index's very easy.  That seems
     like a good indication that changing slices to match the
     above relationships is worth doing.

     It may be possible to get the current behaviour by applying
     a callable to the slice like the open, closed, and ones index
     examples below.
     )

"""

# A string sub-class for testing.

class Str(str):

     def _fix_slice_indexes(self, slc):
         #  Replace Nones and check step value.
         if isinstance(slc, int):
             return slc
         i, j, k = slc.start, slc.stop, slc.step
         if k == 0:
             raise ValueError("slice step cannot be zero")
         if i == None: i = 0
         if j == None: j = len(self)
         if k == None: k = 1
         return slice(i, j, k)

     def __getitem__(self, args):
         """
         Gets a item from a string with either an
         index, or slice.  Apply any callables to the
         slice if they are pressent.

         Valid inputes...
              i
              (i, callables ...)
              slice()
              (slice(), callables ...)

         """
         # Apply callables if any.
         if isinstance(args, tuple):
             slc, *callables = args
             slc = self._fix_slice_indexes(slc)
             for fn in callables:
                 slc = fn(self, slc)
         else:
             slc = self._fix_slice_indexes(args)

         # Just an index
         if isinstance(slc, int):
             return str.__getitem__(self, slc)

         # Handle slice.
         rval = []
         i, j, k = slc.start, slc.stop, slc.step
         ix = i if k > 0 else j-1
         while i <= ix < j:
             rval.append(str.__getitem__(self, ix))
             ix += k
         return type(self)('').join(rval)


"""
    These end with 'i' to indicate they make index adjustments,
    and also to make them less likely to clash with other
    functions.

    Some of these are so simple, you'd probably just
    adjust the index directly, ie.. reversei.  But
    they make good examples of what is possible.  And possible
    There are other uses as well.

    Because they are just objects passed in, the names aren't
    important. They can be called anything and still work, and
    the programmer is free to create new alternatives.
"""

def reversei(obj, slc):
     """Return a new slice with reversed step."""
     if isinstance(slc, slice):
         i, j, k = slc.start, slc.stop, slc.step
         return slice(i, j, -k)
     return slc

def trimi(obj, slc):
     """Trim left and right so an IndexError is not produced."""
     if isinstance(slc, slice):
         ln = len(obj)
         i, j, k = slc.start, slc.stop, slc.step
         if i<0: i = 0
         if j>ln: j = ln
         return slice(i, j, k)
     return slc

def openi(obj, slc):
     """Open interval - Does not include end points."""
     if isinstance(slc, slice):
         i, j, k = slc.start, slc.stop, slc.step
         return slice(i+1, j, k)
     return slc

def closedi(obj, slc):
     """Closed interval - Includes end points."""
     if isinstance(slc, slice):
         i, j, k = slc.start, slc.stop, slc.step
         return slice(i, j+1, k)
     return slc

def onei(obj, slc):
     """First element is 1 instead of zero."""
     if isinstance(slc, slice):
         i, j, k = slc.start, slc.stop, slc.step
         return slice(i-1, j-1, k)
     return slc - 1



def _test_cases1():
     """

     # test string
     >>> s = Str('0123456789')

     #  |0|1|2|3|4|5|6|7|8|9|
     #  0 1 2 3 4 5 6 7 8 9 10
     # 10 9 8 7 6 5 4 3 2 1 0


     >>> s[:]
     '0123456789'

     >>> s[:, trimi]
     '0123456789'

     >>> s[:, reversei]
     '9876543210'

     >>> s[:, reversei, trimi]
     '9876543210'

     >>> s[::, trimi, reversei]
     '9876543210'


     # Right side bigger than len(s)

     >>> s[:100]
     Traceback (most recent call last):
     IndexError: string index out of range

     >>> s[:100, trimi]
     '0123456789'

     >>> s[:100, trimi, reversei]
     '9876543210'

     >>> s[:100, reversei]
     Traceback (most recent call last):
     IndexError: string index out of range

     >>> s[:100, reversei, trimi]
     '9876543210'


     # Left side smaller than 0.

     >>> s[-100:]
     Traceback (most recent call last):
     IndexError: string index out of range

     >>> s[-100:, trimi]
     '0123456789'

     >>> s[-100:, trimi, reversei]
     '9876543210'


     # Slice bigger than s.

     >>> s[-100:100]
     Traceback (most recent call last):
     IndexError: string index out of range

     >>> s[-100:100, trimi]
     '0123456789'


     # Slice smaller than s.

     >>> s[3:7]
     '3456'

     >>> s[3:7, reversei]
     '6543'



     # From left With negative step.

     >>> s[::-1]
     '9876543210'

     >>> s[::-1, reversei]
     '0123456789'

     >>> s[:100:-1, trimi]      # j past right side
     '9876543210'

     >>> s[-100::-1, trimi]     # i before left side
     '9876543210'

     >>> s[-100:100:-1, trimi, reversei]    # slice is bigger
     '0123456789'



     # Null results

     >>> s[7:3:1, trimi]
     ''

     >>> s[7:3:-1, trimi]
     ''


     # Check None values.

     >>> s[:]
     '0123456789'

     >>> s[None:None]
     '0123456789'

     >>> s[None:None:None]
     '0123456789'

     >>> s[:: 1]
     '0123456789'

     >>> s[::-1]
     '9876543210'

     >>> s[None:None:1]
     '0123456789'

     >>> s[None:None:-1]
     '9876543210'


     # Check error messages.

     >>> s[0:0:0:0]
     Traceback (most recent call last):
     SyntaxError: invalid syntax

     >>> s[5:5:0]
     Traceback (most recent call last):
     ValueError: slice step cannot be zero



     # And various other combinations.

     >>> s = Str('123456789')

     >>> s[3, onei]
     '3'

     >>> s[4:8, onei]
     '4567'

     >>> s[4:8, onei, openi]
     '567'

     >>> s[4:8, onei, closedi]
     '45678'


     """


def _test():
     import doctest
     print(doctest.testmod(verbose=False))


if __name__=="__main__":
     _test()




More information about the Python-ideas mailing list