<jupyter_start><jupyter_text>1.Python介绍    Python是一门强大的解释型、面向对象的高级程序设计语言，它优雅、简单、可移植、易扩展，可用于桌面应用、系统编程、数据库编程、网络编程、Web开发、图像处理、人工智能、数学应用、文本处理等 1.1 Python的历史Python的重大事件如下：1989年，<NAME>开发了Python语言。1999年，第一个基于Python的Web框架（ZOPE）诞生。2000年，Python2.0版本发布，其中加入了内存回收机制，构成了现在Python语言框架的基础。2004年，Python2.4版本诞生，同时也诞生了Django这个Web框架。2008年，Python3.0版本诞生，其在2.X版本上实现了一次大的跳跃。2010年，诞生了目前应用最广泛的Python2.7版本，它是承接2.X和3.X特性的过渡版本。 1.2 Python可以做什么Python是门真正意义上的编程语言，除了可以用来编写脚本、数值计算外，还可以用于编写命令行程序、编写带用户界面的应用程序、编写网站、绘制图形等。使用python，很容易编写跨平台的应用程序。python脚本可以驱动MySQL、Sqlite、ArcGIS、Adobe Indesign等软件，也可以调用C、C++以及Fortan等语言的函数库。 1.3 Python在数据科学中的地位及前景Python已经成为人工智能领域的第一主流语言。现在各大主流人工智能框架+TensorFlow、CNTK、Caffe2等，都支持用户使用Python进行开发。  2. 配置机器及环境搭建 2.1 Python版本及开发环境介绍 2.1.1 Python的运行方式1. 命令行方式命令行方式是一种最基础的方式。直接在DOS窗口中输入具体的命令(`python py文件`)，以运行Python程序。由于Python是解释型语言，也可以在命令行中一句一句的输入命令。一边编程一边执行。2. 图形交互式图形交互式，在调试和编写代码时，用户体验更加友好。 2.1.2 常见的集成开发环境（IDE）关于Python的集成开发环境有很多，如Upterm、Ptpython、Sublime、PyCharm、Anaconda等 2.1.3 Anaconda下载及安装1. 下载Anaconda软件1）通过 https://www.anaconda.com 进入Anaconda的官网，按照下图进入下载页面![image.png](attachment:image.png)2）将网页拉到最下面，找到下图位置![image-2.png](attachment:image-2.png)3）选择`64-Bit Graphical Installer (466 MB)`下载2. 安装Anaconda软件在Windows下安装Anaconda的方法与一般的软件安装相似，右击安装包，选择安装路径，默认安装即可。  3. 语言规则 3.1 了解编程语言的分类1. 从运行角度的分类* 编译型* 解释型python属于解释型语言（1）解释型语言：代码可以直接运行。当然，这也是依赖于附加程序（解释器）来实现的。解释器会不断地循环一组动作：取一行源程序，将其转成二进制指令，然后执行。一直下去，直到全部的源程序读取完毕。类似这样的语言还有JavaScript、VBScripts、Perl等。（2）编译型语言：代码本身不能运行，需要一个附加程序（编译器）将其转换成由二进制代码组成的可执行文件，然后才可以运行。例如：C/C++、VB等。解释型语言和编译型语言最大的区别是：* 解释型语言的代码必须依赖于解释器才可以执行* 编译型语言的代码生成可执行程序后，可以独立执行。注意：有一附属的打包程序，可以将解释型语言的代码打包成为独立执行的程序。但是，使用打包程序的效率及性能会略慢于编译型语言生成的程序。2. 从形态角度的分类* 动态语言* 静态语言Python属于动态语言。（1）动态语言：是指程序运行时可以改变其结构，可以对变量或函数进行修改。因为程序中的代码是在运行时才开始检查数据类型的 ，所以没有运行的语句是被程序忽略的。即，定义变量时不需要指定数据类型，只有在第一次给变量赋值时，根据赋值的类型在内部指定该变量的类型。类似的语言Perl、Ruby等（2）静态语言：常用于编译型语言，在编译时检查数据的类型。即，在使用变量之前必须要定义号数据类型。如：C、C++、C、Java等3. 语义角度的编程语言分类* 强定义类型* 弱定义类型Python属于强定义类型（1）强定义类型语言：会严格区分内部的变量类型。一旦指定了变量的类型，就必须经过转换才能存取为其他类型。类似的语言有C、Java等。（2）弱定义类型语言：是指不严格区分内部的变量类型，一般是只要大小放得下即可转换。Tips:* Python是一种动态解释型的强定义类型语言* 优点：代码简洁、可读性强、开发效率比较高、可移植性好、可扩展性好、可嵌入性好* 缺点：运行时的性能相对较低、源代码不能加密。 3.2 基础规则 3.2.1 了解Python源代码相关的几个概念* 模块文件：是指封装好的代码文件，可作为独立的模块被其他程序引用。* 代码块：可以理解为一个容器，其中可放置一条一条语句。一个模块文件、一个函数体、一个类、交互式命令中的单行代码，都可以成为一个代码块。* 代码中的函数：在一个模块文件中，将若干条语句封装在一个代码块里以完成某个独立的功能，共其他程序使用。代码块是通过模块文件来承载的。* 代码中的类：以面向对象思想，将变量及函数封装成具有某个类别特性的结构体。它可以模块文件的形式单独提供，也可以与本地程序一起放在相同代码文件中。 3.2.2 语句的基本规则：变量、语句、代码块1. 变量的命名可以使用字母、数字和下划线的组合，首字母可以是字母或者下划线。2. 语句区分Python解释器中，源代码是一行一行被解释执行的，行与行之间通过换行符来区分。默认是一行一条语句，如果在一行中放油多条语句可以通过“;”来区分。例如：<jupyter_code>A = 6    # 一行就是一条语句
B = 8;cc = 10   # 这种数据两条语句，虽然在一行，但是使用分好分开了<jupyter_output><empty_output><jupyter_text>3. 代码块通过缩进来表述“代码块”，即同一个代码块中的语句具有相同的缩进格式。缩进可以是使用空格或是Tab键来实现。<jupyter_code>def fun():            # 定义一个函数名字叫做fun
    print("hello fun")   # 该语句被缩进，标识fun函数的函数体，只有执行函数fun时才被执行
print("hello world")     # 该语句没有被缩进
fun()<jupyter_output>hello world
hello fun<jupyter_text>上面的代码是一个函数的定义及调用 3.2.3 添加注释英文字符“”用来表示注释。它的生效范围是“行”，即在一行代码中 之后的内容将不被Python解释器处理注释一般用来说明代码的具体含义，以方便开发人员阅读。另外，也可以用来控制程序暂时不被执行某一部分代码块。另外，也可以使用三个单引号(''')或者三个双引号(""")将代码段变为字符串，程序同样不会执行。例如<jupyter_code>'''
def fun():
    print("hello fun")
pirnt("hello main")
func()
'''
print('hell china')<jupyter_output>hell china<jupyter_text>系统会将上述代码中由三个单引号(''')包含的部分当做一个字符串，不会将其当做语句来执行。 3.2.4 使用Python的“帮助”在Python中使用help命令来获取帮助信息。它可以查找关于Python的基础函数、类型、常用库等信息。例如> help(print)     显示print函数的帮助信息输出的信息中，描述了print函数的说明、定义、参数的意义。 3.3 代码文件的结构Python的文件结构可以分为代码、模块、包代码是组成程序的基本单位，是一条一条的具体语句。代码的载体是文件。Python中的代码文件是以`.py`结尾的 3.3.1 模块在实现一个复杂的程序时，需要编写的代码量往往非常大。将所有的代码都放在一个文件里，显然是不合适的。这个时候就需要将代码分割成多个文件，每个文件中放置功能相对独立的代码。在使用时，同时将多个文件中的代码导入到当前代码文件中，以实现完整的功能。这种被分后的仍具有独立功能的代码文件就是**模块(Module)**. 3.3.2 包当模块足够庞大时，维护起来不太方便。这就需要使用包（Package）。包可以把多个模块（.py文件）放在同一个文件夹中，以便归类与管理。概括来说，包就是放置模块的文件夹。例如，下面代码是一个名字为CycleGAN对抗神经网络的目录：```textCycleGAN/               顶级包    __init__.py    mian.py    sample/            样本出来子包        __init__.py        Download_data.py        build_data.py    model/             模型子包        __init__.py        discriminator.py        generator.py    work/              协调工作子包        __init__.py        train.py        export_graph.py        eval.py```在上面的目录结构中，顶级包CycleGAN下面分为样本处理子包、模型子包和协调工作子包。每个子包里放有功能相近的模块。比如，模型子包里面放的都是关于各种模型的代码。* discriminator.py：用于实现判别器的模型* generator.py：用于实现生成器的模型注意：在每个包文件夹里都必须包含一个、\_\_init\_\_.py文件。该文件的作用就是告诉Python环境该文件夹是一个包。\_\_init\_\_.py可以是一个空文件。 3.4 模块的详细介绍Python模块（Module）包含了Python对象定义和Python语句。在模块里可以定义函数、类和变量。模块也能包含可执行的代码。把相关的代码分配到一个模块里，可以使代码更好用、更易懂。养成使用模块的习惯，对于更有逻辑的组织代码段有很大的帮助。 3.4.1 模块的作用及分类模块的主要作用是，用来被导入到其他代码模块中。将其他模块导入(import)到当前模块，可以理解成是对当前模块的一种功能增强。不同模块中的函数名和变量名是可以相同的。模块的使用，避免了庞大代码量中函数名和变量名冲突的问题。另外，可以有效的提高代码的可维护性与重用性。python中的模块可以分为内置模块、自定义模块和第三方模块* 内置模块：Python中本来就有的模块* 自定义模块：自己开发的模块* 第三方模块：需要单独下载、安装并导入的模块。 3.4.2 模块的基本使用方法使用“import 模块名字”即可将模块导入例如：<jupyter_code>import time    # 引入time模块
print(time.time())    # 获取当前时间
print(time.localtime(time.time()).tm_year)   # 将当前时间格式化，输出年<jupyter_output>1600656455.0840569
2020<jupyter_text>3.4.3 模块的搜索路径自定义模块，还需要考虑模块所在的位置，要结合“包名”一起引入。具体语法是：> import 包名.模块名解释器会先尝试从内置模块匹配；如果没有找到哦啊，则将在`sys.path`记录的所有目录中搜索导入的模块名的文件。变量`sys.path`是一个字符串列表，它为解释器指定了模块的搜索路径。`sys.path`包括：* 当前程序所在目录* 标准库的安装目录* 操作系统环境变量PythonPATH所包含的目录。 3.4.4 模块的属性模块处理被引用以外，还会有自己的属性可供调用者查看。其属性大致有如下几种：* \_\_name\_\_：名字* \_\_doc\_\_：详细说明，介绍了该模块的使用方法* \_\_package\_\_：所在的包名* \_\_loader\_\_：加载的类名* \_\_spec\_\_：简介，介绍了该模块的名字、加载类名、来源类型等概要信息。要查看这些属性的内容，可以通过“导入的模块名+ . + 具体的属性变量”。<jupyter_code>import time
print(time.__name__)
print('*' * 10)
print(time.__doc__)
print('*' * 10)
print(time.__package__)
print('*' * 10)
print(time.__loader__)
print('*' * 10)
print(time.__spec__)<jupyter_output>time
**********
This module provides various functions to manipulate time values.

There are two standard representations of time.  One is the number
of seconds since the Epoch, in UTC (a.k.a. GMT).  It may be an integer
or a floating point number (to represent fractions of seconds).
The Epoch is system-defined; on Unix, it is generally January 1st, 1970.
The actual value can be retrieved by calling gmtime(0).

The other representation is a tuple of 9 integers giving local time.
The tuple items are:
  year (including century, e.g. 1998)
  month (1-12)
  day (1-31)
  hours (0-23)
  minutes (0-59)
  seconds (0-59)
  weekday (0-6, Monday is 0)
  Julian day (day in the year, 1-366)
  DST (Daylight Savings Time) flag (-1, 0 or 1)
If the DST flag is 0, the time is given in the regular time zone;
if it is 1, the time is given in the DST time zone;
if it is -1, mktime() should guess based on the date and time.

**********

**********
<class '_frozen_importlib.BuiltinImporter'>
**********
Modul[...]<jupyter_text>3.4.5 模块名字的可变性在Python中，模块的名字属性会根据不同的使用场景发生变化。当模块被导入到其他模块时，`__name__`的值为模块本身的名字；导入当模块自己独立运行时，`__name__`的值会变为`__main__`。例如 3.4.6 模块的常规写法Python程序中，每一个代码文件在可以独立运行的同时，也可以作为一个模块文件。编写模块文件的好习惯是，在编写该模块提供的函数或类的同时，也可以把自身当做独立运行的文件来为自身模块做单元测试。这需要借助模块名字的可变特性来实现。例如，可以在模块的最下面加入名字的判断，并执行单元测试代码：```pythonif __name__ == "__main__":    执行单元测试代码``` 3.5 模块的4种导入方式 3.5.1 import as 方式import as的方式其实是实现了两步操作：现将模块导入，再为模块重命名。其写法如下：> import a as b其中，a代表要引入的模块，b是将a重命名后的名称。 3.5.2 from import方式from import方式是直接把模块内的函数或变量的名称导入当前模块。其写法如下：> form a import func上面表示，将模块a中的func函数导入到当前模块。使用这种方式导入时，当前模块将只能使用a中的func函数，无法使用a中的其他函数 3.5.3 from import * 方式from import * 方式将模块中所有的名字（以下划线开头的名字除外）导入到当前模块符号表里。例如：```pythonfrom time import *    导入模块time中的所有名字， 除开以下划线开头的s = ctime()        直接调用函数print(s)          将时间输出```这种方式导入的好处是方便，弊端是容易产生名字冲突。 3.5.4 导入文件名中带空格的模块模块是一个代码文件，而代码文件的命名规范是符合操作系统的命名规范的。在操作系统中允许文件名中出现空格，但这个规则与Python语法发生冲突，因为Python中是以空格来隔离一行语句中的不同元素的。在这种情况下，导入带空格的模型会带来麻烦。解决办法是使用\_\_import\_\_方法。假设有个模块叫做“9-24 yuyinutils”，要在当前文件引入它时，可以写成如下：`yuyinutils = __import__("9-24 yuyinutils")`使用\_\_import\_\_方法，将模块名称用字符串的方式传入，就可以得到该模块的引用了。 3.6 实例：封装获取系统信息的模块，并将其导入<jupyter_code>import platform
import sys
import os

def showENV():    # 函数
    s = platform.platform()
    print("当前系统：", s)        # 获取系统信息
    p = sys.path
    print("当前安装路径：", p)    # 获取安装路径
    op = os.getcwd()
    print("当前代码路径：", op)   # 获取当前代码路径
    print("Python版本信息：", sys.version_info)

if __name__ == "__main__":
    showENV()<jupyter_output>当前系统： Windows-10-10.0.18362-SP0
