Skip to content

Commit

Permalink
2021-01-01 23:44:20
Browse files Browse the repository at this point in the history
  • Loading branch information
wizardforcel committed Jan 1, 2021
1 parent dbe6f6e commit 8d1058a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 2 deletions.
37 changes: 37 additions & 0 deletions docs/3.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# 第 3 章 Python 的数据结构、函数和文件


本章讨论 Python 的内置功能,这些功能本书会用到很多。虽然扩展库,比如 pandas 和 Numpy,使处理大数据集很方便,但它们是和 Python 的内置数据处理工具一同使用的。

我们会从 Python 最基础的数据结构开始:元组、列表、字典和集合。然后会讨论创建你自己的、可重复使用的 Python 函数。最后,会学习 Python 的文件对象,以及如何与本地硬盘交互。

# 3.1 数据结构和序列

Python 的数据结构简单而强大。通晓它们才能成为熟练的 Python 程序员。

## 元组

元组是一个固定长度,不可改变的 Python 序列对象。创建元组的最简单方式,是用逗号分隔一列值:

```python
Expand Down Expand Up @@ -84,6 +87,7 @@ Out[14]: ('foo', 'bar', 'foo', 'bar', 'foo', 'bar', 'foo', 'bar')
对象本身并没有被复制,只是引用了它。

## 拆分元组

如果你想将元组赋值给类似元组的变量,Python 会试图拆分等号右边的值:

```python
Expand Down Expand Up @@ -169,6 +173,7 @@ In [33]: a, b, *_ = values
```

## `tuple`方法

因为元组的大小和内容不能修改,它的实例方法都很轻量。其中一个很有用的就是``count``(也适用于列表),它可以统计某个值得出现频率:

```python
Expand All @@ -179,6 +184,7 @@ Out[35]: 4
```

## 列表

与元组对比,列表的长度可变、内容可以被修改。你可以用方括号定义,或用``list``函数:

```python
Expand Down Expand Up @@ -212,6 +218,7 @@ Out[44]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

## 添加和删除元素

可以用``append``在列表末尾添加元素:

```python
Expand Down Expand Up @@ -277,6 +284,7 @@ Out[56]: False
在列表中检查是否存在某个值远比字典和集合速度慢,因为 Python 是线性搜索列表中的值,但在字典和集合中,在同样的时间内还可以检查其它项(基于哈希表)。

## 串联和组合列表

与元组类似,可以用加号将两个列表串联起来:

```python
Expand Down Expand Up @@ -312,6 +320,7 @@ for chunk in list_of_lists:
```

## 排序

你可以用``sort``函数将一个列表原地排序(不创建新的对象):

```python
Expand All @@ -337,6 +346,7 @@ Out[66]: ['He', 'saw', 'six', 'small', 'foxes']
稍后,我们会学习``sorted``函数,它可以产生一个排好序的序列副本。

## 二分搜索和维护已排序的列表

``bisect``模块支持二分查找,和向已排序的列表插入值。``bisect.bisect``可以找到插入值后仍保证排序的位置,``bisect.insort``是向这个位置插入值:

```python
Expand All @@ -359,6 +369,7 @@ Out[72]: [1, 2, 2, 2, 3, 4, 6, 7]
> 注意:``bisect``模块不会检查列表是否已排好序,进行检查的话会耗费大量计算。因此,对未排序的列表使用``bisect``不会产生错误,但结果不一定正确。
## 切片

用切边可以选取大多数序列类型的一部分,切片的基本形式是在方括号中使用``start:stop``

```python
Expand Down Expand Up @@ -418,9 +429,11 @@ Out[82]: [1, 0, 6, 5, 3, 6, 3, 2, 7]
```

## 序列函数

Python 有一些有用的序列函数。

## `enumerate`函数

迭代一个序列时,你可能想跟踪当前项的序号。手动的方法可能是下面这样:

```python
Expand Down Expand Up @@ -452,6 +465,7 @@ Out[86]: {'bar': 1, 'baz': 2, 'foo': 0}
```

## `sorted`函数

``sorted``函数可以从任意序列的元素返回一个新的排好序的列表:

```python
Expand All @@ -465,6 +479,7 @@ Out[88]: [' ', 'a', 'c', 'e', 'e', 'h', 'o', 'r', 'r', 's']
``sorted``函数可以接受和``sort``相同的参数。

