>>> from env_helper import info; info()
页面更新时间: 2024-04-04 19:41:23
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-18-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

2.2. Avoid Striding and Slicing in a Single Expression

In addition to basic slicing (see Item 11: “Know How to Slice Sequences”), Python has special syntax for the stride of a slice in the form somelist[start:end:stride]. This lets you take every nth item when slicing a sequence. For example, the stride makes it easy to group by even and odd indexes in a list:

>>> x = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']
>>> odds = x[::2]
>>> evens = x[1::2]
>>> print(odds)
>>> print(evens)
['red', 'yellow', 'blue']
['orange', 'green', 'purple']

The problem is that the stride syntax often causes unexpected behavior that can introduce bugs. For example, a common Python trick for reversing a byte string is to slice the string with a stride of -1:

>>> x = b'mongoose'
>>> y = x[::-1]
>>> print(y)
b'esoognom'

This also works correctly for Unicode strings (see Item 3: “Know the Differences Between bytes and str”):

>>> x = '寿司'
>>> y = x[::-1]
>>> print(y)
司寿

But it will break when Unicode data is encoded as a UTF-8 byte string:

w = '寿司' x = w.encode('utf-8') y = x[::-1] z = y.decode('utf-8')

>>>
Traceback ...

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb8 in position 0: invalid start byte

Are negative strides besides -1 useful? Consider the following examples:

>>> x = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> x[::2]   # ['a', 'c', 'e', 'g']
>>> x[::-2]  # ['h', 'f', 'd', 'b']
['h', 'f', 'd', 'b']

Here, ::2 means “Select every second item starting at the beginning.” Trickier, ::-2 means “Select every second item starting at the end and moving backward.”

What do you think 2::2 means? What about -2::-2 vs. -2:2:-2 vs. 2:2:-2?

>>> x[2::2]     # ['c', 'e', 'g']
['c', 'e', 'g']
>>> x[-2::-2]   # ['g', 'e', 'c', 'a']
['g', 'e', 'c', 'a']
>>> x[-2:2:-2]  # ['g', 'e']
['g', 'e']
>>> x[2:2:-2]   # []
[]

The point is that the stride part of the slicing syntax can be extremely confusing. Having three numbers within the brackets is hard enough to read because of its density. Then, it’s not obvious when the start and end indexes come into effect relative to the stride value, especially when the stride is negative.

To prevent problems, I suggest you avoid using a stride along with start and end indexes. If you must use a stride, prefer making it a positive value and omit start and end indexes. If you must use a stride with start or end indexes, consider using one assignment for striding and another for slicing:

>>> y = x[::2]   # ['a', 'c', 'e', 'g']
>>> y
['a', 'c', 'e', 'g']
>>> z = y[1:-1]  # ['c', 'e']
>>> z
['c', 'e']

Striding and then slicing creates an extra shallow copy of the data. The first operation should try to reduce the size of the resulting slice by as much as possible. If your program can’t afford the time or memory required for two steps, consider using the itertools built-in module’s islice method (see Item 36: “Consider itertools for Working with Iterators and Generators”), which is clearer to read and doesn’t permit negative values for start, end, or stride.

2.2.1. Things to Remember

✦ Specifying start, end, and stride in a slice can be extremely confusing.

✦ Prefer using positive stride values in slices without start or end indexes. Avoid negative stride values if possible.

✦ Avoid using start, end, and stride together in a single slice. If you need all three parameters, consider doing two assignments (one to stride and another to slice) or using islice from the itertools built-in module.