当前安装路径： ['d:\\Git_projects\\studyspace\\Notes', 'E:\\Program Files\\Microsoft VS Code', 'c:\\Users\\admin\\.vscode\\extensions\\ms-python.python-2020.8.109390\\pythonFiles', 'c:\\Users\\admin\\.vscode\\extensions\\ms-python.python-2020.8.109390\\pythonFiles\\lib\\python', 'E:\\Program Files\\Python38\\python38.zip', 'E:\\Program Files\\Python38\\DLLs', 'E:\\Program Files\\Python38\\lib', 'E:\\Program Files\\Python38', '', 'E:\\Program Files\\Python38\\lib\\site-packages', 'E:\\Program Files\\Python38\\lib\\site-packages\\win32', 'E:\\Program Files\\Python38\\lib\\site-packages\\win32\\lib', 'E:\\Program Files\\Python38\\lib\\site-packages\\Pythonwin', 'E:\\Program Files\\Python38\\lib\\site-packages\\IPython\\extensions', 'C:\\Users\\admin\\.ipython']
当前代码路径： d:\Git_projects\studyspace\Notes
Python版本信息： sys.version_info(major=3, minor=8, micro=5, releaselevel='final', serial=0)<jupyter_text>4. 变量 4.1 什么是变量变量是编程语言中最基本的术语，用来代指就算计中存放的可变数据——如整数、小数、字符或被调用的一片内存空间。如：> Val = "hello"上述代码在执行时，系统中就会有一个变量叫Val，它指向内存中存放字符串“hello”所在的位置，定义了变量之后，可以通过编写具体的逻辑对变量进行操作计算，从而实现想要的功能。 4.2 变量的规则变量是要分为各种类型的。比如，整型变量只可以代表整数，浮点型只可以代表小数点的数字，字符型变量只可以代表字符。在Python代码过程中，定义变量与变量赋值必须在同一步完成。Python内部会根据所赋变量的类型来创建该变量。 4.2.1 明白变量的本质——对象Python的内部使用了一个对象模型，这个对象模型用来存储变量及其对应的数据。在Python语言中，任何类型的变量都会被翻译程一个对象，这就是变量的本质。Python内部的对象模型由3部分组成：身份、类型和值。具体含义如下：* 身份：用来标识对象的唯一的标识。通过调用函数id可以得到它。这个身份标识值可以被理解成该对象的内存地址* 类型：用来表明对象可以存放的类型。具体的类型限制了改对象保存的内容、可以进行的操作、遵循的规则。想要查看某个对象的类型可以调用函数type* 值：对象所存储的具体数值 4.2.2 同时定义多个变量实际开发中，我们可以通过一条语句定义多个变量，彼此之间用逗号隔开即可。例如：在一条语句中为多个变量同时赋值> var1, var2, var3 = value1, value2, value3上述的代码中，系统会根据value1、value2、value3的值类型，分别定义var1、var2、var3三个变量，并为它们赋值。```In [1]: var1, var2, var3 = 2, 5, 3In [2]: print("指针：", id(var1), "类型：", type(var1), "值：", var1)指针： 140718912952000 类型：  值： 2In [3]: print("指针：", id(var2), "类型：", type(var2), "值：", var2)指针： 140718912952096 类型：  值： 5In [4]: print("指针：", id(var3), "类型：", type(var3), "值：", var3)指针： 140718912952032 类型：  值： 3``` 4.2.3 变量类型的介绍Python3中，所划分的类型有六种：* numbers：数值    * int：整型    * float：浮点型    * bool：布尔型    * complex：复数* string：字符串* tuple：元组* sets：集合* dict：字典 4.2.4 变量类型的帮助函数Python将自身所支持的变量类型定义成了一个个的类，任何类型都是一个类。每个类都有自己的方法和属性，通过类的方法和属性来规范该类型数值的操作。Python中内置了两个帮助函数：* dir：用来查询类或者类型的所有属性，如dir(list)* help：用来查询类或者类型具体的说明文档，如help(int) 4.3 numbers（数字）类型numbers类型是一个数值类型的合集，具体又可以细分为int(整型)、float(浮点型)、bool（布尔型）、complex（复数）等类型。 4.3.1 获取对象的类型每个对象都可以内置的函数type来查询该变量所指的对象类型。示例：```In [1]: a, b, c, d = 32.6, 58, True, 8+7j    定义四个变量a、b、c、d，为它们赋予不同类型的值In [2]: print(type(a), type(b), type(c), type(d))   ``` 4.3.2 算数运算符numbers类型支持多种算术运算处理| 运算符 | 描述 || :---: | :--- || + | 加 || - | 减 || * | 乘 || / | 除，会生成浮点数结果 || % | 取模（余数） || ** 或pow(x,y) | 幂 || // | 整除 || abs(x) | 取绝对值 || int(x,\[base\]) | 将x转换为整型。x可以是字符串或其他数字：base是可选参数，默认为10，表示将字符串x以10进制转换为整数。当base被赋值时，x必须是字符串；当x为浮点数时，转成的整数会将小数点后面的全部舍掉。如果想要更精确的转化，推荐用math库里面的floor和ceil函数来明确转换方式。 || float(x) | 将x转换为浮点型 || complex(re, im) | 生成复数。re为实数部门，im为虚数部分。 || c.conjugate() | 取c的共轭复数 || divmod(x,y) | 返回商和余数 | 4.3.3 实例```In [1]: a = 20In [2]: b = -3In [3]: print(a + b)  加好运算17In [4]: print(a - b)  减号运算23In [5]: print(a * b)  乘号运算-60In [6]: print(a / b)  除号运算-6.666666666666667In [7]: print(a % b)  取余运算-1In [8]: print(a ** b)  幂运算0.000125In [9]: print(a // b)  整除运算-7In [10]: print(abs(b))  绝对值运算3In [11]: print(int("1010", 2))   将字符串以二进制转换成整数10In [12]: print(float("3.14"))  将字符串转换成浮点数3.14In [13]: c = complex(a,b)  生成复数In [14]: print(c)(20-3j)In [15]: print(c.conjugate())   计算共轭复数(20+3j)In [16]: print(divmod(a,b))(-7, -1)``` 4.3.4 赋值运算符| 运算符 | 描述| :---: | :--- || += | 加。例如：a+=b等价于a=a+b || -= | 加。例如：a-=b等价于a=a-b || \*= | 加。例如：a\*=b等价于a=a\*b || /= | 加。例如：a/=b等价于a=a/b || %= | 加。例如：a%=b等价于a=a+%b || \*\*= | 加。例如：a\*\*=b等价于a=a\*\*b || //= | 取整数。例如：a//=b等价于a=a//b | 4.3.5 实例```In [1]: a = 9;b = 3In [2]: a += b   加。等价于 a = a + bIn [3]: print(a)12In [4]: a = 9;b = 3In [5]: a -= b   减。等价于 a = a - bIn [6]: print(a)6In [7]: a = 9;b = 3In [8]: a *= bIn [9]: print(a)   乘。等价于 a = a * b27In [10]: a = 9;b = 3In [11]: a /= b    除。等价于 a = a / bIn [12]: print(a)3.0In [13]: a = 9;b = 3In [14]: a %= bIn [15]: print(a)0In [16]: a = 9;b = 3In [17]: a **= bIn [18]: print(a)729In [19]: a = 9;b = 3In [20]: a //= bIn [21]: print(a)3``` 4.3.6 比较运算符| 运算符 | 描述 || :---: | :--- || == | 等于 || != | 不等于 || > | 大于 || < | 小于 || >= | 大于等于 || <= | 小于等于 || is | 指针等于 || is not | 指针不等于 || in | 存在序列中(列表、元组、集合、字典） || not in | 在序列中(列表、元组、集合、字典）不存在 |1. 常规使用方法```In [1]: a = 3In [2]: b = 5In [3]: c = NoneIn [4]: print(a == b)  等于比较FalseIn [5]: print(a != b)  不等于比较TrueIn [6]: print(a > b)   大于比较FalseIn [7]: print(a < b)   小于比较TrueIn [8]: print(a >= b)  大于等于比价FalseIn [9]: print(a <= b)  小于等于比较TrueIn [10]: print(a is b)  指针等于比价FalseIn [11]: print(a is not b)  指针不等于比较TrueIn [12]: print(c is None)  None值等于比较True```2. 链式使用```In [1]: a = 3In [2]: b = 5In [3]: c = 7In [4]: print(a<b<c)True```上述代码，会从左到右的顺序来进行计算。相当于`x < y and y <= z`3. “比较运算符”与“算术运算符”的优先级比较“比较运算符”的优先级会比“算数运算符”低> print(1.1 == 1.1 -0.1)  输出False尽管Python内部有这样优先级的规则，但还是建议在写类似这样的代码时为后面的算数运算符加上括号。> print(1.1 == (1.1- 0.1)  将后面的算数运算符括起来，表示优先计算这样做的目的在于提高代码的可读性，最大程序上消除代码歧义4. 复数的比较复数类型的对象不能比较大小，只能比较是否相等 4.3.8 谨慎使用si函数is函数的作用是比较指针是否相等。这里的指针代表存放对象的内存地址，即，is函数是比较两个对象所在的内存是否相等。is函数与“==”的区别：* “==”只是判断两个对象的内容是否相等* is函数不仅比较对内容是否相等，还比较指针是否相等**注意：**两个对象如果使用“==”得到true，使用is得到的不一定是true。因为它们的地址有可能会不同；但是如果使用is得到的结果是true，使用“==”就会一定是true，因为他们本身就是一块内存的内容。示例：```In [1]: a = -256In [2]: b = -256In [3]: print(a == b)  判断a和b的内容是否相等TrueIn [4]: print(a is b)  判断a和b的内存是否相等FalseIn [5]: print(id(a), id(b))  id的意思是打印出对象的地址2801587781008 2801587781584```1. 赋值机制在Python中，等号赋值是指直接将对象的内存指针赋值，如下示例：```In [1]: x = 23In [2]: y = 45In [3]: print('x = {} id(x) = {}\ny = {} id(y) = {}'.format(x, id(x), y, id(y)))x = 23 id(x) = 140717471357280y = 45 id(y) = 140717471357984In [4]: x = yIn [5]: print('x = {} id(x) = {}\ny = {} id(y) = {}'.format(x, id(x), y, id(y)))x = 45 id(x) = 140717471357984y = 45 id(y) = 140717471357984```2. 缓存的重用机制在优化整数对象时，Python会根据对象的读取频繁程度以及内存占用情况来考虑，按照一定的规则把某些对象存入缓存中。当程序中其他部分代码使用该值时，系统会先去缓存中查找该值，并直接引用找到的缓存地址。不需要额外创建。| 对象 | 是否需要重新创建 | 生效范围 || :--- | :--- | :--- || 范围在\[-5,256\]之间的小整数 | 如之前在程序中创建过，就直接存入缓存，后续不在创建 | 全局 || 字符串对象 | 如之前在程序中创建过，就直接存入缓存，后续不在创建 | 全局 || 大于256的整数 | 只要在改代码块内创建过就直接缓存。后续不再创建 | 本代码块 || 大于0的浮点型对象 | 只要在改代码块内创建过就直接缓存。后续不再创建 | 本代码块 || 小于0的浮点型对象 | 不进行缓存，每次都需要额外创建 | || 小于-5的整数 | 不进行缓存，每次都需要额外创建 | | 示例```In [1]:  范围在[-5,256]之间的小整数In [2]: int1 = -5In [3]: int2 = -5In [4]: print("[-5,256]情况下的两个变量指针",id(int1), id(int2))[-5,256]情况下的两个变量指针 140717471356384 140717471356384In [5]:  对于字符串In [6]: s1 = "3344"In [7]: s2 = "3344"In [8]: print("字符串情况下的两个变量指针",id(s1), id(s2))字符串情况下的两个变量指针 1985494965360 1985494965360In [9]:  大于256的整数In [10]: int3 = 257In [11]: int4 = 257In [12]: print("大于256的整数情况下的两个变量的指针",id(int3), id(int4))大于256的整数情况下的两个变量的指针 1985506345104 1985506347952In [13]:  大于0的浮点数In [14]: f1 = 256.4In [15]: f2 = 256.4In [16]: print("大于0的浮点数情况下的两个变量指针",id(f1), id(f2))大于0的浮点数情况下的两个变量指针 1985505062224 1985506346576In [17]:  小于0的浮点数In [18]: f1 = -2.45In [19]: f2 = -2.45In [20]: print("小于0的浮点数情况下的两个变量指针",id(f1), id(f2))小于0的浮点数情况下的两个变量指针 1985506345840 1985506346736In [21]:  小于-5的整数In [22]: n1 = -6In [23]: n2 = -6In [24]: print("小于-5的整数情况下的两个变量指针", id(n1), id(n2))小于-5的整数情况下的两个变量指针 1985506713424 1985506710128``` 4.3.10 布尔型关系的运算符布尔型数值之间需要进行逻辑关系运算。在计算机的底层逻辑电路中，布尔型运算应用地更为广泛。1. 布尔型关系运算符| 运算符 | 描述 || :---: | :--- || and | 取“与”。左右都为True，结果才为True || or | 取“或”。左右有一个为True，结果就位True || not | 取“反”。如果是True,结果就为False |注意：* and是一个短路运算符。它只有在第一个运算数位True时，才会计算第二个运算数的值。* or是一个短路运算符。它只有在第一个运算数为False时，才会计算第二个运算数的值。* not的优先级比其他类型的运算符低。2. 布尔型关系运算符示例```In [1]: a = 3In [2]: b = 5In [3]: c = NoneIn [4]: print(a < 5 and c is None)True``` 4.3.11 位运算符位运算符属于更细微的操作，它是针对每个字节中的每个位（bit）进行操作的。| 运算符 | 描述 || :---: | :--- || & | 按位“与” || \| | 按位“或” || ^ | 按位“异或” || ~ | 按位“取反” || << | 按位左移 || >> | 按位右移 |位运算通常在网络通信、硬件驱动领域应用的比较广泛。 **示例**```In [1]: a = 1In [2]: b = 3In [3]: print('a的二进制：', bin(a)[2:].zfill(8))a的二进制： 00000001In [4]: print('b的二进制：', bin(b)[2:].zfill(8))b的二进制： 00000011In [5]: print('a&b二进制：',bin(a&b)[2:].zfill(8), "\t", "a&b十进制：", a&b)a&b二进制： 00000001     a&b十进制： 1In [6]: print('a|b二进制：',bin(a|b)[2:].zfill(8), "\t", "a|b十进制：", a|b)a|b二进制： 00000011     a|b十进制： 3In [7]: print("a^b二进制：",bin(a^b)[2:].zfill(8), "\t", "a^b十进制：", a^b)a^b二进制： 00000010     a^b十进制： 2In [8]: print("~a二进制：", bin(~a)[2:].zfill(8), "\t", "~a十进制：", ~a)~a二进制： 00000b10      ~a十进制： -2In [9]: print("a<<b二进制：", bin(a<<b)[2:].zfill(8), "\t", "a<<b十进制：", a<<b)a<<b二进制： 00001000    a<<b十进制： 8In [10]: print("a>>b二进制：", bin(a>>b)[2:].zfill(8), "\t", "a>>b十进制：", a>>b)a>>b二进制： 00000000    a>>b十进制： 0``` 4.4 strings（字符串）类型字符串类型属于python中“序列”（sequence）类型的一种。“序列”类型可以理解成由一个容器和容器中的元素组成的结构体，其中的元素是有一定顺序的。字符串类型像一个数组，按照一定顺序放置着一个个的字符 4.4.1 字符串的描述字符串大体上可分为两类：* 单行字符串：使用单引号('')、双引号("")来表示* 多行字符块：也叫多行字符串，用三个（双或单）引号来表示。1. 单行字符串单行字符串表明引号内的字符串必须是单行。如果隔行了，需要用反斜杠“\”符号链接。```a = 'hello world'  a为单行字符串，内容必须在同一行s = 'hello ' \    s为单行字符串的另外一种写法，如果隔行了，用‘\’连接下一行   'world'```单行字符串的输出也是单行的2. 多行字符串在多行字符串的表述中，每行之间可以直接用回车符分开。输出也是按照代码中的回车符号来换行的。``` b为多行字符串b = '''hello world!hello china!'''```需要注意的是：定义多行字符串时，千万不要把注释写在字符串定义符的中间，否则会把注释也当成字符串。除了直接定义单行字符串和多行字符串之外，也可以使用str函数将其它类型转换成字符串。  4.4.2 转义符转义符是一种特殊的字符。在代码中看得见，但是在输出的时候会被转换成特殊意义。1. 转义符介绍转义字符是一种特殊的字符| 转义字符 | 描述 || :---: | :--- || \\(在行位时) | 续行符 || \\\\ | 反斜杠符号 || \\' | 单引号 || \\" | 双引号 || \\a | 响铃 || \\b | 退格 || \\e | 转义 || \\000 | 空 || \\n | 换行 || \\v | 纵向制表符 || \\t | 横向制表符 || \\r | 回车 || \\f | 换页 || \\oyy | 八进制数yy代表的字符 || \\xyy | 十进制数yy代表的字符 || \\other | 其他的字符以普通格式输出 | 2. 原字符串（raw string）转义字符并不是在所有情况下都有用的。如果不想让转义字符发生转义，需要在字符串前面加一个“r”或“R”，将其变为原字符串(raw string)。原字符串的内容会与代码中的内容完全一致。```aa = "line\tline"print(aa)bb = R"line\tline"print(bb)```也可以使用函数`repr(str)`将字符串aa（含有转义字符的字符串）转换成原字符串（raw string）3. 原字符串（raw string）的原理函数repr(str)在字符串前加上“r”或“R”的原理相似，都是在字符串str中查找反斜杠“\”字符。如果能找到，就在该字符前面在加一个反斜杠“\”，组成两个反斜杠字符“\\”，这样就会把原来str中那个不需要转义的反斜杠“\”输出来了。* 在字符串前面加上"r"或"R"，是直接作用在字符串常量上，相对不容易出错* 函数repr(str)是直接作用在字符串常量上。在使用repr(str)函数时要注意：输入的字符串参数必须是一个正常的字符串。如果对一个原字符串（raw string）进行repr转化，则会使反斜杠变得更多。4. 原字符串转成转义字符串转义字符串变量可以通过**repr(str)**函数变为非转义字符串变量。通过eval(str)函数将“转义字符串变量”转变成“转义字符串变量”的方法。```pythonbb = R'line\tline'          定义一个原字符串print(eval("'" + bb + "'")     在原字符串变量前后都加上单引号字符，通过eval函数即可生成转义字符 line    line```5. 规避转义字符带来的问题转义字符的存在一定程度地简化了编码的复杂度，但在某些情况下也容易带来问题。例如，当使用一个字符串来标识某个磁盘上的文件路劲时，则会产生错误。> path = "c:\temp.txt"这时，如果使用函数open来打开指定的文件（），则会报错。系统会将"\t"进行转义，认为要打开的文件路径为：“c:    emp.txt”,于是会提示找不到该文件6. 字符串转化扩展我们可以通过在字符串前面加上一个“r”或“R”，将字符串转成原字符串(raw string)。类似的用法还有很多：* 在字符串前面加上“b”，将字符串转换成二进制字符串* 在字符串前面加上“u”，将字符串转成Unicode编码的字符串 4.4.3 屏幕I/O及格式化**print函数**是用来将指定的内容以字符串的形式输出到屏幕上，属于屏幕I/O方式的一种。**input**函数是用来将键盘的按键信息输入到系统。1. 屏幕输出函数printprint语句的参数如下：`print([object,...][, sep=' '][,end='endline_character_here'][,file=redirect_to_here])`方括号内是可选的，具体参数说明如下：* Object：要输出的内容* sep：分隔符* end：结束符* file：重定向文件（默认值为屏幕输出）通过调整print函数中字符串的格式，可以控制屏幕上输出字符串的样子。这个调整的过程就叫作**字符串的格式化**。字符串格式化的两种方式：* 手动拼接：简单地用加号将不同的字符串连接起来* 使用占位符的方法：相对比较高级，2. 占位符占位符在一个字符串中占据着一个位置，在输出时将这个位置替换成具体的内容。而占位符不是随意地替任何内容占位，它有着严格的规则。即，每一个占位符只能替一种特定的类型占位。| 占位符 | 说明 || :---: | :--- || %s | 字符串 || %r | 非转义功能的字符串 || %c | 单个字符 || %b | 二进制整数 || %d | 十进制整数，一般会写成%nd，其中n代表输出的总长度 || %i | 十进制整数 || %o | 八进制整数 || %x | 十六进制整数 || %e | 指数(基底写为e) || %E | 指数（基底写为E） || %f | 浮点数，一般会写成%m.nf，其中m代表总长度，n代表小数点后几位 || %F | 浮点数，同上 || %g | 指数（e）或浮点数（根据显示长度） || %G | 指数（E）或浮点数（根据显示长度） |**注意**：%m.nf这种形式的占位符，m代表设定的总位数，n代表设定的小数点后的位数。具体的输出结果要根据生成的浮点型数字本身的位数确定：* 输出的浮点型长度小于总长度m时，则会在前面补空格。* 输出的浮点型小数点后的位数小于n时，则会在小数点后面补0* 当总长度m小于实际整数长度时，则会保存数据完整性，令总长度m失效，输出结果右对齐。* 当总长度m小于实际整数加上要输出的小数长度之和时，则会保存数据完整性，令总长度m失效，输出结果右对齐。```In [1]: print("总长度8，小数点后为2，实际长度不足，需要前补空格\n输出：%8.2f"%23.45)总长度8，小数点后为2，实际长度不足，需要前补空格输出：   23.45In [2]: print("总长度8，小数点后为4，小数点后位数不足，会在小数点后面补0\n输出：%8.4f"%23.45)总长度8，小数点后为4，小数点后位数不足，会在小数点后面补0输出： 23.4500In [3]: print("总长度2，小数点后为0，总长度比实际整数长度还小，总长度失效\n输出：%2.0f"%223.45)总长度2，小数点后为0，总长度比实际整数长度还小，总长度失效输出：223In [4]: print("总长度6，小数点后为4，总长度6小于实际长度7，总长度失效\n输出：%6.4f"%223.45)总长度6，小数点后为4，总长度6小于实际长度7，总长度失效输出：223.4500```3. 手动拼接的格式化```In [1]: x = 5  定义整型数值5In [2]: print(":", str(x).rjust(2), str(x*x).rjust(3), end=", ")  占两位，以右对齐的方式输出x本身   ...: ；占3位，以右对齐的方式输出x*x；:  5  25,In [3]: print(str(x*x*x*10).rjust(4))  占4位，以右对齐的方式输出x*x*x1250```str.rjust(n)的作用是将字符串靠右对齐，其中的参数n代表输出的长度。* 如果字符串不足这个长度，则默认在左边填充空格* 如果字符串的长度大于n，则令n失效，并不会截断字符串，而是把字符串全部显示。类似的方法患有字符串左对齐方法**str.rljust**, 字符串居中对齐的方法**str.center**4. 使用占位符的格式化```In [1]: x = 5In [2]: print(":", '%2d %3d,%4d'%(x, x*x, x*x*x))  在模板中放置了3个占位符，并指定输出长度:  5  25, 125```使用占位符的方法是：在模板字符串后面加个“%”符号，在“%”符号后面跟上要替换占位符的内容。5. 使用str.format格式化使用“%”符号进行字符串的格式化时，要求占位符出场的先后顺序必须与后面的具体内容相匹配。而使用str.format方法对字符串格式化时，含有占位符的模板与后面内容的映射关系可以更加灵活。1)基本用法```In [1]: x = 5In [2]: print(":", '{0:d}, {1:3d}, {2:4d}, {0:4d}'.format(x, x*x, x*x*x))  将x、x*x、x*x*x三个数值   ...: 按照字符串模板的格式输出: 5,  25,  125,    5```在print函数里，字符串模板中的占位符都被加上了一个大括号。每个大括号里的第一项用于维护与后面具体内容的对应关系，其数值与format函数中元素的索引相对应。这样就不需要让模板里的占位符与后面的具体内容顺序一一对应了。2）字符串模板的说明在字符串模板中，冒号后面的格式为“\[补齐字符\]\[对齐方式\]\[宽度\]”* 补齐字符：可以是任意字符，但只能是一个字符。如果没有该项，默认是空格* 对齐方式：可以是“”（右对齐）、“^”（居中）* 宽度：如果对应的输出是整数，需要写成“nd”的形式（n代表总长度）；如果对应输出的是浮点型，需要写成“m.nf”的形式（m代表总长度，n代表小数点后面的长度）；如果对应的输出是字符串，需要写成“n”的形式（n代表总长度）```In [3]: print('{0:=>10d}'.format(5))  右对齐，输出长度为10的整数，用=填充=========5In [4]: print('{0:&<10.3f}'.format(0.5))  左对齐，输出长度为10的整数，用&填充0.500&&&&&In [5]: print('{0:-^10}'.format("hello"))  居中，输出长度为10的字符串，用-填充--hello---```3)简介用法如果不追求具体的显示格式。**str.format**方法还有更简单的使用方法```x = 5print(":", '{0} {1}, {2} {0}'.format(x, x*x, x*x*x))  在模板中直接指定后面具体的内容顺序```4）扩展用法1——结合列表或元组类型```In [7]: mylist = [5, 15, 25]In [8]: print('{0}, {1}, {2}'.format(*mylist))5, 15, 25In [9]: mytuple = (5, 15, 25)In [10]: print('{0}, {1}, {2}'.format(*mytuple))5, 15, 25```5)扩展用法2——结合字典```In [13]: d = {'x': 5, 'xx': 15, 'xxx': 25}In [14]: print('x is {x}, xx is {xx}, xxx is {xxx}.'.format(**d))x is 5, xx is 15, xxx is 25.```6. 键盘输入**input**函数用于从标准输入读取一行> s = input("请输入字符，并按Enter键结束：")**input**函数默认的输入是字符串类型 4.4.4 字符串类型的运算及操作* 索引：索引是指在序列中某个排名的具体数值。索引从0开始* 连接：使用“+”运算符将两个序列连接起来，* 重复：使用“*”运算符，意义是重复该序列内容* 检索：使用中括号加下表的标识方法（\[index\]），下标是指序列中的索引位置。    * 当下标为正数时，是从左往右的索引顺序，从0开始计算    * 当下标为负数时，是从右往左的索引顺序，从1开始计算* 反检索：使用index函数来完成与检索相反的功能。即，通过字符返回该字符在字符串中的索引，该索引是按照从左向右排列的。* 切片：使用“\[起始: 结束: 步长\]”形式表示。意思是在原序列数据中，在开始位置切一下，在结束位置切一下，剩的中间片段就是切片。在中间片段你的切片中，还要按照步长指定的间隔来取字符串（一般默认为1）。切片数据是由开始位置的索引与结束位置的前一个索引所组成的（顾头不顾尾）```In [1]: s = "hello "In [2]: s2 = "daimayisheng"In [3]:  连接In [4]: print(s + s2)hello daimayishengIn [5]: print(s * 3)hello hello helloIn [6]:  检索In [7]: print(s[0], s[1], s[2], s[4])h e l oIn [8]: print(s[-1], s[-2], s[-4])  o lIn [9]: print(s[-5])eIn [10]: print(s[5])In [11]: print(s[6])---------------------------------------------------------------------------IndexError                                Traceback (most recent call last) in ----> 1 print(s[6])IndexError: string index out of rangeIn [12]: print(s.index('e'))1In [13]: print(s.index('l'))2In [14]: print(s.index('w'))---------------------------------------------------------------------------ValueError                                Traceback (most recent call last) in ----> 1 print(s.index('w'))ValueError: substring not foundIn [15]: print(s[1:3])elIn [16]: print(s[:3])helIn [17]: print(s[0:])helloIn [18]: print(s2[:])daimayishengIn [19]: print(s2[::2])diaihnIn [20]: print(s2[::-2])gesymaIn [21]: print(s2[::-1])gnehsiyamiadIn [22]: print(s2[-2::-1])nehsiyamiadIn [23]: print(s2[:0:-1])gnehsiyamiaIn [24]: s[3] = '3'---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last) in ----> 1 s[3] = '3'TypeError: 'str' object does not support item assignment``` 4.4.5 字符串相关的函数| 函数 | 说明 || :--- | :--- || len() | 求序列长度 || in | 判断元素是否存在于序列中 || max() | 返回字符编码的最大值 || min() | 返回字符编码的最小值 || ord(str) | 返回的那个字符的字符编码。例如：ord("我")，返回25105（默认utf8下）| chr(number) | 根据某个字符编码返回对应的字符。例如chr(25105),返回“我”（默认utf8下） || str.split(a) | 将字符串根据某字符a进行分割 || chr.join(list) | 将序列list使用chr字符连接起来 || str.title() | 将每个单词的首字母大写。 |更多的字符串方法可以使用`dir(str)`来查看。```In [1]: dir(str)Out[1]:['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']```  4.5 list（列表）类型list（列表）是Python中使用最频繁的数据类型。它是一个有序集合，与字符串一样，都属于“序列”类型。list的描述方法是将其内部元素使用中括号括起来，元素之间使用逗号进行分隔。需要注意的是，列表中每个元素的类型可以互不相同，并且可以嵌套使用。例如：```tt = "hello"   定义一个变量li = [1, 3, 4, tt, 3.4, "yes", [1, 2]]  列表中放置了整型、变量、浮点型、字符串、嵌套列表```**注意：**在定义list时，允许定义空列表。可以使用li = [] 来实现一个空列表。 4.5.1 list的运算及操作list也是“序列”类型，它与字符串一样，具有连接、重复、检索、反检索、切片这几个基本功能。与字符串不同的是，**列表中的元素是可以改变的**。 4.5.2 list的内置方法| 内置方法 | 描述 || :--- | :--- || list.append(x) | 在尾部添加一个元素，等价于a\[len(a): \] = \[x\] || list.extend(L) | 将给定的列表L中的元素接到当前列表list后面。等价a\[len(a): \] = B || list.insert(i, x) | 在给定的索引位置i前插入x。a.insert(len(a), x)等价于“a.append(x)”，表示在尾部插入x；头部插入x，可以使用a.insert(0,x) || list.index(x) | 返回列表中第一个值为x的元素。当list中没有x时会报错 || list.remove(x) | 删除列表中第一个值为x的元素。当list中没有x时会报错 || list.pop(i) | 将指定元素弹出的意思。返回列表中指定索引i的元素，并在列表中删除它。也可以不指定索引。如：a.pop()表示弹出最后一个元素 || list.clear() | 删除列表中的所有项，相当于del a\[:\] || del list\[i或切片\] | 删除变量或是删除列表中指定索引i的元素，也可以删除列表中指定的切片。当删除列表中指定索引i的元素时，等价于a.remove(a\[i\])或a.\_\_delitem\_\_(i),但效率相对较慢。del关键字还可以实现清空列表。 || list.count(x) | 返回列表中x出现的次数 || list.sort() | 列表排序操作 || list.reverse() | 逆序操作，等价于a\[::-1\] |<jupyter_code>tt = "hello"
list1 = [1, 4, tt, 3.4, "yes", [1, 2]]  # 顶一个涵盖各种类型的list
print('list1 = ', list1, '\t', 'id(list1) = ', id(list1))  # 将其内容及地址打印出来

# 比较list中添加元素的集中方法
list3 = [6, 7]   # 定义list3
l2 = list1 + list3   # 使用 + 将list1与list3连接在一起，得到l2
print("l2 = ", l2, '\t', "id(l2) = ", id(l2))  # 输出l2及其地址

l2 = list1.extend(list3)  # 使用extend将list1与list3连接在一起，赋值给l2
print("l2 = ", l2, '\t', "id(l2) = ", id(l2))  # 输出l2及其地址
print('list1 = ', list1, '\t', 'id(list1) = ', id(list1))  # 将list1内容及地址打印出来

l2 = list1.append(list3)  # 使用append将list3添加到list1中
print("l2 = ", l2, '\t', "id(l2) = ", id(l2))  # 输出l2及其地址
print('list1 = ', list1, '\t', 'id(list1) = ', id(list1))  # 将list1内容及地址打印出来

# 删除操作
print("list1 = ", list1) # 输出list1
del list1[2:5] # 删除切片
print("list1 = ", list1)
del list1[2] # 删除指定索引的值
print("list1 = ", list1)

