You create a slice by calling slice with the same fields you would use if doing [start:end:step] notation:
sl = slice(0,4)
To use the slice, just pass it as if it were the index into a list or string:
>>> s = "ABCDEFGHIJKL"
>>> sl = slice(0,4)
>>> print(s[sl])
'ABCD'
Let's say you have a file of fixed-length text fields. You could define a list of slices to easily extract the values from each "record" in this file.
data = """\
0010GEORGE JETSON 12345 SPACESHIP ST HOUSTON TX
0020WILE E COYOTE 312 ACME BLVD TUCSON AZ
0030FRED FLINTSTONE 246 GRANITE LANE BEDROCK CA
0040JONNY QUEST 31416 SCIENCE AVE PALO ALTO CA""".splitlines()
fieldslices = [slice(*fielddef) for fielddef in [
(0,4), (4, 21), (21,42), (42,56), (56,58),
]]
fields = "id name address city state".split()
for rec in data:
for field,sl in zip(fields, fieldslices):
print("{} : {}".format(field, rec[sl]))
print('')
# or this same code using itemgetter, to make a function that
# extracts all slices from a string into a tuple of values
import operator
rec_reader = operator.itemgetter(*fieldslices)
for rec in data:
for field, field_value in zip(fields, rec_reader(rec)):
print("{} : {}".format(field, field_value))
print('')
Prints:
id : 0010
name : GEORGE JETSON
address : 12345 SPACESHIP ST
city : HOUSTON
state : TX
id : 0020
name : WILE E COYOTE
address : 312 ACME BLVD
city : TUCSON
state : AZ
id : 0030
name : FRED FLINTSTONE
address : 246 GRANITE LANE
city : BEDROCK
state : CA
id : 0040
name : JONNY QUEST
address : 31416 SCIENCE AVE
city : PALO ALTO
state : CA
Answer from PaulMcG on Stack OverflowYou create a slice by calling slice with the same fields you would use if doing [start:end:step] notation:
sl = slice(0,4)
To use the slice, just pass it as if it were the index into a list or string:
>>> s = "ABCDEFGHIJKL"
>>> sl = slice(0,4)
>>> print(s[sl])
'ABCD'
Let's say you have a file of fixed-length text fields. You could define a list of slices to easily extract the values from each "record" in this file.
data = """\
0010GEORGE JETSON 12345 SPACESHIP ST HOUSTON TX
0020WILE E COYOTE 312 ACME BLVD TUCSON AZ
0030FRED FLINTSTONE 246 GRANITE LANE BEDROCK CA
0040JONNY QUEST 31416 SCIENCE AVE PALO ALTO CA""".splitlines()
fieldslices = [slice(*fielddef) for fielddef in [
(0,4), (4, 21), (21,42), (42,56), (56,58),
]]
fields = "id name address city state".split()
for rec in data:
for field,sl in zip(fields, fieldslices):
print("{} : {}".format(field, rec[sl]))
print('')
# or this same code using itemgetter, to make a function that
# extracts all slices from a string into a tuple of values
import operator
rec_reader = operator.itemgetter(*fieldslices)
for rec in data:
for field, field_value in zip(fields, rec_reader(rec)):
print("{} : {}".format(field, field_value))
print('')
Prints:
id : 0010
name : GEORGE JETSON
address : 12345 SPACESHIP ST
city : HOUSTON
state : TX
id : 0020
name : WILE E COYOTE
address : 312 ACME BLVD
city : TUCSON
state : AZ
id : 0030
name : FRED FLINTSTONE
address : 246 GRANITE LANE
city : BEDROCK
state : CA
id : 0040
name : JONNY QUEST
address : 31416 SCIENCE AVE
city : PALO ALTO
state : CA
Square brackets following a sequence denote either indexing or slicing depending on what's inside the brackets:
>>> "Python rocks"[1] # index
'y'
>>> "Python rocks"[1:10:2] # slice
'yhnrc'
Both of these cases are handled by the __getitem__() method of the sequence (or __setitem__() if on the left of an equals sign.) The index or slice is passed to the methods as a single argument, and the way Python does this is by converting the slice notation, (1:10:2, in this case) to a slice object: slice(1,10,2).
So if you are defining your own sequence-like class or overriding the __getitem__ or __setitem__ or __delitem__ methods of another class, you need to test the index argument to determine if it is an int or a slice, and process accordingly:
def __getitem__(self, index):
if isinstance(index, int):
... # process index as an integer
elif isinstance(index, slice):
start, stop, step = index.indices(len(self)) # index is a slice
... # process slice
else:
raise TypeError("index must be int or slice")
A slice object has three attributes: start, stop and step, and one method: indices, which takes a single argument, the length of the object, and returns a 3-tuple: (start, stop, step).
I’ve recently been working on a custom mutable sequence type as part of a personal project, and trying to write a __setitem__ implementation for it that handles slices the same way that the builtin list type does has been far more complicated than I realized, and left me scratching my head in confusion in a couple of cases.
Some parts of slice assignment are obvious or simple. For example, pretty much everyone knows about these cases:
>>> l = [1, 2, 3, 4, 5] >>> l[0:3] = [3, 2, 1] >>> l [3, 2, 1, 4, 5] >>> l[3:0:-1] = [3, 2, 1] >>> l [1, 2, 3, 4, 5]
That’s easy to implement, even if it’s just iterative assignment calls pointing at the right indices. And the same of course works with negative indices too. But then you get stuff like this:
>>> l = [1, 2, 3, 4, 5] >>> l[3:6] = [3, 2, 1] >>> l [1, 2, 3, 3, 2, 1] >>> l = [1, 2, 3, 4, 5] >>> l[-7:-4] = [3, 2, 1] >>> l [3, 2, 1, 2, 3, 4, 5] >>> l = [1, 2, 3, 4, 5] >>> l[12:16] = [3, 2, 1] >>> l [1, 2, 3, 4, 5, 3, 2, 1]
Overrunning the list indices extends the list in the appropriate direction. OK, that kind of makes sense, though that last case had me a bit confused until I realized that it was likely implemented originally as a safety net. And all of this is still not too hard to implement, you just do the in-place assignments, then use append() for anything past the end of the list and insert(0) for anything at the beginning, you just need to make sure you get the ordering right.
But then there’s this:
>>> l = [1, 2, 3, 4, 5] >>> l[6:3:-1] = [3, 2, 1] Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: attempt to assign sequence of size 3 to extended slice of size 1
What? Shouldn’t that just produce [1, 2, 3, 4, 1, 2, 3]? Somehow the moment there’s a non-default step involved, we have to care about list boundaries? This kind of makes sense from a consistency perspective because using a step size other than 1 or -1 could end up with an undefined state for the list, but it was still surprising the first time I ran into it given that the default step size makes these kind of assignments work.
Oh, and you also get interesting behavior if the length of the slice and the length of the iterable being assigned don’t match:
>>> l = [1, 2, 3, 4, 5] >>> l[0:2] = [3, 2, 1] >>> l [3, 2, 1, 3, 4, 5] >>> l = [1, 2, 3, 4, 5] >>> l[0:4] = [3, 2, 1] >>> l [3, 2, 1, 5]
If the iterable is longer, the extra values get inserted after last index in the slice. If the slice is longer, the extra indices within the list that are covered by the slice but not the iterable get deleted. I can kind of understand this logic to some extent, though I have to wonder how many bugs there are out in the wild because of people not knowing about this behavior (and, for that matter, how much code is actually intentionally using this, I can think of a few cases where it’s useful, but for all of them I would preferentially be using a generator or filtering the list instead of mutating it in-place with a slice assignment)
Oh, but those cases also throw value errors if a step value other than 1 is involved...
>>> l = [1, 2, 3, 4, 5] >>> l[0:4:2] = [3, 2, 1] Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: attempt to assign sequence of size 3 to extended slice of size 2
TLDR for anybody who ended up here because they need to implement this craziness for their own mutable sequence type:
-
Indices covered by a slice that are inside the sequence get updated in place.
-
Indices beyond the ends of the list result in the list being extended in those directions. This applies even if all indices are beyond the ends of the list, or if negative indices are involved that evaluate to indices before the start of the list.
-
If the slice is longer than the iterable being assigned, any extra indices covered by the slice are deleted (equivalent to
del l[i]). -
If the iterable being assigned is longer than the slice, any extra items get inserted into the list after the end of the slice.
-
If the step value is anything other than
1, cases 2, 3, and 4 instead raise aValueErrorcomplaining about the size mismatch.
slice(*map(lambda x: int(x.strip()) if x.strip() else None, mystring.split(':')))
for single arg slices '-1' or '1' so when mystring.split(':')==1 you just call int(x)
On request, took it out of comment section.
slice(*{True: lambda n: None, False: int}[x == '' for x in (mystring.split(':') + ['', '', ''])[:3]])