# 第四章 操作列表

## 切片也是对象，中括号则是列表的特殊方法

```
# 列表切片
>>> a = [1, 2, 3, 4, 5]
>>> a[1:-1]
[2, 3, 4]
>>> a[::-1]
[5, 4, 3, 2, 1]
```

切片是 python 的一种高级特性，通过它能够方便的对列表实现复杂的子列选取。虽然切片看上去是一种特别语法，但其实，切片本身也是一个对象操作。python 中有一个内置函数叫做 slice，当你使用如上的切片语法时，其实等同于你使用了一个 slice 函数生成一个切片对象。什么意思呢？

```
# 内置切片函数接受三个变量，第一个是 start，第二个是 end，第三个是步长。就如同切片一样。
# 通过它生成切片对象，一样能够操作列表
>>> slice(1, -1, 1)
slice(1, -1, 1)
>>> slice_obj = slice(1, -1, 1)
>>> type(slice_obj)
<class 'slice'>

>>> a[slice_obj]  # 用切片对象而不是带冒号的语法来切片
[2, 3, 4]
>>> a[1:-1:1]  # 效果等同的切片语法
[2, 3, 4]

# 更加本质的切片操作：列表的内置方法 __getitem__() 
# 接受一个切片对象作为参数，从而对列表进行切片操作
# __getitem__() 等同于中括号！
>>> a.__getitem__(slice(1, -1, 1))  # 本质
[2, 3, 4]
>>> a[1:-1:1]  # 实际写法
[2, 3, 4]
```

所以如同三大定理第一条，**python 中的一切皆是对象**。列表是对象，切片也是对象。列表的切片操作其实是在调用列表的内置特殊方法`__getitem__()` ，该方法接受一个切片对象作为参数，以此选择并返回列表中的对应元素。当然，实际情况中，你依然应该用切片语法`list[start:end:step]`来进行切片操作，但通过上述解释，我们可以明白 **python 中的很多特殊语法，其实只是基于 python 对象操作的简写**！

万变不离其宗。

## 迭代