l2 = list1
print("id(l2) = ", id(l2), "id(list1) = ", id(list1))
del list1   # 删除list1变量
print("l2 = ", l2)
print("list1 = ", list1)
# 删除数据
l3 = l2
del l2[:]
print("l2 = ", l2)
print("l3 = ", l3)<jupyter_output>l2 =  []
l3 =  []<jupyter_text>4.5.3 列表嵌套Python中并没有二维数组的概念，但可以通过列表嵌套达到同样的目的。例如```mat = [    [1,2,3],    [4,5,6],    [7,8,9]]``` 4.5.4 实例：使用list类型实现队列和栈队列和栈是两种数据结构，其内部都是按照固定顺序来存放变量的。二者的区别在于对数据的存取顺序：* 队列：先存入的数据，最先取出。即，先进先出。* 栈：最后存入的数据，最先取出。即，后进先出。<jupyter_code>queue = []   # 定义一个空list，当做队列
queue.insert(0, 1)       # 向队列里存入一个整型元素 1
queue.insert(0, 2)       # 向队列里存入一个整型元素 2
queue.insert(0, "hello")       # 向队列里存入一个整型元素 "hello"
print("取第一个元素", queue.pop())     # 从队列里取一个元素，根据先进先出的原则，输出1
print("取第二个元素", queue.pop())     # 从队列里取一个元素，根据先进先出的原则，输出2
print("取第三个元素", queue.pop())     # 从队列里取一个元素，根据先进先出的原则，输出hello
stack = []                    # 定义空list，当做栈
stack.append(1)               # 向栈里存入一个整型元素1
stack.append(2)               # 向栈里存入一个整型元素2
stack.append("hello")         # 向栈里存入一个整型元素hello
print("取第一个元素", stack.pop())     # 从队列里取一个元素，根据先进先出的原则，输出hello
print("取第二个元素", stack.pop())     # 从队列里取一个元素，根据先进先出的原则，输出2
print("取第三个元素", stack.pop())     # 从队列里取一个元素，根据先进先出的原则，输出1<jupyter_output>取第一个元素 hello
取第二个元素 2
取第三个元素 1<jupyter_text>**使用deque结构体实现更高效的队列**标准库的collections.deque结构体，它是被设计成在两端存入和读取都很快的特殊list。```from collections import deque          导入deque结构体queueandstack = deque()              创建空结构体，既可以当队列，也可以当栈queueandstack.append(1)              向结构体里存入一个整型元素1queueandstack.append(2)              向结构体里存入一个整型元素2queueandstack.append(3)              向结构体里存入一个字符串helloprint(queueandstack.popleft())          实现队列功能，从队列里取出一个元素，根据先进先出原则，输出1print(queueandstack.pop())            实现栈功能，从栈里取一个元素，根据后进先出的原则，输出helloprint(list(queueandstack))            将结构体内容打印出来```list的内置方法：`dir(list)````In [1]: dir(list)Out[1]:['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']```  4.6 tuple（元组）类型tuple（元组）可以理解为list的只读版。与list（列表）非常类似，内部元素也是按照一定顺序存储，每个元素的类型也可以各不相同。与list的不同之处在于，**元组的元素不能修改**。 4.6.1 tuple的描述元组的描述方法是：元素使用小括号括起来，元素之间使用逗号来分隔。如<jupyter_code>a = (1999, 2008, "physics", "math")
print("a = ", a)
print("type(a) = ", type(a))
print("len(a) = ", len(a))<jupyter_output>a =  (1999, 2008, 'physics', 'math')
type(a) =  <class 'tuple'>
len(a) =  4<jupyter_text>元组的“序列”类型相关操作与list基本一样，下表索引从0开始，支持索引、切片等读取方式，但不支持修改类的方法。```In [1]: tup = (1, 2, 3, 4, 5, 6)In [2]: print(tup[0], tup[1:5])  通过切片访问内部数据1 (2, 3, 4, 5)In [3]: tup[0] = 11---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last) in ----> 1 tup[0] = 11TypeError: 'tuple' object does not support item assignment```虽然tuple的元素不可改变，但它可以包含可变的对象，比如list对象，并且可变对象内部的内容也是可以修改的tuple的定义还有一些特殊的规则：* 在定义包含0个元素的tuple时，直接使用小括号* 在定义包含1个元素的tuple时，需要在元素后面加一个逗号。例如：```tup1 = ()    定义空元祖tup2 = (20, )    定义只有一个元素的元组```因为字符串常量与元组都有不可修改的特性，所以字符串常量也可以理解成为一种特殊的元组。 4.6.2 运算及操作元组与列表内的元素可是为任何类型，这表明元素与列表都可以互相嵌套，也可以嵌套自己。元组的操作与字符串一致。 4.6.3 实例```In [1]:  空元素In [2]: t = ()   定义一个空的tupleIn [3]: li = []  定义一个空的listIn [4]:  因为tuple不能修改，所以定义空的tuple几乎是没有什么作用的。但是定义一个空的列表是很常用的In [5]:  定义一个元素In [6]: t = (3)    不加逗号，定义了一个整型In [7]: print(t, type(t))3 In [8]: t = (3, )   加逗号，定义了一个拥有一个元素的元组In [9]: print(t, type(t))(3,) In [10]: li = [3]In [11]: print(li, type(li))[3] In [12]: l1 = [3,]In [13]: print(l1, type(l1))[3] In [14]:  元素修改处理In [15]: tt = "hello"In [16]: t1 = (1, 3, 4, tt, 3.4, "yes", [1,2], (4,5))  元组中放置了整型、变量、浮点型、字符串、列表、嵌套元组In [17]: print(t1)(1, 3, 4, 'hello', 3.4, 'yes', [1, 2], (4, 5))In [18]: t1[6][0] = "3456"   为元组中列表里的元素赋值In [19]: print(t1)(1, 3, 4, 'hello', 3.4, 'yes', ['3456', 2], (4, 5))In [20]: t1[0] = 3---------------------------------------------------------------------------TypeError                                 Traceback (most recent call last) in ----> 1 t1[0] = 3TypeError: 'tuple' object does not support item assignment```tuple的内置方法：```In [1]: dir(tuple)Out[1]:['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']```  4.7 set（集合）类型Python中集合的主要作用是，进行成员关系测试和消除重复元素。 4.7.1 set的描述set是一个无序、不重复元素的集合。它的描述方法是：内部的元素使用大括号括起来，元素之间使用逗号分隔；元素可以是任何类型。例如```In [1]: myset = {"hello", "hello", "Python", "tensorflow", 2, 1, 2}  定义一个setIn [2]: print(myset){'tensorflow', 2, 1, 'hello', 'Python'}```也可以使用**set**函数将其他类型的变量转成set。若想定义一个空的set变量，只能使用set函数；如果使用大括号的方法时，里面元素个数为0时，系统会认为这是一个“字典”类型，而不是创建集合类型 4.7.2 set的运算set的运算与数学中的集合运算相似，支持差、并、交等操作。```In [1]: helloset = set("hello")  通过set函数，将字符串"hello"转成一个集合In [2]: tensorflowset = set("tensorflow")  通过set函数，将字符串"tensorflow"转成一个集合In [3]: 'w' in tensorflowset  判断'w'是否在tensorflowset集合中Out[3]: TrueIn [4]: 'w' in helloset   判断'w'是否在helloset集合中Out[4]: FalseIn [5]: helloset    打印hellosetOut[5]: {'e', 'h', 'l', 'o'}In [6]: helloset - tensorflowset   计算集合helloset与tensorflowset的差集Out[6]: {'h'}In [7]: helloset | tensorflowset   计算集合helloset与tensorflowset的并集Out[7]: {'e', 'f', 'h', 'l', 'n', 'o', 'r', 's', 't', 'w'}In [8]: helloset & tensorflowset   计算集合helloset与tensorflowset的交集Out[8]: {'e', 'l', 'o'}In [9]: helloset ^ tensorflowset   计算集合helloset与tensorflowset的对称差集Out[9]: {'f', 'h', 'n', 'r', 's', 't', 'w'}``` 4.7.3 set的内置方法| 集合的内置函数 | 描述 || :--- | :--- || set.add(x) | 往集合中添加一个元素x || set.update(seq) | 将一个序列seq的全部元素添加到集合里 || set.remove(x) | 删除集合里的元素x。当x不存在集合中时，会报KEYERROR的错误 || set.discard(x) | 如果集合里有元素x时，删除集合里的元素x || set.clear() | 清空集合中的所有元素 || set.pop() | 随机选择集合中的一个元素，并删除 || len(set) | 返回集合中元素的个数 || in | 判断某个值是否在集合中 || not in | 判断集合中是否没有给定的值 || set.issubset(set2) 或 set <= set2 | 判断set是否是set2的子集，返回bool类型 || set.issuperset(set2) 或 set >= set2 | 判断set2是否是set的子集 || set.union(set2) 或 set \| set2 | 计算set与set2并集 || set. intersection(set2) 或 set & set2 | 计算set与set2交集 || set.difference(set2) 或 set-set2 | 计算set与set2差集 || set.symmetric_difference(set2) 或set ^ set2 | 计算set与set2的对称差集 | 4.7.4 不可变集合Python中有一个不可变集合（frozenset），类似于元组与列表的关系。在取值上，不可变集合与集合是一样的。不同的是，frozenset里的值不能被改变```In [1]: mytuple = ("hello", "hello", "Python", 2, 1, 2)  定义一个tupleIn [2]: myset = frozenset(mytuple)   是应用frozenset函数将元组转成不可变集合In [3]: mysetOut[3]: frozenset({1, 2, 'Python', 'hello'})```**set的内置方法**```In [1]: dir(set)Out[1]:['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__init_subclass__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']``` 4.8 dict（字典）类型字典（dict）是python中非常有用的内置数据类型。字典是一种映射类型（mapping type），是一个无序的集合。内部元素是以键值对形式出现，即一个关键字(key)与一个值（value）的组合（“键-值”）。关键字(key)必须是不可变类型。在同一个字典当中，关键字必须是唯一的。 4.8.1 字典描述字典与set非常的相似，也是用大括号括起来的，不同的是，字典的元素必须是“键-值”对（key:value）类型。另外，也可以使用dict函数将其他变量转成字典。例如```In [1]: mylist = [('hello', 1), ('good', 2), ['ok', 3]]  定义一个list，里面嵌套元素和list，每个嵌套的元素都是有2个元素In [2]: d = dict(mylist)  使用dict函数将mylist转成字典In [3]: d2 = {'hello': 1, 'good': 2, 'ok': 3}  使用大括号创建字典In [4]: dOut[4]: {'hello': 1, 'good': 2, 'ok': 3}In [5]: d2Out[5]: {'hello': 1, 'good': 2, 'ok': 3}``` 4.8.2 字典的运算字典类型的对象要通过关键字（key）来取值，它的用法是在后面加个中括号，里面输入key的字符串。```In [1]: d2 = {'hello': 1, 'good': 2, 'ok': 3}  使用大括号创建字典In [2]: d2['hello']  取出字典中key为hello的值Out[2]: 1In [3]:  字典中更新某个key的对应的值，直接将其取出用等号赋值In [4]: d2['hello'] = 'e'  将字典中key为hello的赋值为eIn [5]: d2['hello']  取出字典中key为hello的值Out[5]: 'e'```1. 为字典类型添加元素如果要在已有的字典中增加一个键值对(key:value),可以直接在后面加个中括号，里面输入key的字符串，并用等号赋值。```In [1]: d2 = {'hello': 1, 'good': 2, 'ok': 3}In [2]: d2['new'] = 100    在字典中加入key为new，值为100的键值对In [3]: d2Out[3]: {'hello': 1, 'good': 2, 'ok': 3, 'new': 100}```2. 删除字典类型的元素如果要删除某个键值对，可以使用内置函数del。```In [1]: d2 = {'hello': 1, 'good': 2, 'ok': 3}In [2]: del d2['hello']   在字典中删除key为hello的键值对In [3]: d2Out[3]: {'good': 2, 'ok': 3}```3. 获取字典类型的key与value字典中内置的方法keys，可以返回dict_keys类，里面包含素有的key；内置方法value，可以返回dict_values类，里面包含所有的值。```In [1]: d2 = {'hello': 1, 'good': 2, 'ok': 3}In [2]: type(d2.keys())Out[2]: dict_keysIn [3]: d2.keys()Out[3]: dict_keys(['hello', 'good', 'ok'])In [4]: type(d2.values())Out[4]: dict_valuesIn [5]: d2.values()Out[5]: dict_values([1, 2, 3])In [6]: list(d2.keys())Out[6]: ['hello', 'good', 'ok']In [7]: list(d2.values())Out[7]: [1, 2, 3]``` 4.8.3 字典的内置方法| 内置方法 | 描述 || --- | --- || dict.fromkeys(seq \[,value\]) | 创建一个新字典， 序列seq中的元素作为字典的键，value（可选）作为字典所有键对应的初始值 || dict.get(key \[,default=None\]) | 返回指定键key的值，若key不存在，则返回default值 || dict.setdefault(key, default=None) | 与get类似，但如果键不存在，将会添加键并将键值设为default || del | 删除字典中指定了key的键值对 || dict.clear() | 清空字典中的所有元素 || dict.items() | 以列表方式返回可遍历的(键, 值)元组数组 || len(dict) | 返回字典中元素的个数 || in | 判断某个key是否在字典中存在 || not in | 判断某个key是否不存在字典中 || dict.keys() | 以列表方式返回一个字典中所有的键 || dict.values() | 以列表方式返回字典中的所有值 || dict.update(dict1) | 把字典dict1的键值对更新到dict里 |dict内置方法```In [1]: dir(dict)Out[1]:['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']``` 4.9 对组合对象进行“深拷贝”与“浅拷贝” 4.9.1 浅拷贝浅拷贝是指创建一个新的对象，其内容是原对象中元素的引用。所有组合对象被创建并赋值的过程，属于浅拷贝。```In [1]: tt = [[1, 2], "hello"]  定义一个list组合对象In [2]: aa = tt   直接用等号赋值。只是指针赋值In [3]: id(tt), id(aa)   打印两者的指针Out[3]: (1835052508416, 1835052508416)In [4]: aa = list(tt)  使用list创建一个列表对象，并对该对象赋值In [5]: id(tt), id(aa)  打印两者的指针Out[5]: (1835052508416, 1835052287552)In [6]: for x, y in zip(aa, tt):   ...:     print(id(x), id(y))  二者内部元素指针完全没有变化   ...:1835052212864 18350522128641835052215600 1835052215600```类似浅拷贝的现象缓存在于：切片操作、工厂函数、对象的copy方法、copy模块中的copy函数。 4.9.2 深拷贝深拷贝是指创建一个新对象并对其赋值时，原对象中的所有元素都会在新对象重新创建一次。```In [1]: import copy   导入copy模块In [2]: tt = [[1, 2], 'hello']    定义list组合对象In [3]: aa = copy.deepcopy(tt)    使用深拷贝将tt赋值给aaIn [4]: id(tt), id(aa)Out[4]: (2598008537024, 2598008200448)In [5]: for x, y in zip(aa, tt):  将两个list中的每个元素指针打印出来   ...:     print(id(x), id(y))   ...:2598008169472 25980082235522598007465328 2598007465328```  5. 控制流——控制执行顺序的开关控制流又叫做流程控制，即，根据具体情况来控制程序，令程序执行某些特定的程序块。Python中的流程控制语句包括if条件语句、while循环语句和for循环语句。 5.1 if语句if语句是最常用的条件控制语句，关键字有if、elif、else 5.1.1 语句形式一般的表述形式为：```if 条件一:    statements1elif 条件二:    statements2...else:    statements3``` 5.1.2 if语句使用演示<jupyter_code>a = 4 # 定义变量a的值为4
b = 5 # 定义变量b的值为5
if a > b: # 当a大于b时
    c = a
else:  # 当a不大于b时
    c = b
print(c)<jupyter_output>5<jupyter_text>上述代码中，if与else部分还有一个简化的写法，即，c = \[a,b\]\[a<b\]。这种语法的完整含义是：* 如果第二个方括号里的条件为假，则返回第一个方括号中的第一个元素。* 如果第二个方括号里的条件为真，则返回第一个方括号中的第二个元素。 5.1.3 根据性别选择合适的称呼<jupyter_code>getstr = input("请输入性别：")   # 调用input函数，获得输入字符串
anstr = ['先生', '女士']['female' == getstr.strip()]
print("尊敬的", anstr, '你好')<jupyter_output>尊敬的 先生 你好<jupyter_text>5.2 while语句while语句用来表述一个循环执行的代码流程。 5.2.1 语句形式语句的形式为：```while 条件一:    statements1```语句的执行过程，可以分解为如下步骤：（1）执行条件一判断语句，是否为True（2）如果返回True，则执行下面的statements1代码（3）执行完statements1代码后，返回到（1）步（4）如果第（1）步返回False，则整个语句结束 5.2.2 while语句使用的代码演示<jupyter_code>c = 4  # 定义变量c
while c > 0:
    print(c)
    c -= 1<jupyter_output>4
3
2
1<jupyter_text>5.2.3 十进制转化为二进制数<jupyter_code>a = input("请输入一个十进制数：")   # 获取一个十进制数
d = int(a)   # 将输入的字符串转为整型
s = ""
while d != 0:
    d, f = divmod(d, 2)   # 除以2，并返回除数和余数
    s = str(f) + s
print(s)<jupyter_output>请输入一个十进制数：14
1110<jupyter_text>5.3 for语句Python中for语句与while语句都是用来循环执行代码块的。for循环的执行条件，不是判断逻辑是否为真，而是遍历一个序列容器。 5.3.1 语句形式for的语句形式为：```for item in 序列数据:    statements1```意思是，每次从序列容器中取一个值（item），然后执行语句statements1。当遍历完整个序列容器，循环也就结束了。 5.3.2 在for循环中，使用切片序列容器可以是任何python支持的序列数据类型。如果在循环体内，执行语句statements对for后面的序列数据进行了修改，这会影响到for语句的初始循环次数。为了避免这类问题，使用切片或者深拷贝的方式为该序列数据创建一个副本，让for循环来遍历副本中的序列数据。这样即使statements中的语句对序列数据做出了修改，也不会影响for循环。```words = ['I', 'love', 'Python']  定义一个列表for item in words[:]:    words.insert(0, item)   向words中插入一个元素    print(item)print(words)```就上述代码而言，如果将`for item in words[:]`改为`for item in words`，则会造成死循环。 5.3.3 实现冒泡排序<jupyter_code>n = [5, 3, 25, 8, 19, 14, 35, 23]
print("原数据：", n)

for i in range(len(n)-1):
    for j in range(len(n)-i-1):
        if n[j] > n[j+1]:
            n[j], n[j+1] = n[j+1], n[j]

print("排序后：", n)<jupyter_output>原数据： [5, 3, 25, 8, 19, 14, 35, 23]
排序后： [3, 5, 8, 14, 19, 23, 25, 35]<jupyter_text>1. **zip函数****zip函数**可以将任意多的“序列”类型数据结合起来，生成新序列数据。生成的新序列数据中，每个元素都是一个元组。该元组的元素都是由传入zip函数中的多个序列数据的元素组成的。```In [12]: a = [1,2,3,4]In [13]: b = ['a', 'b', 'c', 'd']In [14]: c = ['I', 'II', 'III', 'IV']In [15]: t = zip(a, b, c)In [16]: tOut[16]: In [17]: tuple(t)Out[17]: ((1, 'a', 'I'), (2, 'b', 'II'), (3, 'c', 'III'), (4, 'd', 'IV'))```生成的序列t也可以转成列表（list）类型。当zip对象（t）被转化为元组或列表后，就会自动销毁。如果在使用t，将得不到具体的元素。zip对象还可以通过前面加个字符“*”的方式完成unzip的过程，所谓unzip，就是将zip生成的数据返回。```In [20]: a = [1,2,3,4]In [21]: b = ['a', 'b', 'c', 'd']In [22]: c = ['I', 'II', 'III', 'IV']In [23]: t = zip(a, b, c)In [24]: print(*t)(1, 'a', 'I') (2, 'b', 'II') (3, 'c', 'III') (4, 'd', 'IV')```如果zip里面的序列长度不同，就会以最短的序列数据为主。2. **enumerate函数**在使用enumerate生成的新序列中，每个元素都是一个元组，该元组是由传入enumerate函数中序列的元素与其对应的索引组成的。 5.4 对循环进行控制——break、continue、pass语句* break：跳出当前的for或while循环* continue：终止当前这一次执行，进行循环的下一次迭代* pass：该语句什么都不做，是为了保持程序结构的完整性。 实例代码：人机交谈<jupyter_code>getstr = "" # 定义空字符串，接收输入
while ("Bye" != getstr):
    if "" == getstr:
        print("hello world")
    getstr = input("请输入字符，并按Enter结束：")
    if "hello" == getstr.strip():
        print("How are you today?")
        getstr = "start"
    elif "go away" == getstr.strip():
        print("sorry! bye-bye!")
        break
    elif 'pardon' == getstr.strip():
        getstr = ""
        continue
    else:
        pass
    if "start" == getstr:
        print("init dialog-serving...")
        print("one thing...")
        print("two thing...")
        print("......")<jupyter_output>hello world
请输入字符，并按Enter结束：hello
How are you today?
init dialog-serving...
one thing...
two thing...
......
请输入字符，并按Enter结束：go away
sorry! bye-bye!<jupyter_text>5.5 列表推导式列表推导式是一种创建列表的方法。```In [26]: Y = [1,0,1,0,1,1,1,1,0]In [27]: colors = ['r' if item == 0 else 'b' for item in Y]In [28]: colorsOut[28]: ['b', 'r', 'b', 'r', 'b', 'b', 'b', 'b', 'r']``` 九九乘法表<jupyter_code>for x in range(1, 10):
    l = ['%s * %s = %-2s' % (y, x, x * y) for y in range(1, x + 1)]
    for i in range(len(l)):
        print(l[i], end='\t')
    print('')<jupyter_output>1 * 1 = 1 	
