[Python 4] List and tuple slice slice iteration list generation generator generator iterator iterator object

L = []
n=1
while n < 100:
L.append(n)
n = n + 2

In Python, more code is not better, but less is better

Taking some elements of a list or tuple is a very common operation.

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
r = []
n=3
for i in range(n):
r.append(L[i])

r # ['Michael', 'Sarah', 'Tracy']

For this kind of operation that often takes a specified index range, it is very cumbersome to use a loop. Therefore, Python provides the slice operator, which can greatly simplify this operation.

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3]
# ['Michael', 'Sarah', 'Tracy']
L[:3]
# ['Michael', 'Sarah', 'Tracy']
L[1:3]
# ['Sarah', 'Tracy']

L[0:3] means, starting from index 0, until index 3, but not including index 3
If the first index is 0, it can also be omitted
You can also start from index 1 and take out 2 elements.

Similarly, since Python supports L[-1] to take the first element from the reciprocal, it also supports reciprocal slicing.

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

Remember that the index of the last element is -1

Slicing operations are very useful
You can easily remove a certain sequence of numbers by slicing
For example, the first 10 numbers
last 10 numbers
The first 11-20 numbers
The first 10 numbers, take one from every two
All numbers, take one out of every 5
Even if you don’t write anything, just write [:] to copy a list as it is.

L = list(range(100))
L # [0, 1, 2, 3, ..., 99]

L[:10]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

L[-10:]
# [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

L[10:20]
# [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

L[:10:2]
# [0, 2, 4, 6, 8]

L[::5]
# [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

L[:]
# [0, 1, 2, 3, ..., 99]

Tuple is also a kind of list, the only difference is that tuple is immutable
Therefore, tuples can also be sliced, but the result of the operation is still a tuple.

(0, 1, 2, 3, 4, 5)[:3]
# (0, 1, 2)

The string ‘xxx’ can also be regarded as a list, each element is a character
Therefore, strings can also be sliced, but the result is still a string:

>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[::2]
'ACEG'

In many programming languages, various interception functions (for example, substring) are provided for strings. The actual purpose is to slice strings.
Python does not have an interception function for strings. It only requires one operation to slice, which is very simple.

If a list or tuple is given, we can traverse the list or tuple through a for loop. This traversal is called iteration.
In Python, iteration is done through for…in, while in many languages such as C language, iteration of lists is done through subscripts, such as C code

for(i = 0; i < length; i + + ){<!-- -->
printf('%d', list[i])

It can be seen that the level of abstraction of Python’s for loop is higher than that of C’s for loop, because Python’s for loop can not only be used on lists or tuples, but also on other iterable objects.

Although the data type list has subscripts, many other data types do not have subscripts. However, as long as it is an iterable object, it can be iterated regardless of whether it has a subscript. For example, dict can be iterated

>>> d = {<!-- -->'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
...print(key)
...
a
c
b

Because dict is not stored in a list order, the order of iterated results is likely to be different.

By default, dict iterates over key
If you want to iterate value, you can use for value in d.values(). If you want to iterate key and value at the same time, you can use for k, v in d.items()

Since strings are also iterable objects, they can also be used in for loops.

>>> for ch in 'ABC':
...print(ch)
...
A
B
C

Therefore, when we use a for loop, as long as it acts on an iterable object, the for loop can run normally, and we don’t care whether the object is a list or other data type.

So, how to determine whether an object is an iterable object? The method is to judge through the Iterable type of the collections.abc module

>>> from collections.abc import Iterable
>>> isinstance('abc', Iterable) # Whether str is iterable
True
>>> isinstance([1,2,3], Iterable) # Whether list is iterable
True
>>> isinstance(123, Iterable) # Whether the integer is iterable
False

What if you want to implement a subscript loop similar to Java’s on the list? Python’s built-in enumerate function can turn a list into an index-element pair, so that the index and the element itself can be iterated simultaneously in a for loop

for i, value in enumerate(['a', 'b', 'c']):
print(i, value)

0 A
1B
2C

List Comprehensions, a very simple but powerful built-in Python generation that can be used to create lists

list(range(1, 11))
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

But what if you want to generate [1×1, 2×2, 3×3, …, 10×10]? Method one is loop:

L = []
for x in range(1, 11):
L.append(x * x)

L # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# Loops are too cumbersome, but list generation can use one line of statements instead of loops to generate the above list
[x * x for x in range(1, 11)]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

When writing a list generation expression, put the element x * x to be generated in the front, followed by a for loop, and the list can be created, which is very useful.

You can also add an if judgment after the for loop, so that we can filter out only the squares of even numbers.

[x * x for x in range(1, 11) if x %2 == 0]
[4, 16, 36, 64, 100]

Through list generation, we can directly create a list
However, due to memory constraints, the list capacity is definitely limited.

So, if the elements of the list can be calculated according to a certain algorithm, can we continuously calculate the subsequent elements during the loop? This saves a lot of space by not having to create a complete list
In Python, this mechanism of looping and calculating at the same time is called generator: generator

To create a generator, there are many ways
The first method is very simple. Just change [] to () in a list generation expression to create a generator.

L = [x * x for x in range(10)]
L # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

g = (x * x for x in range(10)]
g # <generator object <genexpr> at 0x1022ef630>

The only difference between creating L and g is the outermost [] and (). L is a list and g is a generator.
The next return value of the generator can be obtained through the next() function
The generator saves the algorithm. Each time next(g) is called, the value of the next element of g is calculated until the last element is calculated. When there are no more elements, a StopIteration error is thrown.
A more correct way is to use a for loop, because generator is also an iterable object

Therefore, after we create a generator, we will basically never call next(), but iterate it through a for loop, and do not need to care about StopIteration errors.

Here is another way to define a generator. If a function definition contains the yield keyword, then the function is no longer an ordinary function, but a generator function. Calling a generator function will return a generator

def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

f = fib(6)
f # <generator object fib at 0x104feaaa0>

The correct way to write it is to create a generator object and then continuously call next() on this generator object.

We already know that the following data types can be directly used in for loops:
One type is collection data types, such as list, tuple, dict, set, str, etc.;
One type is generator, including generator and generator function with yield
These objects that can be directly used in for loops are collectively called iterable objects: Iterable

Python’s Iterator object represents a data flow. The Iterator object can be called by the next() function and continuously returns the next data until a StopIteration error is thrown when there is no data li