python中，许多类型都可以直接通过 for 循环进行迭代，比如列表和字符串。如果 python 是你学习的第一门编程语言，你可能不会觉得这有什么特殊的，但这其实是一个很酷的特性。如果你简单了解一下 [c 语言中类似的循环](https://stackoverflow.com/questions/1597830/iterate-through-a-c-array)，你就会发现，为什么我们说 python 是一门简洁易读，新手友好的语言。

```
>>> a = [1, 2, 3]
>>> for i in a:
...     print(i)
...
1
2
3

>>> a = '123'
>>> for i in a:
...     print(i)
...
1
2
3
```

但同时，不是所有的类型都可以通过 for 语句循环遍历的，比如说 int 就不行

```
>>> a = 123
>>> for i in a:
...     # 这里会打印出 1，2，3 呢？还是会打印出 1 到 123 呢？
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
# 答案是出错了！int 是不可迭代的。
```

如何解释 int 为什么不可以迭代呢？是因为会产生歧义吗？比如到底是迭代从 1-123 的所有数字，还是迭代 int 中出现的每一个字符（就和迭代 str 一样）呢？

其实，要理解深层次的原因，我们又要再次回到 python 中的对象及其方法这一基本定理来。上一节我们提到 **python 中的很多特殊语法，其实只是基于 python 对象操作的简写。**&#x6BD4;如包在切片语法外围的中括号，其实就是调用 python 列表对象的`__getitem__()` 方法，而 for 循环也是这样！当你通过 for 循环语句去试图迭代一个对象时，其实 python 在内部调用了该对象的`__iter__()` 方法，iter 就是 iteration 的简写，只有当一个类型有`__iter__()`方法时，它才可能被 for 循环遍历，而当它没有`__iter__()`方法时，自然也就会产生不可迭代的错误了。

通过hasattr() 函数，我们可以证明这一点。这个内置函数能够让我们通过名称确认某个类型是否具有同名方法：

```
# 通过 hasattr() 查看某个类型是否具有名为 __iter__ 的方法
>>> hasattr(list, '__iter__')
True
>>> hasattr(str, '__iter__')
True

# int 没有 __iter__ 方法，所以 for 循环 int 就出错了
>>> hasattr(int, '__iter__')
False
```

{% hint style="info" %}
&#x20;小问题：既然 int 不可循环，那我们怎么实现用 for 循环 1 - 123 的每一个整数呢？
{% endhint %}

## 列表拆分

考虑下面这样一个需求：

```
# 如下所示，把列表中 5 个元素分别分给 3 个变量
>>> a = [1, 2, 3, 4, 5]

# 目标
x1 = 1
x2 = [2, 3, 4]
x3 = 5

# 你会怎么做？
```

### 小学生写法

```
# 把列表中三个元素分别分给三个变量
>>> a = [1, 2, 3, 4, 5]
>>> x1 = a[0]
>>> x2 = a[1:4]
# 或者
>>> x2 = a[1:-1]
>>> x3 = a[2]
```

### 大学生写法

```
# 把列表中三个元素分别分给三个变量
>>> a = [1, 2, 3, 4, 5]
>>> x1, *x2, x3 = a
```

这里，我们就用到了列表拆分（unpack）。当一个表达式左边有多个变量，而右边是一个可迭代（根据上面的解释，也就是有`__iter__()` 方法）的对象时，python 就会自动拆分这个可迭代对象。但因为左边对象与右边元素数量不相等，我们就需要通过星号表达式（starred expression）来匹配中间的对象。上面这句语句的意思是，第一个和第三个变量只接受列表的第一个和最后一个元素，而中间带星号的变量接受所有其他元素（0 到任意数量个，并且中间变量始终为列表）

{% hint style="info" %}
星号表达式很重要，你知道这个名词之后，可以通过 google 学到更多细节。或者，就留个印象也是很好的，因为在函数章节我们还要提到它。
{% endhint %}

## 介绍一下列表推导式和 `zip()`

考虑下面这样一个需求：

```
# 将三个等长列表中的每一个数相加，得到一个新的列表
>>> a = [1, 2, 3]
>>> b = [3, 4, 5]

# 目标
>>> c = [4, 6, 8]
# 你会怎么做？
```

### 小学生

```
>>> a = [1, 2, 3]
>>> b = [3, 4, 5]
>>>
>>> c = []
>>>
>>> for pos in range(0, 3):
...     i, j = a[pos], b[pos]
...     # i 和 j 之所以能同时获得，原理也和上面的 list unpack 一样！
...     cur_sum = i + j
...     c.append(cur_sum)
>>> c
[4, 6, 8]
```

### 中学生

```
>>> a = [1, 2, 3]
>>> b = [3, 4, 5]
>>> c = []
>>>
>>> for i, j in zip(a, b):
...     # i 和 j 是一对来自 a 和 b 中相同位置的值，如（1, 3）(2, 4) (3, 5)
...     cur_sum = i + j
...     c.append(cur_sum)
>>> c
[4, 6, 8]
```

zip 就是拉链，顾名思义，这个函数能够像拉链一样把两个列表一一对应起来，我们就不需要专门生成一个位置变量，而可以直接迭代他们的值了

### 大学生

```
>>> a = [1, 2, 3]
>>> b = [3, 4, 5]
>>> c = [i + j for i, j in zip(a, b)]
>>> c
[4, 6, 8]
# 😎
```

上面给 c 赋值的这个语法叫做列表推导式（list comprehension），它和第二种语法做的事情一模一样，只不过把生成列表与循环向列表赋值放在了一行里面完成！我们将在下一章结合 if 详细讨论列表推导式。但是这里，你可以看到，同一件事情，其实有很多不同的方式去完成。一般来说，我们都喜欢最简洁和优美的写法，也就是第三种！

## 学习建议：正确的名词 + Google 是最好的学习方式

很多时候，只要知道一个名词，剩下的事情，请教 google 就可以了！这里限于篇幅（懒），我没有详细展开上面提到的诸多名词，但既然你知道他们大概是什么了，你可以轻而易举的从 google 中获取更多他们的说明，以及精心设计的案例!&#x20;

* 列表拆分（unpack）&#x20;
* 星号表达式（starred expression）&#x20;
* 对象的内置方法是许多语法形式的本质（如`__iter__()`），内置方法也叫魔法方法（magic methods， python 的魔力来源！） ，这是个很大的话题，有一本叫做 fluent python 的进阶书籍对我帮助很大！但这本书不太适合刚刚接触 python 的新手。
* 列表推导式（list comprehension）

这些内容不仅仅是看上去简洁优美的奇技淫巧而已，在深入学习他们的过程中，我们也更能了解 python 的许多深刻特性（三大定理）。**这对于理解后面的很多内容都是大有裨益的**。

{% hint style="info" %}
比如，星号表达式就会在函数参数传递中再次讲到，可迭代对象也不仅仅有列表和字符串，你在后面遇到的绝大多数数据结构，都是某种程度可迭代的，他们在被迭代时行为是通过各自独特的`__iter__()`方法来实现的，所以各不相同！
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://hq-1.gitbook.io/python/di-si-zhang-cao-zuo-lie-biao-0511.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