1 * 2 = 2 	2 * 2 = 4 	
1 * 3 = 3 	2 * 3 = 6 	3 * 3 = 9 	
1 * 4 = 4 	2 * 4 = 8 	3 * 4 = 12	4 * 4 = 16	
1 * 5 = 5 	2 * 5 = 10	3 * 5 = 15	4 * 5 = 20	5 * 5 = 25	
1 * 6 = 6 	2 * 6 = 12	3 * 6 = 18	4 * 6 = 24	5 * 6 = 30	6 * 6 = 36	
1 * 7 = 7 	2 * 7 = 14	3 * 7 = 21	4 * 7 = 28	5 * 7 = 35	6 * 7 = 42	7 * 7 = 49	
1 * 8 = 8 	2 * 8 = 16	3 * 8 = 24	4 * 8 = 32	5 * 8 = 40	6 * 8 = 48	7 * 8 = 56	8 * 8 = 64	
1 * 9 = 9 	2 * 9 = 18	3 * 9 = 27	4 * 9 = 36	5 * 9 = 45	6 * 9 = 54	7 * 9 = 63	8 * 9 = 72	9 * 9 = 81<jupyter_text>5.6 迭代器for语句的循环次数，是根据遍历序列容器中的数据个数来决定的。在遍历循环的过程中，隐式的调用了内置函数iter。函数iter别调用后，会返回一个迭代器对象。迭代器对象定义了\_\_next\_\_方法，在每次访问时，就会得到一个元素，当没有任何元素时，\_\_next\_\_将通过StopIteration异常来告诉for语句停止迭代。```In [1]: x = [1, 2, 3]In [2]: it = iter(x)In [3]: print(it.__next__())1In [4]: print(it.__next__())2In [5]: print(it.__next__())3In [6]: print(it.__next__())---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last) in ----> 1 print(it.__next__())StopIteration:```Python中有一个内置函数**next**，它是迭代器对象中的\_\_next\_\_方法的另一种写法。```In [1]: x = [1, 2, 3]In [2]: it = iter(x)In [3]: print(next(it))1In [4]: print(next(it))2In [5]: print(next(it))3In [6]: print(next(it))---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last) in ----> 1 print(next(it))StopIteration:```  6. 函数面向过程思想，是一种以过程为中心的编程思想。面向过程的大概意思是：先分析出解决问题所需要的步骤，接着编写函数来实现每一步的功能，最后再对每个函数一次调用**函数（function）**是组织好的、可重复使用的、具有一定功能的代码段。它能提高模块和代码的重复利用率。 6.1 函数的基本概念Python中可通过固定的格式来定义函数，函数具体的组成部分（函数名、函数体、参数、返回值）。从分类上来看，函数可分为内置函数、自定义函数 6.1.1 函数的定义定义函数使用关键字def。格式如下：```def 函数名(参数1, 参数2, ..., 参数N):    pass``` 6.1.2 函数的组成部分1. 函数的组成函数有四个组成部分：* 函数名：def后面的名字* 函数参数：函数名后面的变量* 函数体：函数名的下一行diamante，起始需要缩进。* 返回值：函数执行完的返回内容，用return开始。如没有返回值，则可以不写。2. 文档字符串在函数中，常会在开始位置放置一个多行字符串，用来说明函数的功能及调用方法。这个字符串叫做函数的文档字符串（docstring），可以使用代码`print(函数名.__doc__)`将其输出。 6.1.3 函数的参数：形参与实参* 形参：从函数的角度来说。* 实参：从调用的角度来说。 6.1.4 函数的返回值函数不需要返回值的时候，函数体可以什么都不做，函数体使用`pass`占位符即可。需要返回值时，就要使用return语句将具体的值返回。使用return语句可以一次返回多个值。调用时，可以定义多个变量来接收，也可以使用一个元组来接收。```def getrecoder():     定义一个函数getrecoder    name = 'Gary'    age = 32    return name, agemyname, myage = getrecoder()    在调用时，使用与返回值对应的两个值来接收print(myname, myage)person = getrecoder()print(person)```有时候可能只是需要用到返回值中的一个，而将其他的忽略掉。这种情况下，可以使用下划线（\_）来接收对应返回值。```personname, _ = getrecoder()   调用时，使用_来接收不需要的返回值print(personname)``` 6.1.5 函数的属性1. \_\_dict\_\_：用来放置函数的属性。2. 直接在函数名后面加个点，就可以在后面直接添加属性；获取该属性时，也同样是在函数名后面加个点，再跟上函数的具体属性名。这种方式可以在使用函数时传递更多的信息。 6.1.6 函数的本质函数的本质是对象。可以被调用的函数，都会继承了可调用的方法call，所以可以使用函数callable来检查某函数是否可以被调用。```def recoder(strname, *, age):     定义一个函数recoder    print('姓名：', strname, '年纪：', age)print(callable(recoder))``` 6.1.7 函数的分类1. 从用户角度看从用户角度看，函数可以分为内置函数、自定义函数* 内置函数：Python内部自带的函数* 自定义函数：用户自己写的函数。2. 从功能角度看从功能上又可以分为生成器、一般函数。3. 从形式的角度来看* 普通的函数：使用def来定义* 匿名函数：使用lambda关键字来定义* 可迭代函数：属于一种特殊的内置函数。* 递归函数：自己调用自己的函数。* 偏函数：使用partial关键字来定义。```import mathdef compatefu2(x, y):    var3 = abs(y) - 0.65 * x * x    return var3 >= 0.2def comparefu1(x, y):    H = 3    var1 = math.sqrt(x * x + y * y) - H * math.sin(2 * math.atan(y / x))    var2 = math.sqrt(x * x + y * y) - H * math.sin(2 * math.atan(-y / x))    return abs(var1) <= 1 or abs(var2) <= 1def printstar(y, stepy, comparefun):    while(y <= 0):        x = -4        while(x <= 4):            if comparefun(x,y):                print("*", end="")            else:                print(" ", end="")            x += 0.2        y += stepy        print("")printstar(-4, 0.2, comparefu1)printstar(-1, 0.2, comparefu2)``` 6.2 定义参数及调用函数 6.2.1 函数参数的定义方法与调用形式1. 列表方式定义参数与调用方式：fun(参数1， 参数2, ...)一个函数可以定义任意个参数，参数间用逗号分隔。```def recoder(strname, age):     定义一个函数，名称为recoder    print("姓名：", strname, "年纪：", age)```**（1）函数的调用：按照形参顺序依次传入**单独函数是运行不起来的。需要对其调用才可以执行。调用时，直接使用函数名称，并且还要提供相同个数的参数。默认的情况下，实参顺序需要与形参的顺序一一对应**（2）函数的调用：传入指定形参**在调用函数时，还可以直接为某个指定的形参赋值。在指定具体形参的情况下，传入的参数可以与形参的顺序无关。`recoder(age=32, strname=" Gary ")`**注意**：为防止函数体内对参数的操作与参数类型不 致，可以提前对参数类型进行检查2. 星号方式定义参数与调用方式在参数的列表中还可以直接使用星号(\*)，代表调用函数时，在星号后买你的采纳数都必须要指定参数名称。```def recoder(stranme, *, age):    定义一个函数recoder，要求形参age必须被指定    print("姓名：", strname, "年纪：", age)recoder("Gary", age=20)recoder("Gary", 20)  错误写法，没有指定形参age```3. 带默认实参的列表方式，定义参数与调用方式：fun(参数1, 参数2=值2,...)在调用时，被提供默认值的形参不需要有实参与其对应。没有传入实参的形参，自动会取默认值为其初始化。```def recoder(strname, age=32):    print("姓名：", strname, "年纪：", age)```函数调用```recoder("LiLi", 23)recoder("Lisi")```4. 通过元组或列表的解包参数的方式，定义参数与调用方式：fun(\*参数)这种定义方式只有一个形参，当被调用时，形参会被定义为一个元组。传入的实参都是这个元组类型的形参的元素。在函数体中，可以通过访问形参中的元素来获取传入的实参。（1）函数的调用：传入任意多的实参这种定义方式在调用时，可以传入任意多的实参。```In [1]: def recoder(*person):   ...:     print("姓名：", person[0], "年纪：", person[1])   ...:In [2]: recoder("Gary", "32")姓名： Gary 年纪： 32```注意：* 这种方式无法通过指定形参名称来传入实参，而且传入的实参顺序与形参内部元素的顺序必须一一对应* 函数接收参数的类型是元组，所以，用这种方式传值后不能对形参内容进行修改。（2）函数的调用：传入列表或元组将列表或元组当做实参传入。具体做法是，在列表或元组前加上一个型号。```In [1]: def recoder(*person):   ...:     print("姓名：", person[0], "年纪：", person[1])   ...:In [2]: myList = ["Gary", 32]In [3]: recoder(*myList)姓名： Gary 年纪： 32```5. 通过字典的解包参数方式，定义参数与调用方式：fun(**参数)这是一种更为灵活的传参方式：传入的实参同时为其定义一个形参。这样在函数里就可以通过指定具体形参名称来获取实参了。这种方式是将形参当成一个字典类型变量里来接收实参。这样传入的实参和对应的名字就可以放到这个字典里。形参为字典中元素的key，实参为字典中元素的value。```def recoder(**person):    print("姓名：", persone['name'], "年纪：", persone['age'])```（1）函数的调用：传入指定形参调用时，传入实参的同时也指定了形参名称，例如：```recoder(age=32, name="Gary")```若要使用上述的方式调用函数，就必须为形参指定名称（2）函数的调用：传入字典调用时，还可以直接将字典传入。具体方法是，在传入的字典变量前面加两个星号```Mydic = {"name": "Gary", "age": "32"}recoder(**Mydic)``` 6.2.3 函数调用中的参数传递及影响在Python的内部机制里，函数调用时，实参传递到形参的过程中有两种情况——传值和传引用* 对于不可变对象，函数调用时，是传值。意味着函数体里面可以使用实参的值，但不能改变实参* 对于可参数，函数调用时，是传引用。意味着在函数体里还可以对实参进行过修改。1. 不可变对象不可变对象的传值是指：当传入的实参指向一个常量，或是一个元组等不允许修改的对象时，就把该对象的值传递给形参。```In [1]: def func(arg):   ...:     arg = 5   ...:     print("函数体内部：arg = ", arg)   ...:In [2]: x = 1In [3]: func(x)函数体内部：arg =  5In [4]: print(x)1```2. 可变对象可变对象的传递引用是指：当传入的实参指向一个list，或者一个字典等允许修改的对象时，就把这个对象的值传递给形参。```In [1]: def fun(arg):   ...:     arg.append(3)   ...:In [2]: x = [1, 2]In [3]: fun(x)In [4]: xOut[4]: [1, 2, 3]``` 6.3 匿名函数与可迭代函数匿名函数一般适用于单行代码函数。匿名函数的作用是，把某一简单功能的代码封装起来，让整体代码更规整一些。一般只调用一次就不再需要，所以函数名别省略了。 6.3.1 匿名函数与可迭代函数匿名函数是以关键字`lambda`开始的，写法如下：> lambda 参数1, 参数2, ...: 表达式```In [1]: r = lambda x, y: x*x + y*yIn [2]: r(2, 3)Out[2]: 13```lambda表达式可以在任何地方使用，相当于一个可以传入参数的单一表达式。```In [1]: def sum_fun(n):   ...:     return lambda x: x+n   ...:In [2]: f = sum_fun(15)In [3]: fOut[3]: .(x)>In [4]: f(5)Out[4]: 20```匿名函数本质与函数是没有两样的。匿名函数常常与可迭代函数配合使用。可迭代函数就是一种有循环迭代功能的内置函数，包括reduce、map和filter等。每个可迭代函数中都需要指定一个处理函数，习惯上使用匿名函数  6.3.2 匿名函数与reduce函数的组合应用reduce函数的功能是按照采纳数sequence中的元素顺序，来依次调用函数function，并且每次调用都会向其传入两个参数：一个是sequence序列中的当前元素，另一个是sequence序列中上一个元素在函数function中的返回值。定义如下：`reduce(function, sequence, [initail])`* function：要回调的函数。* sequence：一个“序列”类型的数据* 第三个参数可选，是一个初始值。reduce函数本质上可以算作一个内嵌循环的函数。reduce函数与匿名函数的结合使用，能够以更为简单的代码实现较为复杂的循环计算功能。```In [1]: from functools import reduceIn [2]: reduce(lambda x, y: x + y, range(1, 101))Out[2]: 5050```**reduce函数一般用于归并性任务** 6.3.3 匿名函数与map函数的组合应用**map**函数的功能：将采参数sequence内部的元素作为参数，并按照sequence序列的顺序依次调用回调函数function。具体定义如下：`map(function, sequence[, sequence, ...])`* function：要回调的函数。* sequence：一个或多个“序列”类型的数据该函数返回一个map对象。使用时，得用list或tuple等函数进行转化1. 使用map函数处理一个序列数据当map后面直接跟一个序列数据时，直接将该序列数据中的元素作为参数，一次传入前面的函数。```In [1]: t = map(lambda x: x ** 2, [1,2,3,4,5,6,7,8,9,10])In [2]: list(t)Out[2]: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]In [3]: tOut[3]: ```2. 使用map函数处理多个序列数据当前map后面直接跟多个序列数据时，处理函数的参数个数要与序列数据的个数形同。运行的时候，map内部依次提取每个序列数据中的元素，一起放到所提供的处理函数中，直到循环遍历完最短的那个序列。```In [4]: t = map(lambda x,y: x + y, [1,2,3,4,5,6], [1,2,3,4,5])In [5]: list(t)Out[5]: [2, 4, 6, 8, 10]```**map函数一般用于映射性任务** 6.3.4 匿名函数与filter函数的组合filter函数的功能是对指定序列进行过滤。1. filter函数的定义filter函数有两个输入参数：一个是filter的处理函数，另一个是待处理的序列对象。在运行时，filter函数会把序列对象中的元素一次放到filter的处理函数中。如果返回True，留下，反之就舍去。其定义如下：`filter(function or None, sequence)`* function：为filter的处理函数，在filter的内部以回调的方式被调用。返回布尔型。意味着某元素是否留下* sequence：是一个或多个序列类型的数据2. filter函数的使用filter函数的返回值是一个filter类型，需要将其转成列表或元组等序列才可以使用。```In [1]: t = filter(lambda x:x % 2 == 0, [1,2,3,4,5,6,7,8,9,10])In [2]: tOut[2]: In [3]: list(t)Out[3]: [2, 4, 6, 8, 10]```filter函数一般用于过滤性任务。 6.3.5 可迭代函数的返回值每个可迭代函数返回的值都属于一个生成器对象。生成器对象与迭代器对象用法类似，生成器对象会有一个\_\_next\_\_方法。调用该方法可以取到内部的各个值。最终\_\_next\_\_将通过StopIteration异常来停止迭代。```In [1]: t = filter(lambda x:x % 4 == 0, [1,2,3,4,5,6,7,8,9,10])In [2]: t.__next__()Out[2]: 4In [3]: t.__next__()Out[3]: 8In [4]: t.__next__()---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last) in ----> 1 t.__next__()StopIteration:In [5]: t = filter(lambda x:x % 3 == 0, [1,2,3,4,5,6,7,8,9,10])In [6]: for a in t:   ...:     print(a)   ...:369```可迭代函数的返回结果可以直接转换成列表或元组，也可以使用循环来取得。生成器对象与迭代器对象的主要区别在于：生成器对象只能迭代一次，而迭代器对象可以迭代多次。对可迭代函数的结果只能取一次。  6.4 偏函数偏函数是对原始函数的二次封装，它属于寄生在原始函数上的函数。偏函数可以理解为重新定义一个函数，向原始函数添加默认参数。1. 偏函数的定义偏函数的关键字是**partial**，其定义如下：```partial(func, *args, **kwargs)```* func：要封装的原函数* 第二个参数为一个元组或列表的解包参数：代表传入原函数的默认值（可以不指定参数名）* 第三个参数为一个字典的解包参数：代表传入原函数的默认值（指定参数名）第二个参数与第三个参数的作用是一样的，都是代表传入原函数的参数。偏函数的作用是，为其原函数指定一些默认的参数。调用该偏函数，就相当于调用了原函数，同时将默认的参数传入。在使用partial前，必须引入functools模块。2. 偏函数的使用```In [1]: from functools import partialIn [2]: def recoder(strname, age):     定义函数   ...:     print("姓名: ", strname, "年纪：", age)    函数体内容为输出语句   ...:In [3]: Garyfun = partial(recoder, strname="Gary")    定义偏函数In [4]: Garyfun(age=32)   调用偏函数姓名:  Gary 年纪： 32```偏函数的本质是：将函数式编程、默认参数和冗余参数结合在一起。通过偏函数传入的参数调用关系，与正常函数的参数调用关系是一致的偏函数通过将任意数量（顺序）的参数，转化为另一个带有剩余参数的函数对象，从而实现了截取函数功能（偏向）的效果。 6.5 递归函数递归函数就是自己调用自己的函数。在递归函数体中要有跳出函数的条件，否则会形成死循环导致堆栈溢出。 6.6 eval与exec函数**eval**与**exec**都可以执行一个字符串形式的Python代码（代码以字符串的形式提供），相当于一个Python的解释器。 6.6.1 eval与exec的区别```In [1]: exec("print(\"I Love Python \")")I Love PythonIn [2]: eval("print(\"I Love Python \")")I Love Python```**不同点1**：eval执行完要返回结果，而exec执行完不返回结果```In [1]: a = 1In [2]: exec("a = 2")In [3]: print(a)2In [4]: a = exec("2+3")In [5]: aIn [6]: a = eval('2+3')In [7]: aOut[7]: 5``` 6.6.2 eval与exec的定义对exec和eval的定义有三个参数(expression, globals=None, locals=None),具体含义如下：* expression：这个参数是一个字符串，代表要执行的语句。该语句受后面的两个字典类型参数globals与locals限制，只有在globals字典和locals字典的作用域内的函数和变量才能被执行* globals：这个参数管控的是一个全局的命名空间，即expression可以使用全局的命名空间中的函数。如果只是提供了globals这个参数，而没有提供自定义的\_\_builtins\_\_，则系统会将当前环境中的\_\_builtins\_\_复制到自己提供的globals中，然后才会进行计算；如果连globals这个参数都没有被提供，则使用python的全局命名空间。* locals：这个参数管控的是一个局部的命名空间，和globals类似，当它和globals中没有重复或冲突时，以locals的为准。如果locals没有被提供，则默认为globals。1. exec与eval中的globals参数作用```In [1]: dic = {}In [2]: dic['b'] = 3In [3]: dic.keys()Out[3]: dict_keys(['b'])In [4]: exec("a = 4", dic)In [5]: dic.keys()Out[5]: dict_keys(['b', '__builtins__', 'a'])```2. 了解内建模块\_\_builtins\_\_\_\_builtins\_\_是Python的内建模块，例如平时使用的int、str、abs等都在这个模块中。```In [1]: dic = {}In [2]: dic['b'] = 3In [3]: dic.keys()Out[3]: dict_keys(['b'])In [4]: exec("a = 4", dic)In [5]: dic.keys()Out[5]: dict_keys(['b', '__builtins__', 'a'])In [6]: dic['__builtins__']    打印__builtins__的value取出Out[6]:{'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': _frozen_importlib.BuiltinImporter, '__spec__': ModuleSpec(name='builtins', loader=), '__build_class__': , '__import__': , 'abs': , 'all': , 'any': , 'ascii': , 'bin': , 'breakpoint': , 'callable': , 'chr': , 'compile': , 'delattr': , 'dir': , 'divmod': , 'eval': , 'exec': , 'format': , 'getattr': , 'globals': , 'hasattr': , 'hash': , 'hex': , 'id': , 'input': , 'isinstance': , 'issubclass': , 'iter': , 'len': , 'locals': , 'max': , 'min': , 'next': , 'oct': , 'ord': , 'pow': , 'print': , 'repr': , 'round': , 'setattr': , 'sorted': , 'sum': , 'vars': , 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': bool, 'memoryview': memoryview, 'bytearray': bytearray, 'bytes': bytes, 'classmethod': classmethod, 'complex': complex, 'dict': dict, 'enumerate': enumerate, 'filter': filter, 'float': float, 'frozenset': frozenset, 'property': property, 'int': int, 'list': list, 'map': map, 'object': object, 'range': range, 'reversed': reversed, 'set': set, 'slice': slice, 'staticmethod': staticmethod, 'str': str, 'super': super, 'tuple': tuple, 'type': type, 'zip': zip, '__debug__': True, 'BaseException': BaseException, 'Exception': Exception, 'TypeError': TypeError, 'StopAsyncIteration': StopAsyncIteration, 'StopIteration': StopIteration, 'GeneratorExit': GeneratorExit, 'SystemExit': SystemExit, 'KeyboardInterrupt': KeyboardInterrupt, 'ImportError': ImportError, 'ModuleNotFoundError': ModuleNotFoundError, 'OSError': OSError, 'EnvironmentError': OSError, 'IOError': OSError, 'WindowsError': OSError, 'EOFError': EOFError, 'RuntimeError': RuntimeError, 'RecursionError': RecursionError, 'NotImplementedError': NotImplementedError, 'NameError': NameError, 'UnboundLocalError': UnboundLocalError, 'AttributeError': AttributeError, 'SyntaxError': SyntaxError, 'IndentationError': IndentationError, 'TabError': TabError, 'LookupError': LookupError, 'IndexError': IndexError, 'KeyError': KeyError, 'ValueError': ValueError, 'UnicodeError': UnicodeError, 'UnicodeEncodeError': UnicodeEncodeError, 'UnicodeDecodeError': UnicodeDecodeError, 'UnicodeTranslateError': UnicodeTranslateError, 'AssertionError': AssertionError, 'ArithmeticError': ArithmeticError, 'FloatingPointError': FloatingPointError, 'OverflowError': OverflowError, 'ZeroDivisionError': ZeroDivisionError, 'SystemError': SystemError, 'ReferenceError': ReferenceError, 'MemoryError': MemoryError, 'BufferError': BufferError, 'Warning': Warning, 'UserWarning': UserWarning, 'DeprecationWarning': DeprecationWarning, 'PendingDeprecationWarning': PendingDeprecationWarning, 'SyntaxWarning': SyntaxWarning, 'RuntimeWarning': RuntimeWarning, 'FutureWarning': FutureWarning, 'ImportWarning': ImportWarning, 'UnicodeWarning': UnicodeWarning, 'BytesWarning': BytesWarning, 'ResourceWarning': ResourceWarning, 'ConnectionError': ConnectionError, 'BlockingIOError': BlockingIOError, 'BrokenPipeError': BrokenPipeError, 'ChildProcessError': ChildProcessError, 'ConnectionAbortedError': ConnectionAbortedError, 'ConnectionRefusedError': ConnectionRefusedError, 'ConnectionResetError': ConnectionResetError, 'FileExistsError': FileExistsError, 'FileNotFoundError': FileNotFoundError, 'IsADirectoryError': IsADirectoryError, 'NotADirectoryError': NotADirectoryError, 'InterruptedError': InterruptedError, 'PermissionError': PermissionError, 'ProcessLookupError': ProcessLookupError, 'TimeoutError': TimeoutError, 'open': , 'copyright': Copyright (c) 2001-2020 Python Software Foundation. All Rights Reserved. Copyright (c) 2000 BeOpen.com. All Rights Reserved. Copyright (c) 1995-2001 Corporation for National Research Initiatives. All Rights Reserved. Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam. All Rights Reserved., 'credits':     Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands     for supporting Python development.  See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object., '__IPYTHON__': True, 'display': , 'get_ipython': >}```3. 演示exec与eval中作用域```In [1]: dic = {}    定义一个字典In [2]: a = 2In [3]: exec("a = 4", dic)    在exec执行的语句后面跟一个作用域dicIn [4]: a    打印a的值Out[4]: 2In [5]: dic['a']    其实执行语句所生成的变量是在dic中。Out[5]: 4```上面的代码表明exec执行的语句中，赋值的变量a并不是全局的变量a，而是新生成了一个a，存放在dic中。```In [1]: dic = {}In [2]: dic['a'] = 3    往字典中加个d键值对b: 3In [3]: dic['b'] = 4    往字典中加个d键值对b: 4In [4]: result = eval('a+b', dic)    让dic内部的a与b对应的值相加，结果赋给resultIn [5]: resultOut[5]: 7```4. exec与eval的locals参数作用```In [4]: g = {'a': 6, 'b':8}In [5]: t = {'b': 100, 'c': 10}In [6]: eval('a+b+c', g, t)Out[6]: 116``` 6.6.3 exec和eval的使用经验使用exec和eval时一定要记住：里面的第一个参数是字符串，而字符串的内容一定要是可执行的代码。1. exec与eval的可接受打印字符串```In [1]: s = "hello"In [2]: eval('s')Out[2]: 'hello'```上述写法含义是要eval执行“hello”这句代码。这个hello是有引号的，代码中，代表字符串的意思，可以执行。等同于下列代码```In [3]: s = '"hello"'In [4]: eval(s)Out[4]: 'hello'```2. repr函数在exec与eval函数中的作用repr函数可以不改变原有字符串的写法```In [1]: s = "hello"In [2]: eval(repr(s))Out[2]: 'hello'```repr函数与str函数对字符串s的转化区别```In [1]: s = "hello"In [2]: repr(s)Out[2]: "'hello'"In [3]: str(s)Out[3]: 'hello'In [4]: print(repr(s))'hello'In [5]: print(str(s))hello``` 6.7 案例：批量测转化函数<jupyter_code># 实现两个功能
# 编写测试用例
# 进行批量调用

def string_to_other(a):  # 组合对象类型转换成string
    b = eval(a)
    return b

def other_to_string(b):  # string转化成组合对象类型
    a = str(b)
    return a


# 编写单元测试用例
def fun1():  # 字符串与列表相互转换
    a = "[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]"
    b = string_to_other(a)
    print(b)
    print(other_to_string(b))

def fun2():   # 字符串与字典相互转换
    a = "{1: 'a', 2: 'b'}"
    b = string_to_other(a)
    print(b)
    print(other_to_string(b))

    
def fun3():  # 字符串与元组相互转换
    a = "((1, 2), (3, 4), (5, 6), (7, 8), (9, 0))"
    b = string_to_other(a)
    print(b)
    print(other_to_string(b))

if __name__ == '__main__':
    print('********** fun1 **********')
    fun1()
    print('********** fun2 **********')
    fun2()
    print('********** fun3 **********')
    fun3()
    print('********** 批量运行单元测试 **********')
    s = ""
    for i in range(1, 4):
        s += 'fun' + str(i) + '()\n'
    exec(s)<jupyter_output>********** fun1 **********
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
********** fun2 **********
{1: 'a', 2: 'b'}
{1: 'a', 2: 'b'}
********** fun3 **********
((1, 2), (3, 4), (5, 6), (7, 8), (9, 0))
((1, 2), (3, 4), (5, 6), (7, 8), (9, 0))
********** 批量运行单元测试 **********
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 0]]
{1: 'a', 2: 'b'}
{1: 'a', 2: 'b'}
((1, 2), (3, 4), (5, 6), (7, 8), (9, 0))
((1, 2), (3, 4), (5, 6), (7, 8), (9, 0))<jupyter_text>6.8 生成器函数 6.8.1 生成器与迭代器的区别* 迭代器是所有的内容都在内存里，使用next函数来依次往下遍历* 生成器不会把内容放到内存里，每次调用next函数时，返回的都是本次计算出来的那个元素，用完之后立刻销毁。当整个序列占用内存特别大时，使用生成器对象会节约内存；当系统的运算资源不足时，使用迭代器对象会节约运算资源。二者在应用上的区别属于“时间优先”与“空间优先”之间区别。 6.8.2 生成器函数生成器函数（Generator）是一种特殊的函数，是用来创建生成器对象的工具。生成器函数使用yield语句返回，返回值是一个生成器对象。```In [1]: def Reverse(data):   ...:     for idx in range(len(data)-1, -1, -1):   ...:         yield data[idx]    使用yield返回具体的一个元素   ...:In [2]: for c in Reverse('Python'):  使用for循环来迭代Reverse返回的生成器对象   ...:     print(c, end=' ')   ...:n o h t y P``` 6.8.3 生成器表达式生成器还有一个简单的写法，就是用生成器表达式。它适合一句代码的语句。生成器表达式与“for循环列表推导式”写法很像，也是使用ffor循环来实现的。不同的是“for循环列表推导式”外层是方括号，而生成器表达式外层是圆括号。```In [1]: mylist = [x * x for x in range(3)]In [2]: for i in mylist:   使用for循环将其打印出来   ...:     print(i)   ...:014```  6.9 变量的作用域Python为变量分配了4个作用域，代表程序中出现同名变量的有效范围* L：本地作用域，被当前函数包括* E：上一层结构中def或lambda的本地作用域（其实就是函数嵌套的情况）* G：全局作用域，不被任何函数包括。* B：内置作用域，是Python内部的命名空间这几个作用域的优先级由高到低排列，依次为 L->E->G->B ,俗称LEGB原则。意思是：如果代码里用到了某个变量，系统内部会按照LEGB的顺序去不同的作用域里面找变量a，并在第一个能够找到变量名的地方停下来；如果在这4个作用域中都没找到，Python会报错。 6.9.1 作用域前三个作用（LEG)是分布在用户代码中，是在自己的工程代码中可以看到。```var = 1    在全局作用域G中定义个变量var，值为1def fun1():       在定义一个函数fun1，其内部的函数体都属于作用域E    def fun2():    在函数fun1中定义一个函数fun2，其内部的函数体都属于作用域L        var = 3   在L中定义一个变量var，值为3        print(var)    输出var    var = 2    在E中定义变量var    fun2()     在函数fun1中调用了fun2fun1()   调用fun1，最终会进入fun2，fun2中的var生效，输出3```去掉上述代码fun2中的变量定义varr```In [1]: var = 1In [2]: def fun1():   定义一个函数fun1，其内部的函数体都属于作用域E   ...:     def fun2():    在函数fun1中定义一个函数fun2，其内部的函数体都属于作用域L   ...:         print(var)   输出var   ...:     var = 2    在E中定义变量var   ...:     fun2()     在函数fun1中调用了fun2   ...:In [3]: fun1()2``` 6.9.2 global语句如果在本地作用域L或嵌套作用域E下，相对全局作用域G的变量进行赋值等操作，可以使用global语句```In [1]: a = 6In [2]: def func():   定义函数   ...:     global a   获取全局变量a   ...:     a = 5   ...:     print(a)   ...:In [3]: func()5In [4]: print(a)```如果不加global```In [5]:  a = 6In [6]: def func():   ...:     a = 5   ...:In [7]: func()In [8]: print(a)6``` 6.9.3 nonlocal语句global语句是可以引用全局的优先级变量。类似global语句，nonlocal可以引用比其优先级低的作用下的变量。如果在本地作用域L或嵌套作用域E下，相对全局作用域G的变量赋值等操作，可以使用global语句：而使用nonlocal语句，则会本地作用域以外按照优先级的顺序逐级去寻找声明的变量，并引用该变量。<jupyter_code>a = 6  # 全局变量a为6
def func():  # 定义函数
    a = 7   # 定义嵌套作用域下的变量a
    def nested():   # 内嵌函数nested
        nonlocal a  # 使用nonlocal关键字，引用外层的a
        a += 1  # 对a进行加1操作，改变a的值
    nested()   # 调用函数nested
    print("本地：", a)
func()
print("全局：", a)<jupyter_output>本地： 8
全局： 6<jupyter_text>6.10 工厂函数工厂函数某种程序上实现了面向对象思想的编程方法，它可以让代码更具有层次。 6.10.1 普通工厂函数的是实现利用作用域的知识来实现一个偏函数的功能。<jupyter_code>def recoder(strname, age):  # 定义函数
    print("姓名：", strname, "年纪：", age)  # 函数的内容为一句代码，实现将指定内容输出


def Garyfun(age):      # 实现了偏函数的功能
    strname = "Gary"   # 定义了本地作用域下的变量
    return recoder(strname, age)   # 直接将固定的变量传入
Garyfun(age=32)<jupyter_output>姓名： Gary 年纪： 32<jupyter_text>6.10.2 闭合函数（closure）闭合函数又叫做闭包函数，本质上与普通工厂函数的编程思想一致，是普通工厂函数的更优形式，由自由变量与嵌套函数组成。1. 闭合函数的实现闭合函数实现方法：将名字作为自由变量，将原有的recoder函数作为嵌套函数，通过一次函数的封装即可实现前面的功能<jupyter_code>def wrapperfun(strname):   # 闭合函数，strname为自由变量
    def recoder(age):   # 定义一个嵌套函数recoder
        print("姓名；", strname, "年纪：", age)
    return recoder  # 返回recoder函数

fun = wrapperfun('Anna')
fun(37)
fun2 = wrapperfun('Gary')
fun2(32)<jupyter_output>姓名； Anna 年纪： 37
姓名； Gary 年纪： 32<jupyter_text>闭合函数wrapperfun中实现了一个嵌入函数recoder，并将嵌入函数recoder返回。嵌入函数recoder中调用了外部变量strname，这个strname是由wrapperfun被调用时传入的参数。当wrapperfun被调用后，返回自身嵌入recoder的同时，又将自身的参数strname与内嵌recoder绑定起来，赋值给了fun。fun就等同一个strname被初始化后的recoder函数。此时的fun可以理解为一个recoder的闭合函数。2. 闭合函数的\_\_closure\_\_属性闭合函数会比普通的函数多一个属性**\_\_closure\_\_** ，该属性会记录着自由变量的参数对象地址。当闭合函数被调用时，系统就会根据地址找到自由变量，完成整体的函数调用。<jupyter_code>def wrapperfun(strname):   # 闭合函数，strname为自由变量
    def recoder(age):   # 定义一个嵌套函数recoder
        print("姓名；", strname, "年纪：", age)
    return recoder  # 返回recoder函数

fun = wrapperfun('Anna')
fun(37)
fun2 = wrapperfun('Gary')
fun2(32)
print(fun.__closure__)
print(fun2.__closure__)<jupyter_output>姓名； Anna 年纪： 37
姓名； Gary 年纪： 32
(<cell at 0x000001B0F0E2F370: str object at 0x000001B0F0D712B0>,)
(<cell at 0x000001B0F0D622B0: str object at 0x000001B0F0D8D830>,)<jupyter_text>上述代码执行的结果可以看出，显示的内容是一个字符串对象，这个对象就是fun与fun2中自由变量strname的初始值。\_\_closure\_\_ 属性的类型是一个元组，表明闭合函数可以支持多个自由变量的形式。 6.10.3 装饰器（decorator）1. 装饰器的实现装饰器的主要作用就是在扩展原有功能基础上，最大化地使用已有代码。也可以理解成：在不改变原有代码实现的基础上，添加新的实现功能。装饰器的实现方法是：在原有的函数外面再包装一层函数，使新函数的再返回原有的函数之前实现一些其他的功能。<jupyter_code>def checkParams(fn):   # 装饰器函数，参数是要被装饰的函数。相当于闭合函数
    def wrapper(strname):  # 定义检查参数的函数
        if isinstance(strname, str): # 判断是否是字符串类型
            return fn(strname)  # 如是，则调用fn(strname)返回计算结果
        print("variable strname is not a string type") # 如果参数不符合条件，则打印警告，然后退出
        return
    return wrapper  # 将装饰后的函数返回

def wrapperfun(strname):  # 闭合函数，strname为自由变量
    def recoder(age):  # 定义一个嵌套函数recoder
        print("姓名：", strname, "年纪：", age)  # 函数的内容为一句代码，实现将指定内容输出
    return recoder  # 返回recoder

wrapperfun2 = checkParams(wrapperfun) # 对wrapperfun进行装饰，即，将自由变量设为wrapperfun函数
fun = wrapperfun2('anna')
fun(37)
fun = wrapperfun2(37)<jupyter_output>姓名： anna 年纪： 37
variable strname is not a string type<jupyter_text>2. 装饰器的本质与用途装饰器本质是一个闭合函数，该闭合函数的自由变量是一个函数。它的存在可以是代码的重用性与扩展性大大加强。 6.10.4 @修饰符装饰器的另外一种写法——使用@修饰符@修饰符的作用是，再定义原函数时就可以为其指定修饰器函数。好处：使修饰器与被修饰函数的关系更加明显，也使得需要修饰的函数再第一时间得到修饰，降低了编码出错的可能性。@修饰符的语法：在@后面的添加修饰器函数，同时在其下一行添加被修饰函数的定义。<jupyter_code>def checkParams(fn): # 装饰器函数，参数是要被装饰的函数
    def wrapper(strname):  # 定义一个检查参数的函数
        if isinstance(strname, (str)): # 判断是否是字符串类型
            return fn(strname) # 如果是，则调用fn(strname)返回计算结果
        print("variable strname is not a string type")  # 如果参数不符合条件，打印警告
        return
    return wrapper # 装饰后的函数返回