## `zip`函数

``zip``可以将多个列表、元组或其它序列成对组合成一个元组列表:

```python
Expand Down Expand Up @@ -514,6 +529,7 @@ Out[99]: ('Ryan', 'Clemens', 'Curt')
```

## `reversed`函数

``reversed``可以从后向前迭代一个序列:

```python
Expand All @@ -524,6 +540,7 @@ Out[100]: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
要记住``reversed``是一个生成器(后面详细介绍),只有实体化(即列表或`for`循环)之后才能创建翻转的序列。

## 字典

字典可能是 Python 最为重要的数据结构。它更为常见的名字是哈希映射或关联数组。它是键值对的大小可变集合,键和值都是 Python 对象。创建字典的方法之一是使用尖括号,用冒号分隔键和值:

```python
Expand Down Expand Up @@ -616,6 +633,7 @@ Out[120]: {'a': 'some value', 'b': 'foo', 7: 'an integer', 'c': 12}
``update``方法是原地改变字典,因此任何传递给``update``的键的旧的值都会被舍弃。

## 用序列创建字典

常常,你可能想将两个序列配对组合成字典。下面是一种写法:

```python
Expand All @@ -636,6 +654,7 @@ Out[122]: {0: 4, 1: 3, 2: 2, 3: 1, 4: 0}
后面会谈到``dict comprehensions``,另一种构建字典的优雅方式。

## 默认值

下面的逻辑很常见:

```python
Expand Down Expand Up @@ -688,6 +707,7 @@ for word in words:
```

## 有效的键类型

字典的值可以是任意 Python 对象,而键通常是不可变的标量类型(整数、浮点型、字符串)或元组(元组中的对象必须是不可变的)。这被称为“可哈希性”。可以用``hash``函数检测一个对象是否是可哈希的(可被用作字典的键):

```python
Expand Down Expand Up @@ -717,6 +737,7 @@ Out[132]: {(1, 2, 3): 5}
```

## 集合

集合是无序的不可重复的元素的集合。你可以把它当做字典,但是只有键没有值。可以用两种方式创建集合:通过`set`函数或使用尖括号`set`语句:

```python
Expand Down Expand Up @@ -808,6 +829,7 @@ Out[153]: True
```

## 列表、集合和字典推导式

列表推导式是 Python 最受喜爱的特性之一。它允许用户方便的从一个集合过滤元素,形成列表,在传递参数的过程中还可以修改元素。形式如下:

```python
Expand Down Expand Up @@ -870,6 +892,7 @@ Out[160]: {'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
```

## 嵌套列表推导式

假设我们有一个包含列表的列表,包含了一些英文名和西班牙名:

```python
Expand Down Expand Up @@ -927,6 +950,7 @@ Out[167]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
这段代码产生了一个列表的列表,而不是扁平化的只包含元素的列表。

# 3.2 函数

函数是 Python 中最主要也是最重要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。

函数使用``def``关键字声明,用``return``关键字返回值:
Expand Down Expand Up @@ -961,6 +985,7 @@ my_function(10, 20)
> 这种写法可以提高可读性。
## 命名空间、作用域,和局部函数
函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。Python 有一种更科学的用于描述变量作用域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,具体请参见后面介绍闭包的那一节)。看看下面这个函数:
```python
Expand Down Expand Up @@ -997,6 +1022,7 @@ In [170]: print(a)
> 注意:我常常建议人们不要频繁使用`global`关键字。因为全局变量一般是用于存放系统的某些状态的。如果你发现自己用了很多,那可能就说明得要来点儿面向对象编程了(即使用类)。
## 返回多个值

在我第一次用 Python 编程时(之前已经习惯了 Java 和 C++),最喜欢的一个功能是:函数可以返回多个值。下面是一个简单的例子:

```python
Expand Down Expand Up @@ -1028,6 +1054,7 @@ def f():
取决于工作内容,第二种方法可能很有用。

## 函数也是对象