@checkParams     # 使用@修饰符实现对wrapperfun的修饰
def wrapperfun(strname):  # 闭合函数，strname为自由变量
    def recoder(age):  # 定义一个嵌套函数recoder
        print("姓名：", strname, "年纪：", age)  # 函数的内容为一句代码，实现将指定内容输出
    return recoder   # 返回recoder函数

fun = wrapperfun('Anna')  # wrapperfun为带有参数检查的闭合函数
fun(37)
fun = wrapperfun(37)<jupyter_output>姓名： Anna 年纪： 37
variable strname is not a string type<jupyter_text>使用一个@符合，就可以把装饰器与被修饰函数的关系简洁地体现出来。 6.10.5 更高级的装饰器1. 能够接收任何参数的通用参数装饰器在上面的示例代码中，装饰器checkParams的内部实现了一个wrapper函数。wrapper函数的参数必须与被装饰函数参数一样，这样才能够实现对被装饰函数的参数检查，并将输入参数透传给装饰函数。为了在定义装饰器时，解耦内部wrapper的参数对于被装饰函数参数的强依赖关系，可以使用能够适应任何参数的通用参数装饰器。<jupyter_code>def checkParams(fn): # 定义通用参数装饰器函数
    def wrapper(*arg, **kwargs):  # 使用字典和元组的解包参数来作形参
        if isinstance(arg[0], (str)): # 判断第一个参数是否是字符串类型
            return fn(*arg, **kwargs)  # 如满足条件，则将参数传递给原函数，并返回
        print("variable strname is not a string type") # 如果参数不符合条件，则打印警告
        return
    return wrapper

fun = wrapperfun('Anna')  # wrapperfun为带有参数检查的闭合函数
fun(37)
fun = wrapperfun(37)<jupyter_output>姓名： Anna 年纪： 37
variable strname is not a string type<jupyter_text>2. 可接收参数的通用装饰器通用参数装饰器，解决了装饰器参数与被装饰函数间的强关联关系。为了让装饰器有更大的通用性，还可以通过在装饰器的外部传入参数的方式，来告诉装饰器当时使用的外部情况。这样，装饰内部就可以通过传进来的外部变量来选择不同的执行分支，从而可以适应更多的调用场景。可接收参数的通用装饰器，需要在原有的通用参数装饰外面再封装一层函数，这个函数的参数就是用来接收外部变量的。<jupyter_code>def isadmin(userid):  # 可以接收参数的装饰函数
    def checkParams(fn):  # 用通用参数来装饰器函数
        def wrapper(*arg, **kwargs):  # 定义一个检查参数函数
            if userid != 'admin': # 对外部调用环境进行判断，如不是admin，则返回
                print('Operation is prohibited as you are not admin!')
                return
            if isinstance(arg[0], (str)):  # 判断是否是字符串类型
                return fn(*arg, **kwargs)  # 满足条件，将参数透传给原函数，并返回
            print("variable strname is not a string type") # 如果条件不符合，则打印警告
            return
        return wrapper # 将装饰后的函数返回
    return checkParams

@isadmin(userid='admin') # 在admin模块中，传入admin身份到装饰器
def wrapperfun(strname): # 闭合函数，strname为自由变量
    def recoder(age):  # 定义一个嵌套函数recoder
        print("姓名：", strname, "年纪：", age)
    return recoder

@isadmin(userid='user')
def wrapperfun2(strname):
    def recoder(age):
        print("姓名：", strname, "年纪：", age)
    return recoder

fun = wrapperfun('anna')
fun(37)
fun = wrapperfun(37)
fun = wrapperfun2(37)<jupyter_output>姓名： anna 年纪： 37
variable strname is not a string type
Operation is prohibited as you are not admin!<jupyter_text>假设外部情况有两个模块：一个是admin，另一个是user。在上面的代码中，在通用参数装饰器checkParams外部添加一层封装，变为isadmin装饰器。该装饰器的作用是检查调用者的身份，只允许身份为admin的用户调用。在admin中实现了函数wrapperfun，在user中实现了wrapperfun2，两个函数都用isadmin来装饰。在实际调用时，wrapperfun可以正常使用，但是wrapperfun2在调用时就会显示“Operation is prohibited as you are not admin!信息。原因是：isadmin装饰器中对传入的外部参数进行了判断，只有admin才会正常运行。下面两个代码实现了同样的功能：一个使用了@注解的方法定义装饰器；另一个使用了普通的方法定义装饰器。（1）@注解后面跟着可接收参数的装饰器的定义代码：```@isadmin(userid='user')   在user模块中，传入user身份到装饰器def wrapperfun2(strname):   闭合函数，strname为自由变量    def recoder(age):        print("姓名", strname, "年纪：", age)    return recoder```（2）普通装饰器的定义代码：```In [2]: def isadmin(userid):   ...:     def checkParams(fn):   ...:         def wrapper(*arg, **kwargs):   ...:             if userid != 'admin':   ...:                 print("Operation is prohibited as you are not admin!")   ...:                 return   ...:             if isinstance(arg[0], (str)):   ...:                 return fn(*arg, **kwargs)   ...:             print("Variable strname is not a string type")   ...:             return   ...:         return wrapper   ...:     return checkParams   ...:In [3]: def wrapperfun2(strname):   ...:    def recoder(age):   ...:        print("姓名：", stname, "年纪：", age)   ...:    return recoder   ...:In [4]: wrapperfun2 = isadmin(userid='user')(wrapperfun2)In [5]: print(wrapperfun2.__name__)wrapper```3.装饰器返回函数的名称修复当函数被装饰完后，对函数的名字属性再赋值一次，将函数的名称恢复过来。这样就可以避免出现装饰完后函数名字变化的现象。<jupyter_code>def isadmin(userid):  # 可以接收参数的装饰器函数
    def checkParams(fn): # 通用参数装饰器函数
        def wrapper(*arg, **kwargs):  # 定义一个检查参数的函数
            if userid != 'admin':
                print("Operation is prohibited as you are not admin!")
                return
            if isinstance(arg[0], (str)):
                return fn(*arg, **kwargs)
            print("Variable strname is not a string type")
            return
        wrapper.__name__ = fn.__name__  # 将哈桑农户名称属性恢复
        return wrapper
    return checkParams

def wrapperfun2(strname):
    def recoder(age):
        print("姓名：", stname, "年纪：", age)
    return recoder

wrapperfun2 = isadmin(userid='user')(wrapperfun2)
print(wrapperfun2.__name__)<jupyter_output>wrapperfun2<jupyter_text>在上面的代码中的倒数第三行，是新加的语句，其意义是：将被装饰的函数名称赋给wrapper函数。这样，装饰器返回的wrapper函数名称，就与被装饰的函数名称一致了。Python中提供了一个内置的装饰器函数functools.wraps，它的作用是将被装饰的函数名称还原并赋值给装饰后的返回函数。<jupyter_code>import functools

def isadmin(userid):
    def checkParams(fn):
        @functools.wraps(fn)
        def wrapper(*arg, **kwargs):
            if userid != 'admin':
                print("Operation is prohibited as you are not admin!")
                return
            if isinstance(arg[0], (str)):
                return fn(*arg, **kwargs)
            print("variable strname is not a string type")
            return
        return wrapper  # 将装饰后的函数返回
    return checkParams

def wrapperfun2(strname):
    def recoder(age):
        print("姓名：", strname, "年纪：", age)
    return recoder

wrapperfun2 = isadmin(userid='user')(wrapperfun2)
print(wrapperfun2.__name__)<jupyter_output>wrapperfun2<jupyter_text>4. 组合装饰——多个装饰器联合使用在实际的开发中，可以将具有不同功能的装饰器合起来用，以实现组合装饰的效果<jupyter_code>def checkParmas(fn):
    def wrapper(*arg, **kwargs):
        if isinstance(arg[0], (str)):
            return fn(*arg, **kwargs)
        print("variable strname is not a string type")
        return
    return wrapper

def logging(userid):
    def checkParams(fn):
        def wrapper(*arg, **kwargs):
            print(userid, end=" : ")
            return fn(*arg, **kwargs)
        return wrapper
    return checkParams

@logging(userid='admin')
@checkParams
def wrapperfun(strname):
    def recoder(age):
        print("姓名：", strname, "年纪：", age)
    return recoder

fun = wrapperfun('anna')
fun(37)<jupyter_output>admin : 姓名： anna 年纪： 37<jupyter_text>上述代码中，实现了两个装饰器——checkParams和logging。使用装饰器，可以将代码按照要实现功能的主次逐层分开，使代码更有条理。将核心功能与参数检查、外围交互等功能逐层分开的思想，也大大降低了代码的复杂度。这就是**面向切面的编程思想(Aspect Oriented Program, AOP)**5. 多装饰器的调用顺序多装饰器的载入顺序是从下往上的，在调用时，执行的函数顺序时从上往下的。<jupyter_code>def logging(fn):
    print("in logging")
    def wrapper_logging(*args, **kwargs):
        print('in wrapper_logging')
        return fn(*args, **kwargs)
    return wrapper_logging

def checkParams(fn):
    print('in checkParams')
    def wrapper_checkParams(*args, **kwargs):
        print('in wrapper_checkParmas')
        return fn(*args, **kwargs)
    return wrapper_checkParams

@logging
@checkParams
def wrapperfun(strname):
    print("姓名：", strname)

wrapperfun('Anna')<jupyter_output>in checkParams
in logging
in wrapper_logging
in wrapper_checkParmas
姓名： Anna<jupyter_text>6.10.6 解决“同作用域下默认参数被覆盖”问题在使用工厂函数时，常会配合循环来批量生成多个函数。这里有个特例值得注意：如果通过循环来生成工厂函数，在循环过程中，该循环体作用域下的默认参数会被循环值所覆盖。这种情况会导致，所有在这个循环中产生的函数都将会有相同的默认值。<jupyter_code>def recoder(strname, age): # 定义一个函数recoder
    print("姓名：", strname, "年纪：", age)

def makerecoders():  # 该函数的作用是，批量生成工厂函数
    acts = []
    for i in ["Gary", "Anna"]: # 通过循环将列表中的元素作为默认值，依次生成工厂函数
        acts.append(lambda age: recoder(i, age)) # 将每个生成的函数放在列表中
    return acts

for a in makerecoders():  # 调用函数批量生成工厂函数，并使用for遍历每个函数
    a(age=32) # 将每个函数取出，依次调用<jupyter_output>姓名： Anna 年纪： 32
姓名： Anna 年纪： 32<jupyter_text>上面实例中，通过for循环遍历列表来批量生成工厂函数。其中，列表里的值为工厂函数的值。在实际调用过程中，只有列表中最后的一个元素的值起到了默认值的作用，前面的元素都没有生效。这是因为循环时后面的变量覆盖了前面的变量。为了避免这种情况的发生，在循环生成工厂函数的过程中，就不能将默认值放到作用域空间来存储，必须将默认值当作参数传入到原函数recoder中。<jupyter_code>def recoder(strname, age): # 定义一个函数recoder
    print("姓名：", strname, "年纪：", age)

def makerecoders():  # 该函数的作用是，批量生成工厂函数
    acts = []
    for i in ["Gary", "Anna"]: # 通过循环将列表中的元素作为默认值，依次生成工厂函数
        acts.append(lambda age,i=i: recoder(i, age)) # 将每个生成的函数放在列表中
    return acts

for a in makerecoders():  # 调用函数批量生成工厂函数，并使用for遍历每个函数
    a(age=32) # 将每个函数取出，依次调用<jupyter_output>姓名： Gary 年纪： 32
姓名： Anna 年纪： 32<jupyter_text>这次在函数makerecoders中的for循环内，将循环值i也作为参数传入匿名函数里。避免了本地作用域下的变量在循环中会被覆盖的问题，实现了正确的功能。  7. 错误与异常 7.1 错误的分类编程程序过程中遇到的错误都分为两类：语法错误与运行时错误。 7.1.1 语法错误语法错误，也就是在解析时所出现错误。当代码不符合Python语法规则时，在解析过程中会报SyntaxError。报错的同时会显示出错的那一行，并且用小箭头指出最早探测到错误的位置。```print "hello"``` 7.1.2 运行时错误运行时错误，即语句或表达式在语法上都是正确的，但在运行时发生了错误。```a = 1/0   让1除以0，结果赋值给a```上面这句代码的意思是“用1除以0，并赋给a”。因为0作被除数是没有意义的，所以运行后会产生如下错误。```---------------------------------------------------------------------------ZeroDivisionError                         Traceback (most recent call last) in ----> 1 a = 1/0ZeroDivisionError: division by zero```上面显示的内容中：前两段是错误的位置；最后一句是出错的类型。在Python中，会把这种运行产生错误的情况叫做异常（Exceptions）。这种异常情况还有很多。例如```In [1]: a = 3 + t---------------------------------------------------------------------------NameError                                 Traceback (most recent call last) in ----> 1 a = 3 + tNameError: name 't' is not defined```这种异常就是属于NameError类型的异常。上述示例中的NameError与前面的ZeroDivisionError都属于内置的异常名称。Python中内建了许多类似NameError的全局标识符，用来规范标准异常类型的命名。当一个程序发生异常时，代表该程序在执行时出现了非正常的情况，无法在执行下去。默认情况下，程序是终止的。为了避免程序退出，可以使用捕获异常的方式获取这个异常的名称，在通过其他的逻辑代码让程序继续运行。这种根据异常做出的逻辑处理焦作异常处理。 7.2 异常的基本语法Python语法会异常当作一个对象，通过try/except语句来捕捉该异常对象。try/except语句后面都会跟着对应的代码块。系统会在在try对应的代码块中内置检测错误的代码。当检测到错误时，就会进入except代码块，来执行相应的逻辑处理，以决定是否继续运行。1. 异常的定义异常处理的语法定义如下：```try:        运行别的代码except :        如果在try部分引发了'name'异常except , :        如果引发了'name'异常，获得附加的数据else:        如果没有异常发生，则执行该分支语句```2. 异常的使用举例一个try语句可以有多条except语句，用以指定不同的异常，但至多只有一个会被执行<jupyter_code>try:
    x = int(input("请输入一个被除数："))  # 等待输入一个数
    print("30除以", x, "等于", 30 / x)
except ValueError:    # 捕获ValueError
    print("输入了无效的整数。重新输入...")
except ZeroDivisionError:   # 捕获ZeroDivisionError异常
    print("被除数不等于0，重新输入....")
except:
    print("其他异常...")<jupyter_output>请输入一个被除数：0
被除数不等于0，重新输入....<jupyter_text>7.2.1 同时处理多个异常except关键字还可以同时接收多个异常。具体的写法是在except后面加个括号，将要接收的异常当作参数传入。<jupyter_code>try:
    x = int(input("请输入一个被除数：")) # 等待输入一个数，并赋值给x
    print("30除以", x, "等于", 30/x)
except (ZeroDivisionError, ValueError): # 同时捕获ZeroDivisionError与ValueError异常
    print("输入错误，请重新输入....")
except:
    print("其他异常...")<jupyter_output>请输入一个被除数：0
输入错误，请重新输入....<jupyter_text>7.2.2 异常处理中的else语句try...except...语句后面还可以跟else语句。当没有异常发生时，将执行else语句。else语句是个可选语句，必须要放在所有except语句后面。<jupyter_code>try:
    x = int(input("请输入一个被除数：")) # 等待输入一个数，并赋值给x
    print("30除以", x, "等于", 30/x)
except (ZeroDivisionError, ValueError): # 同时捕获ZeroDivisionError与ValueError异常
    print("输入错误，请重新输入....")
except:
    print("其他异常...")
else:
    print("再见")<jupyter_output>请输入一个被除数：6
30除以 6 等于 5.0
再见<jupyter_text>7.2.3 输出未知异常上述的一些示例中，直接捕获了指定的异常类型。是因为程序比较简单，可以预知会发生哪些异常。如果在一些复杂的程序中，则往往很难预测有什么异常情况发生。这时就可以使用下面语法输出未知异常。```try:    ...do something...except Exception as e:    print(e)```<jupyter_code># 示例
try:
    x = int(input("请输入一个被除数：")) # 等待输入一个数，并赋值给x
    print("30除以", x, "等于", 30/x)
except Exception as e:
    print(e)
    print("其他异常...")<jupyter_output>请输入一个被除数：0
division by zero
其他异常...<jupyter_text>7.2.4 输出异常的详细信息在实际调试程序的时候，只获得异常的类型时远远不够的。有时必须借助更加详细的异常信息，辅助开发人员才能解决问题。1. sys的exc_info函数模块sys中有两个函数可以返回异常的全部信息：一个是**exc_info**；另一个是**last_traceback**。两个函数有相同的用法及功能。exc_info函数会将当前的异常信息以元组的类型返回。元组内包含3个元素，分别为type、value和traceback。* type：异常类型的名称，它是BaseException的子类* Value：捕获到的异常实例* traceback：是一个traceback对象<jupyter_code># 示例
import sys
try:
    x = int(input("请输入一个被除数：")) # 等待输入一个数，并赋值给x
    print("30除以", x, "等于", 30/x)
except:
    print(sys.exc_info())
    print("其他异常...")<jupyter_output>请输入一个被除数：0
(<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero'), <traceback object at 0x00000257810B5940>)
其他异常...<jupyter_text>输出结果的第2行为异常的全部信息。该信息是一个元组类型，第1个元素是ZeroDivisionError类；第2个元素是异常类ZeroDivisionError类的一个实例；第3个元素为一个traceback对象。通过这种输出，不仅可以看到异常类型的信息，还可以从traceback对象里面找到异常位置的调用堆栈信息。2. traceback对象的显示要查看traceback对象的内容，需要先引入traceback模块，调用traceback模块中的print_tb方法，并将sys.exc_info输出的traceback对象传入。print_tb方法专用于显示traceback对象的内容。<jupyter_code>import traceback
import sys

try:
    x = int(input("请输入一个被除数："))
    print("30除以", x, "等于", 30/x)
except:
    traceback.print_tb(sys.exc_info()[2])
    print("其他异常...")
else:
    print("再见")<jupyter_output>请输入一个被除数：0
其他异常...<jupyter_text>这次程序输出了更多的异常信息，包括文件名、行数、模块名、代码3. traceback模块的其他函数通过traceback模块的载入，可以更简单地获得更多异常信息显示。比u调用traceback对象的print_exc方法，可以直接将异常内容打印出来。<jupyter_code>import traceback

try:
    x = int(input("请输入一个被除数："))
    print("30除以", x, "等于", 30/x)
except:
    traceback.print_exc()
    print("其他异常...")
else:
    print("再见")<jupyter_output>请输入一个被除数：0
其他异常...<jupyter_text>7.3 捕获与处理异常 7.3.1 异常的处理流程try语句在执行时，如果出现了一个异常，该语句的剩余部分将被跳过；如果该异常的类型匹配到了except后面的异常名，则该except后的语句将被执行。**注意**：如果except后面都没有跟异常名，但必须放在所有捕获异常的语句之后，表示它可以匹配任何类型的异常 7.3.2 try语句的工作原理try语句的工作原理如下：（1）当执行一个try语句后，Python就在当前程序的上下文中做标记（2）如果程序出现异常，系统就会回到标记处。接着执行第一个匹配该异常的except子句。异常处理完毕，控制流就跳过整个try语句（除非在处理异常时又引发新的异常。（3）如果程序出现异常，但没有找到相匹配的except子句。异常将被递交到上层的try或着程序的最上层（这样将结束程序，并打印默认的出错信息）（4）如果try子句在执行时没有发生异常，Python将执行else后的语句（如果有else的话），然后控制流跳过整个try语句。 7.3.3 一些常见的异常| 异常名称 | 描述 || :--- | :--- || BaseException | 所有异常的基类 || SystemExit | 解释器请求退出 || KeyboardInterrupt | 用户中断执行（通常是输入^C） || Exception | 常规错误的基类 || StopIteration | 迭代器没有更多的值 || GeneratorExit | 生成器（generator）发生异常来通知退出 || StandardError | 所有的内建标准异常的基类 || ArithmeticError | 所有数值计算错误的基类 || FloatingPointError | 浮点计算错误 || OverflowError | 数值运算超出最大限制 || ZeroDivisionerError | 除（或取模）零（所有数据类型） || AssertionError | 断言语句失败 || AttributeError | 对象没有这个属性 || EOFError | 没有内建输入，到达EOF标记 || EnvironmentError | 操作系统错误的基类 || IOError | 输入/输出操作失败 || OSError | 操作系统错误 || WindowsError | 系统调用失败 || ImportError | 导入模块/对象失败 || LookupError | 无效数据查询的基类 || IndexError | 序列中没有此索引 || KeyError | 映射中没有这个键 || MemoryError | 内存溢出错误 || NameError | 未声明/初始化的本地变量 || UnboundLocalError | 访问未初始化的本地变量 || ReferenceError | 弱引用（weak reference）试图访问已经垃圾回收了的对象 || NotImplementedError | 尚未实现的方法 || SyntaxError | 语法错误 || IndentationError | 缩进错误 || TabError | Tab和空格混用 || SystemError | 一般的解释器系统错误|| TypeError | 对类型无效的操作 || ValueError | 传入无效的参数 || UnicodeError | Unicode相关的错误 || UnicodeDecodeError | Unicode解码时错误 || UnicodeEncodeError | Unicode编码时错误 || UnicodeTranslateError | Unicode转换时错误 || Warning | 警告的基类 || DeprecationWarning | 关于被弃用的特征的警告 || FutureWarning | 关于构造将来语句会有改变的警告 || OverflowWarning | 旧的关于自动提升为长整型（long）的警告 || PendingDeprecationWarning | 关于特性将会被废弃的警告 || RuntimeWarning | 可以的运行时行为警告 || SyntaxWarning | 可以的语法的警告 || UserWarning | 用户代码生成警告 |  7.4 创建异常 7.4.1 常见异常的方法创建异常又叫做抛出异常，也叫作出发异常，是主动向系统报出异常，使用关键字**raise**来实现。创建异常的写法如下：```raise [Exception [, args [, traceback]]]```* Exception：异常参数值，指代异常的类型（例如：NameError）。该参数是可选的，默认是None* traceback：可选的，代表所要跟踪的异常对象**注意**：raise抛出的异常必须是一个异常实例或者类（派生自Exception的类）。 7.4.2 创建异常举例当程序运行时，无论当前代码是否正常，只要执行到有raise开头的语句，就会抛出异常。<jupyter_code>try:
    x = int(input("请输入一个被除数："))  # 等待输入一个数
    if 0 == x:  # 判断输入是否为0.如果为0，主动生成异常
        raise ValueError("输入错误：0不能做被除数")
    print("30除以", x, "等于", 30/x)  # 输出30除以该输入的数字
except Exception as e:
    print(e)
except ZeroDivisionError:  # 捕获ZeroDivisionError异常
    print("被除数不等于0，重新输入...")
except:
    print("其他异常...")<jupyter_output>请输入一个被除数：0
输入错误：0不能做被除数<jupyter_text>从程序运行结果，在捕获异常时，程序运行了ValueError异常分支。这与代码中通过raise生成的异常类型（ValueError）一致。在ValueError异常分支中，执行的代码是将异常信息打印出来。使用**raise**语句的程序会更加严谨，可以在程序执行到错误的分支情况时主动报出异常，增强了程序逻辑的健壮性。在出错时，还可以提供自定义的人机交互信息，使得程序调试起来，更加方便。 7.5 异常的最终处理（清理动作）Python中，finally语句是与try和except语句配合使用的。finally语句中的内容一般都是用来做清理工作的。无论try中的语句是否跳入except中，最终都要进入finally语句，并执行。 7.5.1 finally的使用场景try和except语句改变了程序执行的流程。代码在没有运行的前提下，并不知道会进入那个分支。在复杂的逻辑关系中，很容易出现由于出现资源不释放，而引起资源泄露或内存泄漏的问题。为了解决这种问题，一般在except的最后加个finally，并在finally语句中将资源或是内存进行统一地清理。这样，无论程序运行到那个分支，最终都会进入finally语句的分支代码进入资源的收尾工作，从而避免了资源或内存泄漏的问题。<jupyter_code>try:
    print("打开一个文件")
    print("读取内容")
    raise IOError("读取出错")
except Exception as e:
    print(e)
finally:
    print("关闭文件")<jupyter_output>打开一个文件
读取内容
读取出错
关闭文件<jupyter_text>7.5.2 finally与else的区别finally与else的区别在于：* else：语句只在没有异常发生的情况下执行* finally：语句则不管异常发生与否都会执行finally语句总是在退出try语句前被执行，无论是正常退出、异常退出，并通过break、continue、return语句退出。 7.6 判定条件的正确性（断言） 7.6.1 断言的表达形式断言，使用assert关键字后面接着一个条件表达式。* 如果条件表达式为真，意味着程序当前的条件于开发人员自己断言的情况一致，则程序继续运行* 如果为假，则表明一定是前面发生了错误，程序停止运行，报出异常。> assert 1!=1 7.6.2 带错误信息的断言语句Python中，如果断言后面的条件语句失败了，还可以为其指定对应的字符串输出。这样代码就会变得更加友好。具体做法是：直接在assert后面得条件语句之后，加上“,字符串”。例如：> assert 1!=1,("1 不等于 1， 报错")在正规得开发流程中，单元测试是每个程序员都应该做的事情。开发人员编写代码后，可以利用断言语句来检查自身的错误，实现的单元测试。断言常常被用在单元测试和参数检查中，以避免程序运行时出现开发人员编写中的逻辑错误。 实例<jupyter_code>def retry(attempt):   # 定义装饰器函数
    def decorator(runc):
        def wrapper(*args, **kwargs):
            att = 0
            while att < attempt:  # 按照计数器att的条件来执行循环语句
                print(att)
                try:
                    return func(*args, **kwargs)  # 使用try except捕获
                except Exception as e:
                    att +=1   # 调整计数器
        return wrapper
    return decorator

import requests

@retry(attempt=3)   # 应用装饰器并定义重试次数
def get_response(url):  # 定义爬虫函数
    r = requests.get(url)  # 对指定URL发出HTTP请求
    return r  # 返回请求结果

url = "http://www.163.com"
r = get_response(url)
print(r)<jupyter_output>0
1
2
None<jupyter_text>8.文件操作 8.1 文件的基本操作文件的操作有很多种，例如：创建、删除、修改权限、写入、读取等* 删除、修改权限：作用于文件本身，属于系统级操作* 写入、读取：是文件最常用的操作，作用于文件的内容，属于应用级操作。文件的系统级操作功能单一，容易实现。编码时，可以导入Python中的专用模块（os、sys等），并调用模块中的指定函数来实行。例如，假设代码的同级目录下有一个文件“a.txt”，可以直接调用os模块下的remove函数将其删除，具体代码如下：```import osos.remove("a.txt")    删除文件a.txt``` 8.1.1 读写文件的一般步骤读写文件可以分为3步，每一步都有对应的函数（1）打开文件：使用open函数，返回的是一个文件对象（2）具体读写：使用该文件对象的read、write等方法（3）关闭文件：使用该文件对象的close方法一个文件，必须在打开之后才可以对其进行操作，并在操作结束之后将其关闭。 8.1.2 打开文件“打开”文件读写操作的第一步。open函数的具体定义如下：```open(文件名, mode)```函数中有两个参数：* 文件名：属于字符串类型。使用时要注意转义问题，尽可能使用源字符串（以r开头的字符串）* mode：是指打开文件的方式，包括只读、只写、读写、二进制等。如果不指定mode参数，文件将默认以“只读”模式打开。函数open的返回值是一个文件对象。该对象中封装了文件的各种操作。1. open中的模式介绍在open函数中，参数mode起主要作用。它决定了文件的打开模式。具体如下：* r：只读。文件必须存在* w：只写。如果文件已存在，则将其覆盖。如果该文件不存在，则创建新文件。* +：读写（不能单独使用）* a：以只写的方式打开文件，用于在文件后追加内容。如果文件不存在，则创建新文件。* b：以二进制模式打开（不能单独使用）。在上面列出的mode值中，只有w和a可以创建文件。在实际应用中，这些mode值还可以组合使用，即同时使用多种模式来操作文件。调用open函数时，传入mode的常用值由r、w、r+、w+、rb、wb、rb+、wb+、a、a+、ab、ab+。**注意**：r+、w+、a+都是可读写的意思。三者的区别是：* r+：读写。文件必须存在。当写入时，会清空原内容* w+：读写。如果该文件不存在，则创建新文件。如果文件已存在，则清空原有内容。* a+：读写。如果文件不存，则创建新文件。如果文件已存在，则在文件后面追加内容。通常情况下，文件都是以文本模式（text mode）打开的。即，从文件中读写的以一种特定的编码格式（默认的时UTF-8)进行编码的字符串。如果文件以二进制模式打开，则数据以字节对象的形式进行读写。2. 文件模式与二进制模式的区别文本模式与二进制模式的区别如下：* 在Windows系统中，文本模式下，默认是将Windows平台的行未标识符 \r\n 在读取擦走时转为\n，而在写入操作时将 \n 转化 \r\n。这种隐藏的行为对于文本文件是没有问题的，但如果以文本模式打开二进制数据文件（如JPEG或EXE）则会发生问题，因为它改变了具体内容。* 在Unix/Linux系统中，行末标识符为 \n ，即文件以 \n 代表换行。所以，在Unix/Linux系统中文本模式和二进制模式并无区别。3. 函数open的返回对象函数open的返回值是由打开模式决定的，具体如下：* 文本模式：返回TextIOWrapper对象* 读取二进制模式：即“r+b”模式，返回BufferedReader对象* 写入和追加二进制模式：即“w+b”“a+b”模式，返回BufferedWriter对象* 读/写模式：即含有符号“+”的打开模式，返回BufferedRandom对象。 8.1.3 具体读写通过open函数得到文件对象后，就可以对文件进行操作了。最常用的方法是读和写。1. 读写文件通过调用文件对象的read方法可以获得文件的内容。```f = open('a.txt', 'r')   以只读方式打开文件s = f.read()         读取文件内容，并将返回的字符串赋值给sprint(s)```文件对象f的read方法，会将文件的全部内容一次性读取到内存中。2. 写入文件将字符串写入文件，可以调用文件对象的write方法。```f = open("a.txt", "w")    已写入的方式打开文件f.write("efgh")        写入efghf = open("a.txt", "r")   以只读的方式打开文件s = f.read()   读取文件内容，并将返回的字符串赋值给aprint(s)    将文件内容s打印，输出：efgh```**注意**：如果文件以二进制形式打开的，则只能以二进制形式写入，否则会报错。f = open("a.txt", "wb+")    以二进制形式打开一个文件f.write(I like Python!")   以文本形式向该文件写入数据，会报错在字符串前面加一个b，代表该字符串是二进制形式。 8.1.4 关闭文件直接使用文件对象的close方法可关闭文件。文件在打开并操作完事之后，需要及时关闭，否则会给程序带来好多无法预知的错误。<jupyter_code>import os

f = open("a.txt", "wb+")  # 以二进制形式打开一个文件
f.write(b"I like Python!") # 以二进制形式进行写入
os.remove("a.txt")<jupyter_output><empty_output><jupyter_text>文件还没有进行关闭，是无法被删除的。同时，可以看到当前代码的同级目录下，生成了一个a.txt文件。但是打开该文件会发现其中并没有内容。这表明文件对象的write方法只是把当前的内容缓存到了内存里，并没有真正写入到文件。<jupyter_code>f = open("a.txt", "wb+")
f.write(b"I like Python!")
f.close()<jupyter_output><empty_output><jupyter_text>执行上面代码之后，在当前代码的同级目录下可以生成a.txt文件，发现里面会有内容了。这表明在调用close时，系统自动将缓存里的内容写进了文件。 8.2 文件对象的方法 8.2.1 文件对象的常用方法介绍| 文件操作方法名称 | 描述 || :--- | :--- || f.read(size) | 读取size个字节的数据，然后作为字符串或bytes对象返回。size是一个可选参数。如果不指定size，则读取文件的所有内容 || f.readline() | 读取一行，并在字符串末尾留下换行符（\n）。如果到达文件尾，则返回空字符串 || r.readlines() | 读取所有行，并存储在列表中，每个元素时一行，相当于list(f) || f.write(string) | 将string写入到文件中，返回写入的字符数。如果以二进制模式写文件，则需要将string转换成bytes对象 || f.tell() | 返回文件对象当前所处的位置。是从文件开头开始算起字节数 || f.seek(offset, from_what) | 改变文件对象所处的位置。offset是相对参考位置的偏移量。from_what表示参考位置，取值为0（文件头，默认）、1（当前位置）、2（文件尾） |  8.2.2 把文件对象当作迭代器来读取文件对象本身也是一个迭代器，它也可以与for循环配合进行文件的读取<jupyter_code>f = open("a.txt", "wb+")  # 以二进制形式打开一个文件
f.write(b"I like Python!\n")  # 以二进制形式进行读写
f.write(b"I love Python!")  # 再写一行
f.close()

f = open("a.txt", "r+")  # 打开文件
for line in f:      # 直接使用for循环读取文件
    print(line)   # 将内容打印出来
f.close()  # 关闭文件<jupyter_output>I like Python!

I love Python!<jupyter_text>8.3 实例：带有异常处理的文件操作<jupyter_code>try:
    f = open("a.txt", "wb+")  # 以二进制的形式打开一个文件
    f.write("I like Python!")  # 以文本的方式写入一个用二进制打开的文件，会报错
except Exception as e: # 将错误异常捕获
    print(e)
    f.write(b"I like Python!") # 以二进制的形式进行读写
finally:    # 无论程序是否错误，在执行完前面代码后都要在这里关闭文件
    print("关闭文件")
    f.close()

try:
    f = open("a.txt", "r+")  # 打开文件
    for line in f:
        print(line)
finally:
    f.close() # 关闭文件<jupyter_output>a bytes-like object is required, not 'str'
关闭文件
I like Python!<jupyter_text>8.4 使用with语句简化代码with语句的写法如下：```with 表达式 as 变量:    具体的操作语句``` 8.4.1 实例```with open('a.txt', 'wb+') as f:    try:        f.write("I like Python!")    except Exception as e:        print(e)        f.write(b"I like Python!")  以bytes对象的形式进行读写with open("a.txt", "r+") as f:    for line in f:        print(line)``` 8.4.2 with语法的原理Python中，支持with语法的对象必须有一个\_\_enter\_\_方法和\_\_exit\_\_方法with语句执行过程中，紧跟with后面的语句被求值后，返回对象的\_\_enter\_\_方法被调用，这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完后，将调用前面返回对象的\_\_exit\_\_方法。 8.5 实现字符串与二进制数的相互转化在处理文件操作时，将文件“以二进制的形式保存，以文本的方式使用”是一种常用的做法。文本被转化成二进制数后可以占用更小的空间，便于网络传输和硬盘存储。 8.5.1 将字符串转二进制数如果将二进制转成字符串，可以调用二进制对象decode方法，并传入指定的解码格式。```In [1]: b = bytes("I like Python", "utf-8")In [2]: print(b, b.decode(), sep="；")b'I like Python'；I like PythonIn [3]: bOut[3]: b'I like Python'In [4]: b.decode()Out[4]: 'I like Python'```上数示例代码中，decode方法没有传入指定的解码格式，表明使用了默认的解码格式。上述代码也可以写成如下的样子```In [1]: b = bytes("I like Python", "utf-8")In [2]: print(b, b.decode('utf-8'), sep="；")b'I like Python'；I like Python``` 8.6 将任意对象序列化在Python中有个序列化过程叫做pickle。它能够实现任意对象于文本之间的相互转化，也可以将任意对象与二进制之间的相互转化。即，可以透明地实现Python对象的存储及恢复。使用Python的pickle操作，可以将对象序列化成字符串、磁盘上的文件等类似于文件的任何对象；也可以将这些字符串、文件或任何类似于文件的对象unpickle成原来的对象。 8.6.1 pickle模块pickle模块中共有4个函数* dumps：将Python中的对象序列化成二进制对象，并返回* loads：从给定的pickle数据中读取并返回对象* dump：将Python中的对象序列化成二进制对象，并写入文件* load：读取指定的序列化数据文件，并返回对象这四个函数可以按照作用分为两类：dumps和loads，实现基于内存的Python对象与二进制互转；dump与load，实现基于文件的Python对象与二进制互转。1. 将Python对象转成二进制（dumps函数）dumps函数的具体定义：```dumps(obj, protocol=None, *, fix_imports=True```参数说明如下：* obj：要转换的Python对象* protocol：pickle的转码协议，取值为0、1、2、3、4.其中，0为ASCII码表示；2为旧版本的二进制协议；3为新版本的二进制协议；4为更新的二进制协议。未指定情况下，默认为3* 其他参数：为了兼容Python2保留的参数2. 将二进制对象转成Python对象（loads函数）loads函数的具体定义：```loads(data, *, fix_imports=True, encoding="ASCII", errors="strict")```参数说明如下：* data：要转换的二进制对象* 其他参数：是为了兼容Python2保留的参数在将二进制对象反序列化成Python对象时，会自动 识别转码协议，所以不需要将转码协议当作参数传入。当待转换的二进制对象的字节数超过pickle的Python对象时，多余的字节将被忽略。3. 将Python对象转成二进制文件（dump函数）dump函数的定义如下：```dump(obj, file, protocol=None, *, fix_imports=True)```参数说明如下：* obj：要转换的Python对象* file：文件必须有write方法，并且支持写入二进制数据。* protocol：pickle的转码协议，取值为0、1、2、3、4.其中，0为ASCII码表示；2为旧版本的二进制协议；3为新版本的二进制协议；4为更新的二进制协议。未指定情况下，默认为3* 其他参数：为了兼容Python2保留的参数4. 将二进制对象文件转成Python对象（load函数）load函数的定义如下:```load(file, *, fix_imports=True, encoding="ASCII", errors="strict")```参数说明：* File：对象必须有两个方法——read()和readline()* 其他参数：为了兼容Python2保留的参数在将二进制unpickle转成Python对象时，会自动识别转码协议，所以不需要将转码协议当作参数传入。当待转换的二进制对象的字节数超过pickle的Python对象时，多余的字节将被忽略。<jupyter_code># 用pickle模块实现元组与“二进制对象”“二进制对象文件”之间的转换

import pickle

tup1 = ("I love Python", [1, 2, 3], None)

path = "a.pkl"
with open(path, "wb") as f:  # 打开文件
    pickle.dump(tup1, f, pickle.HIGHEST_PROTOCOL) # 用dump将Python对象转成二进制对象文件

with open(path, "rb") as f: # 再次打开文件
    t3 = pickle.load(f)  # 用load将二进制对象文件转成Python对象
    print(t3)<jupyter_output>('I love Python', [1, 2, 3], None)<jupyter_text>8.6.3 序列化的扩展方法（ZODB）pickle的缺点在于，它不支持并发地访问持久性对象。在复杂系统环境下，尤其时海量数据读取时，使用pickle会使得整个系统的I/O读取性能称为瓶颈，必须使用一个支持多线程并发问题的pickle模块才可以完成这类任务。ZODB是一个健壮的、多用户的和面向对象的数据库系统，专门用于存储Python语言中的对象数据。可以存储Python对象，并支持事物操作和并发控制。  9. 类 9.1 类的相关术语及实现Python中使用类（class）来实现面向对象编程。Python中的类，具有面向对象编程的所有基本特征：允许多继承、派生类可以重写它父类的任何方法、方法可以调用父类中同名的方法，对象可以包含任意数量和类型的数据成员。1. 类的相关术语* 类（class）：用来描述具有相同的属性和方法的对象的集合。它定义了该集合中所有对象共有的属性和方法。* 类变量：类变量在所有实例化对象中是公用的。类变量定义在类中，且在函数体之外。类变量通常不作为实例变量使用。* 数据成员：类变量或实例变量，用于处理类及其实例对象的相关数据* 方法重写：如果从父类继承的方法不能满足子类的需求，可以对其进行 改写，这个过程叫方法的重写（override），也称为方法的重写。* 实例变量：定义在方法中的变量，只作用于当前实例的类。* 继承：即派生类（derived class）具有基类（base class）一样的字段和方法。继承会把一个派生类的对象作为一个基类对象对待。* 实例化：创建一个类的实例，即得到类的具体对象* 对象：对象是类的实例。对象包括两个数据成功（类变量和实例变量）和方法 9.1.1 创建类Python中，使用class语句来创建一个新类。class之后为类的名称，并以冒号结尾。类的定义如下：```class ClassName:    """具体内容"""    pass```在class的代码块中，可以编写该类的执行语句。一般会先使用多行字符串对该类进行说明。类定义会创建一个新的命名空间作为一个局部的作用域。在该作用域中的变量被叫做成员变量。在该类作用域下的函数就叫做成员函数，也叫做该类的方法。当一个类定义结束后，系统就会创建一个类对象 9.1.2 创建类属性1. 定义类属性在定义类方法时，需要有一个默认的形参——self。而在调用类方法时，却不需要传入形参self所对应的实参。因为这个形参self由类的内部机制调用。具体如下：```class MyClass:    定义一个类    """A simple example class"""   定义该类的说明字符串    i = 12345    定义成员变量        def f(self):     定义方法        return "I love Python"```2. 使用类创建好的类可以实例化成一个对象，并通过调用对象的方法来实现具体功能。<jupyter_code>class MyClass:   # 定义一个类
    """A simple example class"""  # 定义该类的说明字符串
    i = 12345   # 定义成员变量

    def f(self):    # 定义方法
        return "I love Python"

myc = MyClass()   # 实例化对象，并赋值给myc
print(myc.i)   # 将类实例myc的成员变量i打印
print(myc.f())  # 调用实例myc的成员函数f<jupyter_output>12345
I love Python<jupyter_text>3. 类的内置属性Python中的类有一些相同的内置属性。这些内置属性用于维护类的基本信息，具体如下：* \_\_name\_\_：类名称* \_\_doc\_\_：类的文档字符串* \_\_module\_\_：类定义所在的模块。如果时直接运行当前文件，该值为\_\_main\_\_* \_\_base\_\_：该类所有的父类列表，是一个tuple类型的对象。* \_\_dict\_\_：该类的所有属性（由类的数据属性组成），是一个dict类型的对象。<jupyter_code>class MyClass:   # 定义一个类
    """A simple example class"""  # 定义该类的说明字符串
    i = 12345   # 定义成员变量

    def f(self):    # 定义方法
        return "I love Python"

print(MyClass.__name__)
print(MyClass.__doc__)
print(MyClass.__module__)
print(MyClass.__base__)
print(MyClass.__dict__)<jupyter_output>MyClass
A simple example class
__main__
<class 'object'>
{'__module__': '__main__', '__doc__': 'A simple example class', 'i': 12345, 'f': <function MyClass.f at 0x00000235F016AB80>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>}<jupyter_text>9.1.3 定义类的动态属性1. 添加动态属性为类添加动态属性的方法是：在实例化对象的后面加一个点，在点后面跟上属性名称。如果该对象已经有这个属性，系统就会读取其内容；否则，系统会自动为该对象加上这个属性。<jupyter_code>class Record:  # 定义一个空的类
    pass

Anna = Record()  # 实例化
Anna.name = 'Anna'  # 为该对象添加动态属性name
Anna.age = 23 # 动态添加属性age
print(Anna.name, Anna.age)<jupyter_output>Anna 23<jupyter_text>2. 删除动态属性使用del加上对象的属性，即可删除属性。<jupyter_code>class Record:  # 定义一个空的类
    pass

Anna = Record()  # 实例化
Anna.name = 'Anna'  # 为该对象添加动态属性name
Anna.age = 23 # 动态添加属性age
print(Anna.name, Anna.age)

del Anna.age   # 删除Anna的age属性
print(Anna.age)<jupyter_output>Anna 23<jupyter_text>9.1.4 限制类属性(\_\_slots\_\_)在类中可以使用\_\_slots\_\_变量来限制类的属性\_\_slots\_\_是一个特殊的变量，其值可以是元组的形式，元组的元素为该类所允许添加的属性名称。在类中，如果为\_\_slots\_\_赋值了，那么这个类的实例化对象只能添加\_\_slots\_\_中所规定的属性。<jupyter_code>class Record:  # 定义类
    __slots__ = ('name', 'age')  # 设置合法的属性为name， age

Anna = Record()  # 实例化
Anna.name = 'Anna' # 动态添加属性name
Anna.age = 32      # 动态添加属性age
Anna.age2 = 23     # 动态添加属性age2<jupyter_output><empty_output><jupyter_text>9.2 实例化类对象 9.2.1 带有初始值的实例化实例化类，可以使用类的名字加括号来实现。对于一些复杂功能的类，还需要再实例化的同时为其初始化某些必需的成员变量，这就是带有初始值的实例化。1. 类的初始化方法（\_\_init\_\_)想要实现带有初始值的实例化，需要再定义类时，在类里面实现一个\_\_init\_\_方法。\_\_init\_\_方法的定义与函数相似，需要形参，并且支持形参默认值等。这样，在实例化时，可以将具体要初始化的值当作实参，传入到类名字后的括号里。<jupyter_code>class MyClass: # 定义类
    """A record class"""  # 说明字符串
    def __init__(self, name, age): # 初始化函数
        self.name = name   # 将传入的参数值赋值给成员变量
        self.age = age
    
    def getrecode(self):  # 定义一个成员函数
        return self.name, self.age  # 该成员函数返回该类的成员变量

myc = MyClass("Anna", 42)  # 实例化对象
print(myc.getrecode())  # 调用成员函数<jupyter_output>('Anna', 42)<jupyter_text>在初始化函数\_\_init\_\_中，直接使用self加点的方式，为指定类中的成员变量赋值。这个成员变量的定义机制与普通变量的定义机制，在赋值语句执行的同时，会自动创建并加入该类。2. 隐藏调用类的初始化方法其实每个类在实例化时，都会在内部调用初始化函数\_\_init\_\_。对于一个没有初始化函数的类，在实例化时，也会调用内部默认的\_\_init\_\_函数。如果在类中实现了函数\_\_init\_\_，就会优先调用自定义的函数\_\_init\_\_。<jupyter_code>class MyClass:   # 定义一个类
    """A record class"""   # 定义该类的说明字符串
    def __init__(self):    # 定义初始化函数
        print("here")      # 并传入的参数赋值给成员变量

myc = MyClass()<jupyter_output>here<jupyter_text>9.2.2 class 中的self在类的代码块中，可以使用self关键字来指定本身。1. 直接调用类的成员函数从函数传值的角度来看，直接使用该类的成员函数并传入实例化对象，与通过“类对象.成员函数”的调用方法效果一样。<jupyter_code>class MyClass:      # 定义类
    """A simple example class"""  # 类描述字符串
    i = 12345       # 成员变量
    def f(self):    # 成员函数
        return "I love python"

myc = MyClass()    # 实例化类对象
print(myc.f())     # 调用类的实例myc的成员函数f
print(MyClass.f(myc)) # 调用类的实例myc的成员函数f<jupyter_output>I love python
I love python<jupyter_text>上述示例中，分别使用了“实例化对象(myc) + 点 + 函数(f)”和“向类（MyClass）的函数（f）中传入对象（myc）”的方式来调用函数。2. 将成员函数定义在类外面如果将类中的成员函数（即方法）看成一个函数，则完全可以把类的成员函数定义在类外面。但是，第一个参数self这个规则还要必须保留。<jupyter_code>def fun(self):  # 在类的外面定义方法
    return "I love Python"

class MyClass:  # 定义类
    """A simple example class"""
    i = 12345   # 成员变量
    f = fun     # 在类的内部指定成员函数

myc = MyClass()
print(myc.f())<jupyter_output>I love Python<jupyter_text>上述示例中可以看出，这种方法使得成员函数可以被多个类使用，从而提高代码重用性。3. 类内的成员互访类内的成员相互访问可以通过`self.`的方式<jupyter_code>class MyClass:
    """A record class"""
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def getage(self):
        return self.age
    
    def getname(self):
        return self.name
    
    def getrecode(self):
        return self.getage(), self.getname()

myc = MyClass("Anna", 43)
print(myc.getrecode())<jupyter_output>(43, 'Anna')<jupyter_text>**注意**：在python中，每个值都是一个对象，可以通过object.\_\_class\_\_来获取对象的class（即类型），作用与type()相同。 9.2.3 类方法（@classmethod）与静态方法（@staticmethod）在默认的情况下，类的成员方法（也叫成员函数）的第一个参数必须是self。在类中还有两种其他类型的成员函数：类方法与静态方法。1. 类方法在一个类的定义中，如某个方法使用了装饰器@classmethod进行装饰，则说明该方法时一个类方法。类方法与默认成员方法的区别时：类方法属于类；默认成员方法属于类实例化对象。具体表现为：* 类方法的第一个参数必须时cls（用来指代该类），而默认成员方法的第一个参数必须是self（用来指代该类的实例对象）。在调用类方法时，需要使用“类名.类方法名”的方式。* 在调用默认成员方法时，则是使用“该类的实例化对象.类方法名”的方式。<jupyter_code>class MyClass:  # 定义类
    def f(self):  # 默认的成员方法
        return "I love Python"
    
    @classmethod  # 定义类方法
    def fcls(cls):
        return "类方法：I love Python"

myc = MyClass()  # 实例化对象，并赋值给myc
print(myc.f())   # 调用类实例化myc的成员函数f
print(MyClass.fcls())  # 调用类方法<jupyter_output>I love Python
类方法：I love Python<jupyter_text>2. 静态方法静态方法的定义是使用装饰器`@staticmethod`进行装饰的。静态方法的特点是：第一个参数没有任何要求，与普通函数参数一样。其调用方式更加宽松，使用类名或是类的实例化对象都可以对其进行调用。<jupyter_code>class MyClass:  # 定义类
    @staticmethod
    def fstatic():
        return "静态方法：I love Python"

myc = MyClass() # 实例化
print(myc.fstatic())
print(MyClass.fstatic())<jupyter_output>静态方法：I love Python
静态方法：I love Python<jupyter_text>3. 总结类成员方法有三种类型，分别面向于类中的三个实体。* 默认的成员方法：仅仅面向该类的一个实例* 类方法：面对的是当前类，独立于类实例化的各个对象。* 静态方法：等同于普通函数，只是被封装在类中而已，其独立于整个类。**注意**：类成员方法的三种类型，在类的派生过程中仍然有效。即，子类也可以调用父类的成员方法。 9.2.4 类变量于实例变量的区别* 类变量：是指类的属性和方法，类似于一种静态数据。类变量是在定义类时所定义的，类的所有实例都会共享类变量* 实例变量：更像是一种动态数据。只有在实例化时，系统才会为该实例指定它特有的数据，有别于该类中的其他个体。1. 类变量于实例变量举例<jupyter_code>class MyClass:  # 定义类
    """A record class"""  # 说明字符串
    Occupation = "scientist"
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def getrecode(self):
        return self.name, self.age

myc = MyClass("Anna", 42)<jupyter_output><empty_output><jupyter_text>上述实例中，Occupation属于类变量，name，age是属于实例变量。2. 类变量与实例变量的易犯错误通过修改一个类的类变量，可以将这个类的所有实例化对象的属性统一修改。但是，当实例变量与类变量同名时，系统会以实例变量优先。这会导致类变量失效，从而无法批量改实例化后的类属性。 9.2.5 销毁实例化对象在创建类实例化对象时，可使用内置的\_\_init\_\_初始化改对象；当改对象需要销毁时，也需要进行一次自我销毁操作。调用\_\_del\_\_方法可以销毁对象，类的\_\_del\_\_方法与初始化方法\_\_init\_\_是一一对应的。<jupyter_code>class MyClass:
    """A record class"""
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __del__(self):
        print(self.__class__.__name__, "del")

myc = MyClass("Anna", 42)
del myc<jupyter_output>MyClass del<jupyter_text>\_\_del\_\_与\_\_init\_\_都是与类操作有关的内置函数。当一个类中没有重写改函数时，程序什么都不做。重写之后，在创建实例化对象时，会自动调用\_\_init\_\_函数。在销毁对象时，会自动调用\_\_del\_\_函数。 9.3 类变量的私有化类属性 9.3.1 公有化（public）与私有化（private）私有化的实现方式是：给类变量加上一个私有化（private）权限。被私有化的属性不能被该类的实例化对象直接访问，但是类的内部成员函数是可以访问的。如果类的实例化对象想要取得该类对的私有化属性，可以通过该类中的函数来完成。与私有化（private）对应的公有化（public）。公有化是Python中的默认权限，即，所有的对象都是可以访问。 9.3.2 私有化的实现Python中规定，以两个下划线开头的成员对象为私有化成员。1. 私有化的实现举例下面通过私有化类属性，来避免类属性被任意篡改。<jupyter_code>class MyClass: # 定义类
    """A record class"""
    __Occupation = "scientist"   # 定义私有属性
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def getrecode(self):
        return self.name, self.age
    
    def getOccupation(self):   # 访问私有变量的访问
        return self.__Occupation

myc = MyClass("Anna", 23)
myc2 = MyClass("Gary", 38)
myc.__Occupation = "inventor"
print(myc.getrecode())
print(myc.getOccupation())
print(myc2.getrecode())
print(myc2.getOccupation())<jupyter_output>('Anna', 23)
scientist
('Gary', 38)
scientist<jupyter_text>2. 私有化的原理Python中，私有化的原理是通过将私有化变量改名的方式实现。<jupyter_code>class MyClass: # 定义类
    """A record class"""
    __Occupation = "scientist"   # 定义私有属性
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def getrecode(self):
        return self.name, self.age
    
    def getOccupation(self):   # 访问私有变量的访问
        return self.__Occupation

print(MyClass.__dict__)<jupyter_output>{'__module__': '__main__', '__doc__': 'A record class', '_MyClass__Occupation': 'scientist', '__init__': <function MyClass.__init__ at 0x000002CB5BD30790>, 'getrecode': <function MyClass.getrecode at 0x000002CB5BD30B80>, 'getOccupation': <function MyClass.getOccupation at 0x000002CB5BD30280>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>}<jupyter_text>从上述的输出的类属性中，可以看到有一个\_MyClass\_\_Occupation变量。这个变量就是私有变量\_\_Occupation。这表明Python语法自动将私有变量改名，通过这种方式，让实例化对象找不到该私有变量。但是我们可以通过改变过的变量取访问私有变量。 9.3.3 使用装饰器技术实现类的私有化（@property）私有化属性的应用提高了程序的健壮性。但必须为私有变量封装get方法，通过调用get方法才可以取值。1. 装饰器实现私有变量的get函数<jupyter_code>class MyClass:
    """A record class"""
    __Occupation = "scientist"

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def getrecode(self):
        return self.name, self.age
    
    def getOccupation(self):
        return self.__Occupation
    
    @property
    def Occupation(self):  # 将get函数装饰成属性
        return self.__Occupation


myc = MyClass("Anna", 42)
print(myc.getOccupation())
print(myc.Occupation)    # 以属性的访问方式来调用装饰器的函数<jupyter_output>scientist
scientist<jupyter_text>2. 装饰器实现私有变量的set函数<jupyter_code>class MyClass:
    """A record class"""
    __Occupation = "scientist"

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    @property
    def Occupation(self):  # 将get函数装饰成属性
        return self.__Occupation
    
    @Occupation.setter
    def Occupation(self, value):  # 将set函数装饰成属性
        if (value != 'inventor') and (value != 'scientist'):
            raise ValueError('Occupation must be inventor or scientist!')
        else:
            self.__Occupation = value

myc = MyClass("Anna", 42)  # 实例化，并初始化
print(myc.Occupation)
myc.Occupation = 'inventor'
print(myc.Occupation)<jupyter_output>scientist
inventor<jupyter_text>上面代码是在原有的基础上，增加了一个被@Occupation.setter装饰的Occupation函数，在Occupation函数里，对传入的参数进行判断。如果不符合抛出异常；否则修改私有变量。使用“装饰器 + 私有变量”的方式，在提升代码健壮性的同时，又保留了对属性直接访问的取值方式，使得get与set的操作变得透明。 9.4 实现子类面向对象编程思想中，类与类之间可以有派生或是继承的关系。派生与继承是针对与父类、子类的关系而言* 父类可以派生一个子类。这样，子类继承了父类的属性与方法。* 子类里同样可以定义自己的属性与方法。并且能够派生出新的子类。 9.4.1 继承类的继承分为单继承和多继承。* 单继承是指派生类只有一个父类。* 多继承是指派生类有多个父类。1. 单继承的实现单继承的实现是在定义类的时候，在类名后面加一个括号，在括号里指定父类的类名。```class DerivedClassName(FatherClassName):    pass```父类也可以是其他模块中，并不仅限于本模块中。当访问派生类的属性时，首先会在当前的派生类中搜索，如果找不到，则会递归的去父类中寻找。2. 子类方法覆写（override）当子类的方法与父类的方法同名时，父类的方法将失效。3. 多重继承的实现多重继承与单继承类似，只不过是在类名后的括号中多加几个父类，使用逗号分隔。```class DerivedClassName(FatherClassName1, FatherClassName2, ...)    pass```在多重继承下，若要访问派生类的属性，默认的搜索的规则是：深度优先，从左到右。* 如果一个属性在当前类中没有找到，他就会在FatherClassName1中寻找，如果FatherClassName1中没有找到，就会在FatherClassName2中寻找，直到最后一个父类；如果最后一个父类还是没有找到属性，则会报错。<jupyter_code>class Record:
    """A record class"""
    __Occupation = "scientist"

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def showrecode(self):
        print("Occupation：", self.getOccupation())
    
    def getOccupation(self):
        return self.__Occupation
    

class GirlRecord(Record):
    """A GirlRecord class"""

    def showrecode(self):
        Record.showrecode(self)  # 调用父类中的方法
        print("the girl: ", self.name, "'s age is ", self.age)

myc = GirlRecord("Anna", 42)
myc.showrecode()<jupyter_output>Occupation： scientist
the girl:  Anna 's age is  42<jupyter_text>9.4.3 super函数在单继承中，直接在子类的方法中调用父类即可调用父类函数。如果继承关系复杂，就很容易出现父类方法被多次自动调用，这种时候可以通过super函数来保证父类的方法只被执行一次。super函数的本意是：获得父类的对象，并调用父类的方法。<jupyter_code># super函数功能：1.1 使用父类名.方法名
class Record:
    """A record class"""
    __Occupation = "scientist"

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def showrecode(self):
        print("Occupation：", self.getOccupation())
    
    def getOccupation(self):
        return self.__Occupation


class FemaleRecord(Record):
    """A GirlRecord class"""

    def showrecode(self):
        print(self.name, " : ", self.age, ", female")
        Record.showrecode(self)


class RetireRecord(Record):
    """A RetireRecord class"""

    def showrecode(self):
        Record.showrecode(self)
        print("retired worker")


class ThisRecord(FemaleRecord, RetireRecord):
    """A ThisRecord class"""

    def showrecode(self):
        print("the member detail as follow: ")
        FemaleRecord.showrecode(self)
        RetireRecord.showrecode(self)

myc = ThisRecord("Anna", 73)
myc.showrecode()
# super函数功能：1.1 使用父类名.方法名
class Record:
    """A record class"""
    __Occupation = "scientist"

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def showrecode(self):
        print("Occupation：", self.getOccupation())
    
    def getOccupation(self):
        return self.__Occupation


class FemaleRecord(Record):
    """A GirlRecord class"""

    def showrecode(self):
        print(self.name, " : ", self.age, ", female")
        super().showrecode()


class RetireRecord(Record):
    """A RetireRecord class"""

    def showrecode(self):
        super().showrecode()
        print("retired worker")


class ThisRecord(FemaleRecord, RetireRecord):
    """A ThisRecord class"""

    def showrecode(self):
        print("the member detail as follow: ")
        super().showrecode()

myc = ThisRecord("Anna", 73)
myc.showrecode()<jupyter_output>the member detail as follow: 
Anna  :  73 , female
Occupation： scientist
retired worker<jupyter_text>9.5 类相关的常用内置函数 9.5.1 判断实例（isinstance）函数isinstance的作用是，判断某个实例对象是否是某个类或其派生类的实例`isinstance(object, class_name)`参数说明：* object：实例对象* class_name：类名如果object（object.\_\_class\_\_）是类class_name或其派生类的实例，则返回True 9.5.2 判断子类（issubclass）函数issubclass的作用是，判断类是否是另一个类的子类。定义如下：`issubclass(class1, class2)`如果class1是class2的派生类，则返回True。例如issubclass(bool, int)会返回True，因为bool是int的派生类。 9.5.3 判断类实例中是否含有某个属性（hasattr）函数hasattr的作用是，判断类实例中是否含有某个属性。定义如下：`hasattr(obj, name, /)`如果类实例obj中含有name属性，则返回True；否则返回False 9.5.4 获得类实例中的某个属性（getattr）函数getattr的作用是，获得类实体中的某个属性。定义如下`getattr(obj, name[, default])`如果类实例obj中含有name属性，则返回该属性的值；否则看是否有default，如果有，则将该default的值返回；否则会产生一个AttributeError的异常。 9.5.5 设置类实例中的某个属性值（setattr）函数setattr的作用是，设置类实例中的某个属性。定义如下`setattr(ojb, name, value, /)`为实例obj中的name赋上value的值，如果obj中没有name，那么新建一个。 9.6 重载运算符 9.6.1 重载运算符的方法与演示重载运算符，是指在类中定义并实现一个与运算符对应的处理方法。在两个对象进行运算符操作时，系统就会调用类中的具体方法来处理。下面通过代码来实现重载运算符：<jupyter_code>class MyClass:  # 定义类
    """A record class"""

    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return "name: {}; age: {}".format(self.name, self.age)
    
    __repr__ = __str__

    def __lt__(self, record):
        if self.age < record.age:
            return True
        else:
            return False
    
    def __add__(self, record):
        return MyClass(self.name, self.age+record.age)

myc = MyClass("Anna", 42)
myc1 = MyClass("Gary", 23)

print(repr(myc))
print(myc)
print(str(myc))
print(myc<myc1)
print(myc+myc1)<jupyter_output>name: Anna; age: 42
name: Anna; age: 42
name: Anna; age: 42
False
name: Anna; age: 65<jupyter_text>9.6.2 可重载的运算符| 重载的运算符方法名称 | 描述 || :--- | :--- || \_\_new\_\_ | 创建类。在\_\_init\_\_之前创建对象 || \_\_init\_\_ | 构造函数。对象创建时，为其初始化 || \_\_del\_\_ | 析构函数。对象销毁时，进行收回操作 || \_\_add\_\_ | 加法运算符+。如果没有重载\_iadd\_，则在类似X+Y、X+=Y这样的语句中，加号生效。 || \_\_or\_\_ | “或”运算符\|。如果没有重载\_ior\_，则类似X\|Y、X\|=Y这样的语句中，“或”符号的作用 || \_\_repr\_\_,\_\_str\_\_ | 格式转换。对应于函数repr(X)、str(X) || \_\_call\_\_ | 函数调用。类似于X(*args, **kwargs)语句 || \_\_getattr\_\_ | 点号运算。用来获取类属性 || \_\_setattr\_\_ | 属性赋值语句。类似于X.any = value || \_\_delattr\_\_ | 删除属性。类似于del X.any || \_\_getattribute\_\_ | 获取属性。类似于X.any || \_\_getitem\_\_ | 索引运算。类似于X\[key\], X\[i:j\] || \_\_setitem\_\_ | 索引赋值语句。类似于X\[key\]=value, X\[i:j\]=sequence || \_\_delitem\_\_ | 索引和分片删除。|| \_\_get\_\_, \_\_set\_\_, \_\_delete\_\_ | 描述符属性。类似于X.attr, X.attr=value, del X.attr || \_\_len\_\_ | 计算长度。类似于len(X) || \_\_lt\_\_, \_\_gt\_\_, \_\_le\_\_, \_\_ge\_\_, \_\_eq\_\_, \_\_ne\_\_ | 比较。分别对应、=、==、!= || \_\_radd\_\_ | 右侧加法。类似于other + X || \_\_iadd\_\_ | 加号等于符号。类似于X += Y中，+=的作用 || \_\_iter\_\_, \_\_next\_\_ | 迭代环境下，生成迭代器于取下一条。类似于I=iter(X)和next() || \_\_contains\_\_ | 成员关系测试。类似于item in X || \_\_index\_\_ | 整数值。类似于hex(X), bin(X), oct(X) || \_\_enter\_\_, \_\_exit\_\_ | 环境管理器，进入和退出。类似于with obj as var语句。|  9.7 包装与代理 9.7.1 包装包装就是将某个对象（或类），进行重新打包，转换成另外一种更合适当前使用场合的对外接口。被包装后的对象（或类），自身的内部逻辑并没有改变。只是变化了对外的接口。Python种包装的具体做法：* 创建一个类，将其要包装的目标类的对象，作为新创建类的成员对象。即，包装类包含被包装类的实例。* 为包装类实现各种与外部调用相吻合的接口。通过接口将调用关系传递到被包装类对象种，以实现真正的包装效果。<jupyter_code>class WrapMe(object):
    def __init__(self, obj):  # 初始化函数，将被包装对象传入
        self.__data = obj     # __data为包装类的成员对象
    
    def get(self):            # 返回被包装类的成员对象
        return "in get function：", self.__data
    
    def __str__(self):         # 重载str函数
        return "in __str__ function, data = {}".format(self.__data)

mynum = WrapMe(888)
print(mynum, type(mynum))
print(str(mynum))<jupyter_output>in __str__ function, data = 888 <class '__main__.WrapMe'>
in __str__ function, data = 888<jupyter_text>9.7.2 代理代理也是一种实现类的包装的方法。具体做法是，将包装类变成一个属性的“代理”。这使得访问包装类的属性等同于访问被包装类的属性。<jupyter_code>class WrapMe(object):
    def __init__(self, obj):        # 初始化函数，将被包装对象传入
        self.__data = obj           # __data为包装类的成员对象
    
    def __repr__(self):             # 重载repr函数
        return "in __repr__ function：{}".format(repr(self.__data))
    
    def __getattr__(self, attr):    # 代理
        return getattr(self.__data, attr)


Complex = WrapMe(2+4j) # 对复数进行包装并实例化成Complex对象
print(Complex)
print(Complex.real)

Complexlist = WrapMe([1,3]) # 对列表进行包装
Complexlist.append(4)
print(Complexlist)
# 使用代理的方式是实现RESTful API接口
"""
表述性状态传递( Representational State Transfer, REST) 是一种软件架构风格。它是一-种针对网络应用的设计和开发方式，可以降低开发的复杂性，提高系统的可伸缩性。它属于网站即软件的概念，即，在互联网环境中使用的单机版软件的思想来开发程序。
"""
# 1.1 静态接口
class ChainURL(object):
    def __init__(self, attr=''):
        self.__data = attr

    def __getattr__(self, attr):
        return ChainURL('%s/%s'%(self.__data, attr))
    
    def __str__(self):
        return self.__data

    __repr__ = __str__

print(ChainURL().status.allusers.list)
# 1.2 动态接口
class ChainURL(object):
    def __init__(self, attr=''):
        self.__data = attr

    def __getattr__(self, attr): # 定义代理函数
        return ChainURL('%s/%s'%(self.__data, attr)) # 返回自身对象，实现链式字符串拼接
    
    def __call__(self, attr):   # 重载call运算符，使其具有被调用的属性
        return ChainURL('%s/%s'%(self.__data, attr))
    
    def __str__(self):
        return self.__data

    __repr__ = __str__

print(ChainURL().users("Anna").info)<jupyter_output>/users/Anna/info<jupyter_text>9.8 自定义异常类 9.8.1 自定义异常类的方法异常类都是从Exception类派生的。自定义的异常类也是继承于Exception或者是其子类。<jupyter_code>class MyEpt(Exception): # 自定义异常类
    def __init__(self, value):
        self.value = value
    
    def __str__(self):
        return repr(self.value)

try:
    raise MyEpt("some thing is wrong!")
except MyEpt as e:
    print('My exception: ', str(e))<jupyter_output>My exception:  'some thing is wrong!'<jupyter_text>9.8.2 自定义异常类的多重继承与使用<jupyter_code>class MyEpt(Exception):
    """Base class for exceptions in this module"""
    pass

class MyEptInput(MyEpt):    # 定义自定义异常类
    def __init__(self, value):
        self.value = value

class MyEptQueue(MyEpt):
    def __init__(self, value):
        self.value = value
    
    def __str__(self):      # 重载str
        return str(self.value)

for e in [MyEpt, MyEptInput, MyEptQueue]:  # 使用for循环来生成不同类型的异常
    try:
        raise e(111)
    except MyEptQueue as e:
        print("MyEptQueue：", str(e)) # 对MyEptQueue类型异步捕获
    except MyEptInput as e:
        print(repr(e))   # 对MyEptInput类型异常捕获
    except MyEpt as e:
        print(e)<jupyter_output>111
MyEptInput(111)
MyEptQueue： 111<jupyter_text>9.9 支持with语法的自定义类with语句并不适用于所有对象，能够使用with语句的对象必须有一个\_\_enter\_\_方法、一个\_\_exit\_\_方法。对于自定义类来讲，要想实现这个功能，需重载这两个方法：在\_\_enter\_\_里实现初始的操作（相当于文件对象中的打开文件句柄操作；在\_\_exit\_\_里实现结尾的清理工作（相当于文件对象中的关闭文件句柄操作）。 9.9.2 代码实现自定义类，并使其支持with语句<jupyter_code>class withClass:        # 自定义类
    def __enter__(self):    # 重载enter
        print("__enter__")
        return self   # 将实例化对象返回
    
    def __exit__(self, type, value, trace):  # 重载__exit__
        print("__exit__")
        print("type: ", value)
        print("value: ", value)
        print("trace: ", trace)
    
    def work(self):
        print("hello Python")
    
    def work_error(self):
        print("hello everyone")
        raise(ValueError('value wrong!'))

with withClass() as myclass:    # 使用with语句将函数的返回值赋给myclass
    myclass.work()              # 实现对withClass成员函数的调用

print('*'*30)

with withClass() as myclass:
    myclass.work_error()<jupyter_output>__enter__
hello Python
__exit__
type:  None
value:  None
trace:  None
******************************
__enter__
hello everyone
__exit__
type:  value wrong!
value:  value wrong!
trace:  <traceback object at 0x0000015A7B906600><jupyter_text>9.11 元类（MetaClass） 9.11.1 Class的实现原理函数type的作用是获取对象类型，Python中类class的实现，也是通过type函数来完成的。使用help函数chakantype<jupyter_code>help(type)<jupyter_output>Help on class type in module builtins:

class type(object)
 |  type(object_or_name, bases, dict)
 |  type(object) -> the object's type
 |  type(name, bases, dict) -> a new type
 |  
 |  Methods defined here:
 |  
 |  __call__(self, /, *args, **kwargs)
 |      Call self as a function.
 |  
 |  __delattr__(self, name, /)
 |      Implement delattr(self, name).
 |  
 |  __dir__(self, /)
 |      Specialized __dir__ implementation for types.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __instancecheck__(self, instance, /)
 |      Check if an object is an instance.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __setattr__(self, name, value, /)
 |      Implement setattr(self, name, value).
 |  
 |  __sizeof__(self, /)
 |      Return memory consumption of the type object.
 |  
 |  __subclasscheck__(self, subclass, /)
 |     [...]<jupyter_text>`type(object_or_name, bases, dict)`* name：类的名称。* bases：类的父类（可以是多个）。* dict：类的成员函数（可以是多个）。<jupyter_code>def fun(self):  # 定义函数
    print("hello world")

cls = type("MyClass", (object,), dict(myfun=fun))  # 使用type创建类
obj = cls()   # 实例化
obj.myfun()   # 调用类成员函数myfun<jupyter_output>hello world<jupyter_text>9.11.2 元类的介绍函数type适合于动态地创建相对简单的类。若要创建更复杂的类，则需要通过元类（MetaClass）的方式。元类，就是创建类的类。即，在创建类之后，再由类来创建实例进行应用。1. 元类的定义定义元类时，需要继承于type类；默认的命名习惯是，让类名以MetaClass结尾；在元类中需要定义并实现\_\_new\_\_方法（一定要有返回值）。使用元类来创建时，元类中的\_\_new\_\_方法将会被调用，用于生成新建的类。\_\_new\_\_方法中有以下几个必要的参数。* cls：类的对象。* name：类的名字。* bases：类继承的父类集合。* attrs：类的属性。\_\_new\_\_方法中，可以通过对attrs的修改，来实现类属性的控制。<jupyter_code>class MyClassMetaClass(type):  # 定义元类
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: print("add value %s"%value) # 添加add属性
        attrs['name'] = 'personName'  # 添加name属性
        return type.__new__(cls, name, bases, attrs)  # 通过type模块创建新的类<jupyter_output><empty_output><jupyter_text>上述示例代码定义了元类MyClassMetaClass，并添加了两个属性add与name。add为一个函数，name为一个字符串之所以继承type并实现\_\_new\_\_方法，是因为在创建类时，内部调用了type的\_\_new\_\_方法为这个类分配内存空间。当内存分配好后，便会调用type的\_\_init\_\_方法初始化2. 元类的使用使用元类创建新类时，需要在新类名后面的括号里传入元类名称，并将传入的元类名称赋值给形参metaclass。<jupyter_code>class MyClassMetaClass(type):  # 定义元类
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: print("add value %s"%value) # 添加add属性
        attrs['name'] = 'personName'  # 添加name属性
        return type.__new__(cls, name, bases, attrs)  # 通过type模块创建新的类

class MyClass(object, metaclass=MyClassMetaClass): # 通过metaclass指定该类对应的元素
    pass

obj = MyClass()  # 实例化
obj.add(55)
print(obj.name)<jupyter_output>add value 55
personName<jupyter_text>3. 元类与继承的区别元类的写法与继承很像，作用也很像。<jupyter_code>class MyClassMetaClass(type):  # 定义元类
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: print("add value %s"%value) # 添加add属性
        attrs['name'] = 'personName'  # 添加name属性
        return type.__new__(cls, name, bases, attrs)  # 通过type模块创建新的类

class MyClass(object, metaclass=MyClassMetaClass): # 通过metaclass指定该类对应的元素
    pass

print(isinstance(MyClass, MyClassMetaClass))
print(issubclass(MyClass, MyClassMetaClass))<jupyter_output>True
False<jupyter_text>4. 元类的意义* 可以更加方便的对类属性进行修改* 可以方便的编写更为高效而合理的底层框架代码。例如：“对象-关系”映射（Object Relational Mapping，ORM）就是一个典型的例子。  10. 系统调度 10.1 进程与线程进程，是应用程序的执行示例。线程，时执行进程中的路径。线程时进程的一部分。线程主抓中央处理器执行代码的过程，其余的资源的保护和管理由进程去完成。一个进程可以由一个或一个以上的线程组成。线程与进程，又可以理解为对CPU时间段的描述：线程时CPU调度的最小单位，进程是CPU资源分配的最小单位。操作系统通过线程与进程，可以将资源分配和调度分开，以实现更合理地使用CPU运算资源。 10.2 线程当一个进程里只有一个线程时，叫做单线程。超过一个线程就叫做多线程。在多线程中，会有一个主线程来玩成整个进程从开始到结束的全部操作，而其他的线程会在主线程的运行过程中被创建或退出。 10.2.1 线程的创建及原理1. 线程相关的模块Python中，两个关于线程的模块：* _thread：时Python3以前版本中thread模块的重命名。提供了低级别的、原始的线程，以及一个简单的锁。* threading：Python3之后的线程模块。2. 主线程的产生一个Python程序就是一个进程。在运行Python程序时，该进程会默认启动一个线程，即主线程。可以通过threading模块中的`current_thread`函数，来查看当前线程的示例。<jupyter_code>import threading  # 导入线程模块
print(threading.current_thread()) # 查看当前线程<jupyter_output><_MainThread(MainThread, started 2500)><jupyter_text>上面代码将`current_thread`函数的返回值打印出来，输出的信息就是`MainThread`的内容。其中，MainThread表明这是一个主线程，而started 2500是这个线程的ID号。在操作系统中，每个线程都会由一个ID号，用来作为该线程的唯一标识。threading模块中，除了current_thread，还有两个常用函数。* threading.enumerate：返回一个正运行线程的list。“正运行”是指线程处于“启动后，且在结束前”状态，不包括“启动前”和“终止后”状态* threading.acriveCount：返回正在运行的线程数量。与len(threading.enumerate())由相同的结果。Python中所由进程的主线程，名字都是一样的，即MainThread。而子线程的名字需要在创建时指定。如果不指定子线程的名字，python会为子线程添加默认名字。3. 创建子线程创建子线程有两种方法，都是通过threading.Thread类来实现的。* 直接对类threading.Thread进行实例化，并调用实例化对象的start方法创建线程* 用threading.Thread派生出一个新的子类，将新建类实例化，并调用其start方法创建线程。<jupyter_code>import threading
import time

def handle(sid):  # 线程处理函数，内部打印出当前线程的参数及名称
    print("Thread %d run"%sid, threading.current_thread())

for i in range(1, 11): # 使用循环创建多个线程
    t = threading.Thread(target=handle, args=(i, )) # 每次佳能计算器作为参数传入新建的线程中
    t.start()
    time.sleep(1)
print("main thread", threading.current_thread()) # 打印主线程信息<jupyter_output>Thread 1 run <Thread(Thread-24, started 6196)>
Thread 2 run <Thread(Thread-25, started 9752)>
Thread 3 run <Thread(Thread-26, started 5280)>
Thread 4 run <Thread(Thread-27, started 7476)>
Thread 5 run <Thread(Thread-28, started 1740)>
Thread 6 run <Thread(Thread-29, started 2900)>
Thread 7 run <Thread(Thread-30, started 15336)>
Thread 8 run <Thread(Thread-31, started 5252)>
Thread 9 run <Thread(Thread-32, started 6236)>
Thread 10 run <Thread(Thread-33, started 7348)>
main thread <_MainThread(MainThread, started 2500)><jupyter_text>4. 定义threading.Thread类threading.Thread模块的初始化定义及解释如下：`class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)`具体参数说明：* group：保留位，默认为空。为以后实现ThreadGroup（线程组）类而预留的。* target：线程要执行的函数。如果为空，则意味着没有东西可以被调用。* name：线程的名称。默认时一个类似与Thread-N形式的名字，N是十进制数字。* args：元组类型。表示在调用target时传入的参数。* kwargs={}：字典类型，即在调用target时传入的参数。* daemon：bool类型，为True时，表明该线程为守护线程，否则为False。这个值必须与主线程的身份一致。即，如果主线程不是守护线程，所有的子线程都得被设为False，否则会报RuntimeError异常守护线程是指，在进程退出时，不用等待这个线程退出。除了上述在创建线程时直接执行线程daemon属性外，还可以使用setDaemon函数来指定守护线程，但是所有的设置都需要在调用start方法之前进行。5. threading.Thread类的方法threading.Thread类提供的方法有：* run：线程活动的方法* start：启动线程活动* join：该方法有一个可选参数（timeout），用于阻塞当前上下文环境的线程，直到调用此方法的线程终止，或到达之地那个的timeout* isAlive：返回线程是否为活动的* getName：返回线程名* setName：设置线程名6. 线程的内部状态及原理线程分为5种状态：创建、就绪、运行、阻塞和退出。其内部执行过程如下：* threading.Thread实例化，就完成了线程的创建* 当程序调用start函数时，线程就会进入就绪状态，等待CPU分配时间片。* 如果分到时间片，则线程进入运行状态，执行run函数* 在run函数执行期间，线程可以被打断，进入阻塞状态* 如果阻塞结束，线程又会回到就绪状态，接着运行* 运行结束（中途退出运行也算运行结束），线程进入退出状态。7. 重载run要创建并运行一个线程，必须先调用start方法将其启动；在调用线程的run方法，使线程真正运行起来。以线程类的方式创建线程时，可以在子线程类中重载run方法，来实现线程的处理流程。<jupyter_code>import threading
import time

def handle(sid):  # 线程处理函数
    print("Thread %d run"%sid, threading.current_thread())

class MyThread(threading.Thread):  # 定义threading.Thread的派生类
    def __init__(self, sid):
        threading.Thread.__init__(self)
        self.sid = sid
    
    def run(self):
        handle(self.sid)

for i in range(1, 11):
    t = MyThread(i)
    t.start()
    time.sleep(0.5)<jupyter_output>Thread 1 run <MyThread(Thread-14, started 10472)>
Thread 2 run <MyThread(Thread-15, started 2924)>
Thread 3 run <MyThread(Thread-16, started 16052)>
Thread 4 run <MyThread(Thread-17, started 16280)>
Thread 5 run <MyThread(Thread-18, started 2840)>
Thread 6 run <MyThread(Thread-19, started 5656)>
Thread 7 run <MyThread(Thread-20, started 16224)>
Thread 8 run <MyThread(Thread-21, started 8436)>
Thread 9 run <MyThread(Thread-22, started 16096)>
Thread 10 run <MyThread(Thread-23, started 14688)><jupyter_text>将处理函数绑定到线程上的方法* 在实例化时，为其target参数赋值* 继承threading.Thread类，重载其run函数，并在其中指定。 10.2.2 互斥锁多线程的优势在于并发性，即，可以同时运行多个任务。但是当线程需要共享数据时，也可能会带来数据不同步的问题。互斥锁的作用就是解决数据不同步的问题。1. 多线程的问题<jupyter_code>import threading
import time

g_num = 1  # 公共计数器
def handle(arg):  # 线程处理函数
    global g_num
    g_num *= 2          # 计算器乘以2
    time.sleep(arg%2)   # 类似与其他操作
    print("thread ", arg, ": ", g_num)  # 输出线程对应的计数器数值

threads = []        # 建立线程列表

for i in range(1, 11):  # 线程创建
    t = threading.Thread(target=handle, args=(i, ))
    t.start()
    threads.append(t)  # 将创建的线程全都放到列表里

for t in threads:
    t.join()

print('main thread end')<jupyter_output>thread  2 :  4
thread  4 :  16
thread  thread  8 :  2566
 :  64
thread  10 :  1024
thread  1 :  1024
thread  3 :  1024
thread  5 :  1024
thread  7 :  1024
thread  9 :  1024
main thread end<jupyter_text>上述示例中，线程的顺序与计数器的值完全对不上。造成这种现象的原因是：当一个子线程调用sleep进行休眠时，其他线程会接着修改原有的公共计数器。这导致，休眠结束后取得的那个公共计数器的值与原来不一样了。这就是数据的不同步。2. 互斥锁锁的出现，主要就是为了解决多线程间的问题。其核心的概念意思是：将执行程序中的某段代码保护起来（相当锁起来），被锁起来的代码一次只能允许一个线程执行。利用这个概念，“将上面的公共计数器乘以2”与输出公共计数器的操作锁起来，就可以实现多线程的同步了。Python中，使用threading.RLock类来创建锁。threading.RLock类有两个方法——acquire与release。* acquire负责开始对代码进行保护。在acquire之后的代码，都将只允许一个线程进行执行。* release方法用于停止保护（即释放锁资源）。在release之后的代码又恢复到原来的样子，可以被多线程交叉执行。<jupyter_code>import threading
import time

g_num = 1                       # 公共计数器
lock = threading.RLock()        # 锁的实例化
def handle(arg):                # 线程处理函数
    lock.acquire()              # 加锁
    global g_num
    g_num *= 2
    time.sleep(arg%2)
    print("thread ", arg, ": ", g_num)
    lock.release()              # 解锁

threads = []

for i in range(1, 11):
    t = threading.Thread(target=handle, args=(i, ))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print('main thread end')<jupyter_output>thread  1 :  2
thread  2 :  4
thread  3 :  8
thread  4 :  16
thread  5 :  32
thread  6 :  64
thread  7 :  128
thread  8 :  256
thread  9 :  512
thread  10 :  1024
main thread end<jupyter_text>**注意**：从系统的角度来看，锁的作用其实是将多线程变回到了单线程。这是以牺牲性能，来换取程序的准确性。在代码设计中，应该最大化地避免使用锁。即使加了锁，也要让被保护的区域尽量地少，在满足准确性的同时实现性能最大化。在代码中，有“加锁”操作，就一定要有与之对应的“解锁”操作，否则代码将失去多线程的优势。 10.2.3 使用信号量来同步多线程间的顺序关系**信号量（semaphore）**是一种带计数的线程同步机制。调用release函数时，计算器加1；调用acquire函数时，计数器减1.当计数为0时，线程会自动阻塞，等待release被调用。Python中存在两种信号量：一种是纯粹的信号量（Semphore），另一种是带有边界的信号量（BoundedSemaphore）。二者的区别如下* Semphore：在调用release函数时，单纯地将计数加1。不会去检查加1后的计数器是否超过上限。* BoundedSemaphore：在调用release函数时，会去检查计数器加1后是否超过上限。对计数器的上限进行校验。是一个更加安全的机制。在多线程环境下，线程数据与系统的资源数据相匹配，才会使整体性能最优。信号量的作用就在于此。系统通过信号量来维护着一个计数器。该计算器代表了，可同时访问资源或者进入临界区的线程数。有了信号量的限制机制，整个软件系统的性能更加稳定。1. 信号量实现信号量，是通过threading中的Semaphore方法来实现的（边界信号量时通过BoundedSemaphore方法来实现的）。使用时，需要将信号量的个数传入即可。<jupyter_code>import threading
import time
import random

semaphore = threading.Semaphore(0)      # 创建信号量

def consumer():     # 定义消费者
    print("consumer: 挂起")
    semaphore.acquire()
    print("consumer: 消费 %s."%item)

def producer():     # 定义生产者
    global item     # 定义商品编号
    time.sleep(3)
    item = random.randint(1, 1000)  # 产生随机数
    print('producer: 生产%s.'%item)
    semaphore.release()

threads = []    # 定义列表收集线程

for i in range(0, 2):
    t1 = threading.Thread(target=producer)
    t2 = threading.Thread(target=consumer)
    t1.start()
    t2.start()
    threads.append(t1)
    threads.append(t2)

for t in threads:   # 等待所有线程完成
    t.join()<jupyter_output>consumer: 挂起
consumer: 挂起
producer: 生产444.
consumer: 消费 444.
producer: 生产21.
consumer: 消费 21.<jupyter_text>前两行输出了“consumer: 挂起”，这表示线程刚启动时，消费者线程进入到“挂起”状态。生产者使用random.randint函数生成随机数，并赋值给全局变量item。3. 使用边界信号量如果要使用边界信号量BoundedSemaphore，将上述示例代码中的`threading.Semaphore(0)`替换成`threading.BoundedSemaphore(1)`即可。这里的参数不能使用0。否会报`ValueError: Semaphore released too many times`。这个错误表明，信号量的上限超过了上限设置值0。另外，BoundedSemaphore的起始值设为了1，表明第一个消费者线程是可以消费item的。因为这时没有item的定义，所以还需要将全局变量item的设置放在外部，来保证程序运行不会报错。同时为了保证程序程序内容的准确性，可以给item初始值一个正常编号（1~1000）以外的书。然后对item的取值进行判断，过滤掉初始值。 10.2.4 实现基于事件机制的消息队列事件机制，是线程间最简单的通信机制。它使得线程或是进程间的同步，可以通过事件告知的方式来实现。在Windows操作系统中，消息通知机制也是以事件的方式来实现的。1. 事件实现Python中，事件的实现使用了threading模块中的Event类。该类有三个方法* set：设置事件。将标志位设为True* wait：等待事件。会将当前线程阻塞，知道标志位变为True* clear：消除事件。将标志位设为False事件，可以通过Event类的实例化来实现。可以使用上面3个方法，来实现整个事件的生命周期。2. 代码实现实现两个线程类WriteThread、ReadThread，分别用于队列的写和读。两个线程类的初始化函数传入共同操作的队列（q）、读事件（RE）、写事件（WE）。* 在写队列类（WriteThread）中，随机生成5个1~10之间的数并传入队列，并进行对读取事件（RE）的通知，之后等待写事件的到来。* 在读队列类（ReadThread）中，等待读事件（RE）的通知。当收到事件后，开始读队列，并通知写事件（WE）<jupyter_code>import queue    # 引入队列模块
from random import randint
from threading import Thread, Event

class WriteThread(Thread):      # 写数据线程
    def __init__(self, q, WE, RE):
        super().__init__()
        self.queue = q
        self.RE = RE
        self.WE = WE

    def run(self):
        data = [randint(1, 10) for _ in range(0, 5)]
        self.queue.put(data)
        print("WriteThread 写队列：", data)
        self.RE.set()       # 通知读线程可以读了
        print("WriteThread 通知读事件")
        print("WriteThread 等待写事件")
        self.WE.wait()      # 等待写事件
        print("WriteThread 收到写事件")
        self.WE.clear()     # 清除写事件，以方便下次读取


class ReadThread(Thread):       # 读数据线程
    def __init__(self, q, WE, RE):
        super().__init__()
        self.queue = q
        self.RE = RE
        self.WE = WE

    def run(self):
        while True:
            self.RE.wait()       # 等待读事件
            print("ReadThread 收到读事件")
            data = self.queue.get()
            print("ReadThread 读队列 {0}".format(data))
            print("ReadThread 发送写事件")
            self.WE.set()
            self.RE.clear()


q = queue.Queue()
WE = Event()
RE = Event()
writethread = WriteThread(q, WE, RE) # 实例化线程类
readthread = ReadThread(q, WE, RE)

writethread.start()
readthread.start()<jupyter_output>WriteThread 写队列： [9, 7, 2, 9, 10]
WriteThread 通知读事件
WriteThread 等待写事件
ReadThread 收到读事件
ReadThread 读队列 [9, 7, 2, 9, 10]
ReadThread 发送写事件
WriteThread 收到写事件<jupyter_text>线程启动之后，执行的过程如下：* 写线程writethread启动后，在类WriteThread中，进行写队列操作* 读线程readthread启动后，在类ReadThread中，是哦那个事件RE的wait方法等待该事件，线程一直处在挂起状态。* 写线程writethread做完写入队列操作之后，通过事件RE的set方法，发出一条可读事件* 程序挂起，等待写事件的到来。* 读线程收到了WriteThread中发出的读事件* 读线程类ReadThread开始进行读队列操作。* 发出写事件，通知写线程类WriteThread。* 在读线程类ReadThread中，收到写事件，并打印。 10.2.5 使用条件锁同步多线程中的生产者与消费者关系条件锁，是线程同步中更高级的一种应用。它是在保护互斥锁的基础之上，又增加了条件判断的机制。在判断当前情况不满足某条件时，条件锁可以主动挂起当前程序，等待条件准备好之后再去执行。1. 条件锁实现python中条件锁的实现，使用了threading模块中的Condition类。它也有acquire方法和release方法，而且患有wait、notify和notifyAll方法。* wait：挂起当前线程，等待唤起* notify：唤起被wait函数挂起的线程* notifyAll：唤起所有线程，防止线程永远于沉默状态2. 代码实现<jupyter_code>from threading import Thread, Condition
import time
import random

c = Condition()  # 创建条件锁
itemNum = 0
item = 0
def consumer():     # 定义消费者
    global item     # 定义商品编号
    global itemNum  # 定义商品个数
    c.acquire()     # 锁住资源
    while 0 == itemNum: # 如无产品则让线程等待
        print("consumer: 挂起。")
        c.wait()
    itemNum -= 1
    print("consumer: 消费 %s."%item, itemNum)
    c.release()     # 解锁资源

def producer():     # 定义生产者
    global item
    global itemNum
    time.sleep(3)
    c.acquire()     # 锁住资源
    item = random.randint(1, 1000)
    itemNum += 1
    print("producer: 生产 %s."%item)
    c.notifyAll()   # 唤醒所有等待的线程（消费者）
    c.release()     # 解锁资源

threads = []

for i in range(0, 2): # 使用循环完成生产者与消费者线程的建立
    t1 = Thread(target=producer)
    t2 = Thread(target=consumer)
    t1.start()
    t2.start()
    threads.append(t1)
    threads.append(t2)

for t in threads:
    t.join()<jupyter_output>consumer: 挂起。
consumer: 挂起。
producer: 生产 170.
consumer: 消费 170. 0
consumer: 挂起。
producer: 生产 540.
consumer: 消费 540. 0<jupyter_text>10.2.6 创建定时器触发程序，在屏幕上输出消息1. 创建定时器Python中的定时器，是使用threading模块下的Timer类来实现的。它本质上还是线程。只不过多了一个时间参数。定义如下：`class threading.Timer(interval, function, args=[], kwargs={})`参数的说明如下：* interval：触发定时器的时间* function：定时器到时间后的处理函数* 其他：传入处理函数的参数需要注意的是，定时器的处理函数是可以传入参数的。定时器在实例化之后，便可以像线程一样通过start方法来运行。2. 单次触发定时器<jupyter_code>import threading
import time

def timer_headle():     # 定时器触发函数
    print("Timer headle!")

timer = threading.Timer(0.5, timer_headle)  # 实例化定时器，1s后执行线程处理函数
# timer.start()<jupyter_output>Timer headle!
Timer headle!
Timer headle!
Timer headle!
Timer headle!
Timer headle!<jupyter_text>3. 循环触发定时器在定时器的触发函数里，再创建一个定时器，即可实现定时器的循环触发。<jupyter_code>import threading
import time

def loop_timer_headle():
    print("Timer headle!")
    global timer2
    timer2 = threading.Timer(1, loop_timer_headle)
    timer2.start()

timer = threading.Timer(1, loop_timer_headle)
# timer.start()<jupyter_output><empty_output><jupyter_text>4. 为循环触发定时器加上时间限制在threading.Timer中，可以使用cancel方法来退出定时器。```time.sleep(10)timer2.cancel()``` 10.2.7 使用线程池提升运行效率线程池是一种多线程处理形式，是在正常的多线程处理方式上的一种优化。* 正常线程使用方式是：创建线程，启用，结束，销毁。* 线程池的处理方式是：在程序启动时就创建好若干个线程，并保存到内存中。当线程启动并执行完成之后，并不做销毁处理，而是等待下次再使用。这样内存中的线程就像个池子一样，需要用时过来取，用完了再还回去。需要频繁创建线程的系统中，一般都会使用线程池技术。原因有两点：* 每一个线程的创建。都是需要占用系统资源的，是一件相对耗时的事情。同样，再销毁线程时，还需要回收线程资源。线程池技术，可以省去创建与回收过程中所浪费的系统开销。* 再某些系统中，需要为每个子任务来创建对应的线程（例如爬虫系统中的子链接）。这种情况会导致线程数量失控性暴涨，知道程序崩溃。线程池技术可以很好地固定线程的数量，保持程序稳定。1. 实现线程池使用concurrent.futures模块下的ThreadPoolExecutor类来实现线程池。再实例化时，会将需要的线程个数传入。系统就会为该线程池初始化相应个数的线程。线程池的使用有两种方式。* 抢占式：线程池中的线程执行顺序不固定。该方法使用ThreadPoolExecutor的submit方法实现* 非抢占式：线程将按照调用的顺序执行。此方法使用ThreadPoolExecutor的map方法来实现从使用角度来看：抢占式更灵活；非抢占式更严格。* 抢占式：允许池中线程的处理函数不一样。如执行过程中某个线程出现异常，也不影其他线程* 非抢占式，要求线程池中的线程必须执行同样的处理函数。而且，一旦某个线程出现异常，其他线程也会停止。<jupyter_code>from concurrent.futures import ThreadPoolExecutor
import time

def printperson(p):     # 定义线程池处理函数
    print(p)
    time.sleep(2)

person = ["Anna", "Gary", "All"]
start1 = time.time()        # 获取开始时间
for p in person:
    printperson(p)
end1 = time.time()          # 获取结束时间
print("time1: ", str(end1-start1))<jupyter_output>Anna
Gary
All
time1:  6.005425930023193<jupyter_text>3. 实现抢占线程池使用with关键字来创建线程池，调用实例化对象的submit方法将线程启用。<jupyter_code>from concurrent.futures import ThreadPoolExecutor
import time

def printperson(p):     # 定义线程池处理函数
    print(p)
    time.sleep(2)

person = ["Anna", "Gary", "All"]
start1 = time.time()        # 获取开始时间

with ThreadPoolExecutor(3) as executor:     # 创建抢占式线程池，池内有3个线程
    for p in person:
        executor.submit(printperson, p)     # 启用线程

end1 = time.time()          # 获取结束时间
print("time1: ", str(end1-start1))<jupyter_output>Anna
Gary
All
time1:  2.0197529792785645<jupyter_text>4. 实现非抢占线程池非抢占池也是使用with关键字来创建的。不同的是，非抢占线程池是通过调用实例化对象的map方法来启用线程。<jupyter_code>from concurrent.futures import ThreadPoolExecutor
import time

def printperson(p):     # 定义线程池处理函数
    print(p)
    time.sleep(2)

person = ["Anna", "Gary", "All"]
start1 = time.time()        # 获取开始时间

with ThreadPoolExecutor(3) as executor:
    executor.map(printperson, person)

end1 = time.time()          # 获取结束时间
print("time1: ", str(end1-start1))<jupyter_output>Anna
Gary
All
time1:  2.0164377689361572<jupyter_text>10.2.8 需要创建多少个线程才算合理多线程的并发机制，虽然可以提升程序效率，但线程个数也不是越多越好。如要找到更优的线程数数量，可以使用如下方法：* 初始化一定数量的线程* 在多次实验中递增或递减线程数量，测试运行性能* 确定最优的线程数据。服务器最佳线程数量 = (线程等待时间 + 线程CPU时间) / 线程CPU时间 * CPU数量 10.3 进程进程，就是操作系统中具体的处理任务。每一个进程都会有自己独立的内存空间。它是线程的载体。 10.3.1 创建多进程的程序进程相关的实现被同意封装在模块`multiprocessing`中。进程的创建方式有两种：* 继承 `multiprocessing`中的Process类，并重写run方法* 直接通过`multiprocessing`中的进程类Process进程创建。类似线程创建，在编写程序对Process类实例化时，直接将进程的处理函数传入即可。在Process类的实例化参数中，也是通过target来接收处理函数的。1. 使用继承类的方式创建进程实现自定义进程类myproc，继承于系统进程类Process。重写run方法，实现进程逻辑。在run方法中，调用sleep函数让程序挂起一段时间。调用os模块getpid方法，得到当前进程ID```from multiprocessing import Process   导入multiprocessingimport timeimport randomimport osclass myproc(Process):   继承Process类，并实现自己的run方法    def __init__(self, name):        super().__init__()   调用父类的init方法        self.name = name        def run(self):        print("%s start"%self.name, os.getpid())        time.sleep(random.randint(1,3))        print("%s end"%self.name, os.getpid())if __name__ == '__main__':    p1 = myproc("test")      实例化子进程并传入参数    p1.start()               启动进程    print("主进程！", os.getpid())    p1.join()    print('主进程结束.')```打开cmd执行下面命令```C:\Users\admin\Desktop>python a.py主进程！ 13272test start 5096test end 5096主进程结束.```2. 使用类的实例化方式创建进程定义进程处理函数run_proc。在实例化Process时，将run_proc传入。接着使用实例化对象p的start方法启动进程。具体代码如下：```from multiprocessing import Process   导入multiprocessingimport timeimport randomimport osdef run_proc(name):    print("%s start"%name, os.getpid())    time.sleep(random.randint(1,3))    print("%s end"%name, os.getpid())if __name__ == '__main__':    p = Process(target=run_proc, args=('test', ))   实例化    print("主进程！", os.getpid())    p.start()    p.join()    print("主进程结束。")```执行过程：```C:\Users\admin\Desktop>python a.py主进程！ 1072test start 11444test end 11444主进程结束。```  10.3.2 多进程与多线程的区别多进程与多线程，都可以使用并行机制来提升系统的运行效率。二者的区别在于；运行时所占的内存分布不同* 多线程是共用一套内存的代码块区间* 多进程是各用一套独立的内存区间 10.4 协程协程（coroutine），可以理解为是线程的优化，又称为轻量级进程。它是一种比线程更节省资源、效率更高的系统调度机制。它的特点是，在同时开启的多个任务中，一次只执行一个。如果当前任务遭遇阻塞，才会切换到下一个任务继续执行。这种机制可以实现多任务的同步，又能够成功的避免线程中使用锁的复杂性。 10.4.1 协程的相关概念及实现步骤以`asyncio`为例，创建协程所用的概念如下：* event_loop(事件循环)：是一个协程处理函数的调用机制。程序会开启一个无线循环，当事件发生时，调用响应的协程函数。* coroutine(协程对象)：指一个使用async关键来定义的函数。调用该函数，会返回一个协程对象。该协程对象就是一个处于挂起状态的协程函数，需要注册到事件循环`event_loop`中，由事件循环`event_loop`进行调用。* task任务：是对协程的进一步封装。* future：等同于task。代表执行任务的结果。* async/await关键字：关于定义协程的关键。async用于定义一个协程，await用于挂起阻塞的异步调用接口。2. 协程的实现步骤 ```import asyncio           引入模块async def do_something(x):       定义协程处理函数    print(x)coroutine = do_something('hello')    生成协程对象loop = asyncio.get_event_loop()      获得事件循环对象try:    loop.run_until_complete(coroutine)  将协程注册到实现事件循环对象中，并开始运行finally:    loop.close()     程序结束关闭事件循环对象 ```执行结果： ![image.png](attachment:image.png) 实现协程的基本步骤：* 使用async关键字定义协程处理函数* 生成协程对象* 调用asyncio中的get_event_loop函数获得事件循环对象* 调用事件循环对象的run_until_complete方法，运行协程处理函数 10.4.2 使用协程实现“任务提交”与”结果接收”协程的特性是，按顺序执行、通过挂起的方式在并行任务切换。这些特征会被用在批处理任务中。1. 封装及回调协程的任务在得到协程对象与事件循环对象后，有两种创建任务的方式* 使用事件循环对象的create_task方法* 使用asyncio模块下的ensure_future函数。得到任务对象后，可以调用该任务对象的**add_done_callback**方法来实现回调函数的绑定。等任务执行完后，系统会自动将任务的结果传入回调函数中的future参数，通过future的result方法可得到结果2. 代码实现```import asyncioasync def do_some_work(x):             定义协程处理函数    print('任务： ', x)    return "任务：{}的返回结果".format(x)def callback(future):         回调函数    print('Callbakc: '， future.result())         返回任务结果coroutine = do_some_work('爬虫当前股票')     定义协程，并传入任务loop = asyncio.get_event_loop()              获得事件循环对象task = asyncio.ensure_future(coroutine)      获得任务对象task.add_done_callback(callback)             封装好后的协程对象（任务）就可以绑定回调函数loop.run_until_complete(task)                执行协程任务```执行结果：![image.png](attachment:image.png)  10.5 线程与协程比较与线程相比，协程封装的内容更多，实现起来更容易在一般情况下优先选择协程，但有一些需要注意的点：* 协程是多任务的顺序执行，只有当前任务挂起后，才会切换到其他任务来执行* 线程是以CPU轮训的方式执行。  使用协程批量修改文件扩展名```import osimport asyncioasync def change_files(x):           协程处理函数    files = os.listdir("D:/test")    列出当前目录下所有的文件     for filename in files:        portion = os.path.splitext(filename)    分离文件名字和后缀        print(portion)        if portion[1] == '.txt':   先根据后缀来修改，如无后缀则空            newname = portion[0] + ".py"   要修改的新后缀            os.chdir("D:/test")    切换文件路径，如无路径则要新建或者路径同上，做好备份            os.rename(filename, newname)    return '{}任务完成'.format(x)def callback(future):    回调函数    print("Callback: ", future.result())   返回任务结果coroutine = change_files("修改扩展名")       定义协程，并传入任务loop = asyncio.get_event_loop()              获得事件循环对象task = asyncio.ensure_future(coroutine)      获得任务对象（对协程的封装）task.add_done_callback(callback)             封装好后的协程对象（任务）就可以绑定回调函数loop.run_until_complete(task)```![image.png](attachment:image.png)<jupyter_code>import numpy as np
import matplotlib.pyplot as plt


# 样本准备
train_X = np.linspace(-1, 1, 100)
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.3 # y=2x但是加入了噪声

# 显示模拟数据点
plt.plot(train_X, train_Y, 'ro', label="Orginal data")
plt.legend()
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16524626452684008, 'fraction_numerical': 0.034038000368935624, 'mean_word_length': 5.486980662454528, 'pattern_counts': {'":': 10, '<': 355, '<?xml version=': 0, '>': 378, 'https://': 1, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '91', 'dir': 'jupyter-structured-clean-dedup', 'id': '518359', 'license': 'MIT', 'n_tokens_mistral': 81881, 'n_tokens_neox': 88297, 'n_words': 8519, 'path': 'python/jupyter notebook/1-python3学习.ipynb', 'repo_name': 'Ljazz/studyspace', 'size': '135535'}