由于 Python 函数都是对象,因此,在其他语言中较难表达的一些设计思想在 Python 中就要简单很多了。假设我们有下面这样一个字符串数组,希望对其进行一些数据清理工作并执行一堆转换:

```python
Expand Down Expand Up @@ -1112,6 +1139,7 @@ West virginia
```

## 匿名(lambda)函数

Python 支持一种被称为匿名的、或 lambda 函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过`lambda`关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。

```python
Expand Down Expand Up @@ -1151,6 +1179,7 @@ Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card']
> 笔记:lambda 函数之所以会被称为匿名函数,与`def`声明的函数不同,原因之一就是这种函数对象本身是没有提供名称`__name__`属性。
## 柯里化:部分参数应用

柯里化(currying)是一个有趣的计算机科学术语,它指的是通过“部分参数应用”(partial argument application)从现有函数派生出新函数的技术。例如,假设我们有一个执行两数相加的简单函数:

```python
Expand All @@ -1172,6 +1201,7 @@ add_five = partial(add_numbers, 5)
```

## 生成器

能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是 Python 的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键:

```python
Expand Down Expand Up @@ -1228,6 +1258,7 @@ Generating squares from 1 to 100
```

## 生成器表达式

另一种更简洁的构造生成器的方法是使用生成器表达式(generator expression)。这是一种类似于列表、字典、集合推导式的生成器。其创建方式为,把列表推导式两端的方括号改成圆括号:

```python
Expand Down Expand Up @@ -1257,6 +1288,7 @@ Out[192]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
```

## `itertools`模块

标准库`itertools`模块中有一组用于许多常见数据算法的生成器。例如,`groupby`可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。下面是一个例子:

```python
Expand All @@ -1279,6 +1311,7 @@ S ['Steven']
![](img/7178691-111823d8767a104d.png)

## 错误和异常处理

优雅地处理 Python 的错误和异常是构建健壮程序的重要部分。在数据分析中,许多函数函数只用于部分输入。例如,Python 的`float`函数可以将字符串转换成浮点数,但输入有误时,有``ValueError``错误:

```python
Expand Down Expand Up @@ -1388,6 +1421,7 @@ finally:
```

## IPython 的异常

如果是在`%run`一个脚本或一条语句时抛出异常,IPython 默认会打印完整的调用栈(traceback),在栈的每个点都会有几行上下文:

```python
Expand Down Expand Up @@ -1419,6 +1453,7 @@ AssertionError:
自身就带有文本是相对于 Python 标准解释器的极大优点。你可以用魔术命令``%xmode``,从 Plain(与 Python 标准解释器相同)到 Verbose(带有函数的参数值)控制文本显示的数量。后面可以看到,发生错误之后,(用`%debug``%pdb magics`)可以进入栈进行事后调试。

# 3.3 文件和操作系统

本书的代码示例大多使用诸如`pandas.read_csv`之类的高级工具将磁盘上的数据文件读入 Python 数据结构。但我们还是需要了解一些有关 Python 文件处理方面的基础知识。好在它本来就很简单,这也是 Python 在文本和文件处理方面的如此流行的原因之一。

为了打开一个文件以便读写,可以使用内置的`open`函数以及一个相对或绝对的文件路径:
Expand Down Expand Up @@ -1557,6 +1592,7 @@ Out[227]:
![表 3-4 Python 重要的文件方法或属性](img/7178691-d25bd6e730afeb39.png)

## 文件的字节和 Unicode

Python 文件的默认操作是“文本模式”,也就是说,你需要处理 Python 的字符串(即 Unicode)。它与“二进制模式”相对,文件模式加一个`b`。我们来看上一节的文件(UTF-8 编码、包含非 ASCII 字符):

```python
Expand Down Expand Up @@ -1638,4 +1674,5 @@ In [244]: f.close()
如果你经常要对非 ASCII 字符文本进行数据分析,通晓 Python 的 Unicode 功能是非常重要的。更多内容,参阅 Python 官方文档。

# 3.4 结论

我们已经学过了 Python 的基础、环境和语法,接下来学习 NumPy 和 Python 的面向数组计算。
Loading

0 comments on commit 8d1058a

Please sign in to comment.