本文主要是介绍六 Python 自学进阶,如果想要打牢基础,应该收藏它,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1 前言
2 最简单的输入
3 数据类型的进阶
4 列表
5 元祖
6 字典
7 集合
8 语法结构的进阶
9 异常捕获
10 能打破循环的 break 和 continue
11 迭代的概念,迭代器
12 生成器
1 前言
随着后面内容的深入,我们将逐渐的去建立软件开发知识构架,在这个过程中也把Python编程语言掌握好。目前我们生活的世界,几乎离不开软件。交通、支付、医疗各行各业都离不开软件行业的支持。那么多的软件,他们背后有没有什么统一的东西呢?
还真有! 直接看下图:
上面这张图展示了所有软件都需要遵循的模型。
-
UI系统:负责信息的输入(用户的操作),输出(各种结果或者图标),比如游戏界面,我们平常访问的网页,手机里APP看得见能操作的界面。
-
业务逻辑:根据一些要求来处理用户输入的信息,比如微信就把用户输入的信息发送给另外一个信息接受者。
-
数据存储系统:把用户与软件系统的交互记录(比如,消费记录),业务数据(比如,订单信息)等信息永久地保存下来。
也就是说,想要做一个能独立开发软件的工程师,需要上面三个方面的知识。这三个方面的知识,都需要软件工程师有扎实的编码能力。
本来这个图我是想把它放到讲具体开发实例设计的时候再放出来,但由于后面的学习若要讲解的更舒服,我们需要大家学会在UI系统里,最简单的输入信息操作,也就是我们可以写一些具有简单交互的程序了!
现在我们就来看看Python内置的输入函数。
2 最简单的输入
Python 内置给我们提供了一个强大的输出函数 print()的同时也给我们提供了一个功能强大的 input()函数,我们直接来看一下它的使用方法:
我们在Pycharm里运行一下这段代码,会看到这样的结果:
运行后,系统提示我们 “输入你想说的话”,然后:
我们就输入了想说的话,回车后系统把我们想说的话输出到了屏幕上。这就是最简单输入的例子,在大部分实际开发项目中input()函数几乎用不到,我们会开发更复杂界面供用户使用。那它存在的意义是什么? 方便我们更便捷的学习Python!
我们再回过头来看一下上面的例子:
虽然只有两行代码,但已经是有输入,有输出的软件了。若再丰富一下代码,让程序变得可以保存信息,那么这段代码,可以视为是一个标准的软件系统了。
3 数据类型的进阶
前面为大家介绍了Python的数字型(Number)数字类型,字符串(String)数据类型,布尔(Bool)数据类型,它们三个叫做基础数据类型。在一些实际的需求当中,我们不但要靠基础的数据类型来构建系统,甚至大部分时候,还需要用复合数据类型来丰富我们的系统。
符合数据类型 = 基数数据类型 + 特定的数据结构和用法
Python 给我们提供了,列表、字段、元祖、类、自定义类等 符合数据类型,它们能高效的帮助软件开发工程师们解决实际问题,下面我们先介绍列表、字典、元祖,在后面的大章节我们专门来说一说类 这种符合数据类型。
4 列表
列表是最常用的符合数据类型,在具体讨论它的时候,我们先来看一下这个问题:
“如何来保存 一个彩票号码,并且能逐个的输出这些号码?”
其实这个问题很简单,根据之前的知识,我们第一反应想到的代码就是:
确实 lottery 这个变量通过字符串的形式,把7个彩票号码保存的好好的。但如果我们再要基于这个变量来做下面的操作就有点麻烦了(不是不能做,是麻烦,其实我建议小伙伴们思考一下,利用前面的知识如何解决下面的问题?):
-
逐个的输出彩票号码
-
替换一些号码
-
给号码球和
若这个时候,使用Python提供给我们的 列表(list) 复合数据类型,那么这个问题就变简单了。直接来看下面的代码:
我们来逐行看一下这些代码:
-
第2行代码,我们用 “,” 逗号 把 3,6,12,17,24,32,10 这 7个数字隔开,再用 “[]” 中括号把它们括起来,赋值给了num_list 这个变量,那么这个变量就是 列表(list)类型的变量。也就是说在Python里,定义一个列表(list)变量使用 “[]” 中括号来操作的,“[]” 中括号里的列表元素或单元,用 “,” 逗号隔开。
-
第5,6行,我们使用了 for 循环,把 num_list 这个列表变量的元素全部打印出来。
-
第9,10行,我们用下标的方式,把列表的第2个元素和第5个元素的值换成了8,25。
-
第12,13行,我们又使用了for循环,把 num_list 这个列表变量的元素全部打印出来。
-
第16、17、18行,我们使用for循环,把 num_list 里的元素进行了个求和。
-
第20行,输出求和结果。
列表的创建
列表的介入让我们解决上面的问题变简单了。在上面的代码里,第2行是重中之重,它给出了在Python里如何去定义一个列表的最基础写法。当然列表是一个很灵活的工具,为什么这么说呢?我们先看一下代码:
我们来逐行解读上面的代码:
-
还记得创建列表用的语法吗? 使用 “[]” 中括号就可以告诉Python,我要创建一个列表。第3行我们创建了一个空列表,就是什么也没有的列表。在实际开发中,我们会创建空列表,为了把后面计算好的结果放进去。
-
第6行,我们创建了一个数字型列表,然后把它赋值给 number_list。
-
第9行,我们创建了一个字符串列表,然后把它赋值给了 string_list。
-
第12行,我们创建了一个混合列表,然后把它赋值给了mixture_list。在这里可以发,列表里不仅仅可以放数字类型、还可以放字符串类型,布尔类型,一个函数 "print()",一个迭代对象 “range(1000)”。其实列表里,可以放任何的东西,只要它是Python语言认可的!
上面例子创建的列表都属于一次元列表,列表中的数据都在一条“线”上。假如我们遇到这样的问题,“要用列表(list)来保存我好朋友的信息该怎么办?” 显然这个问题要使用混合列表的方式来组织数据,具体有没有好的实践?
二次元列表
我们先来看解决上面问题的代码:
上面是二次元列表的经典例子,它的实现很简单,就是列表套列表。
-
第3~7行代码,我们创建了5个混合列表。
-
第10行,我们把这5个混合列表放到了 table_friend 这个列表里面。
-
第13~17行,我们使用两个 for 循环来遍历这二位列表(初学者可能看这段代码会有点吃力,多看几几遍就适应了)。
按照上面的思路,可以组织3次元,4次,5次,6次~~~~等等。其实关于多次元数据结构(矩阵)的创建和操作,可以使用Python强大的第三方库numpy。关于什么是第三方库?如何使用?后面的课程会为大家介绍。还有一个问题,list列表可以放多少元素?
-
在32位的电脑里,List可以放 536870912 个元素。
-
在64位的电脑里,List可以放 1152921504606846975 个元素。
好了,创建好列表后我们来看看如何操作它。
列表的操作
列表最常用的场景,就是查看它里面某个元素是什么东西,或者遍历整个列表(查看列表的所有元素),我们先来看看下面代码:
我们来逐行解读:
-
第3行,创建了一个名词的列表,word_list。
-
第6行,我们打印了 word_list 的 index(序列) 为 2的元素。
-
第7行,我们打印了 word_list 的 index(序列) 为 3的元素 。
-
第10~11行,我们把 word_listl 列表 index 为3 的元素换成了 “动物”。
-
第14~15行,我们遍历了 word_list 列表 index 为3~5的元素。
-
第18-19行, 我们遍历了 word_list 列表 index 为2以后的元素。
-
第 22~23行,我们遍历了 word_list 列表 index 倒数第1~倒数第2,这几个元素。
Index 是怎么计算的呢,我们看下图:
-
如果正着数,index 的第一位 0,然后逐渐加一,直到列表结束。
-
如果是倒着数,index 的最后一位是 -1,然后逐渐-1,直到列表开头。
这样设计的好处就是让软件工程师,可以通过 index 来定位列表里的元素。上面的这张图,是不是有点像在学习字符串数据类型时候,index(序列)的计算方式?
其实,字符串数据类型,和列表数据类型有很多相似的东西,比如它们都可以使用“切片技术”来查看片段内容(例子:[5:21])。但需要注意,字符串和列表,它们在Python世界里,是不一样的东西,我们不能混淆。
我们来总结一下,在Python里,可以使用 [100:520 ] 的语法来实现 “切边技术”,快速对数据进行划分的对象有什么:
-
字符串: “hello word !”
-
列表:[12,32,43,5,767,21]
-
迭代对象 range(100)
现在我们来看一下这个问题:
假如,我们有一个列表,它维护的是你的英语单词本,在平常英语的学习中,你会对这个本子(列表),进行添加新单词、排序、删除一些单词、统计单词数量等等操作,那我们在写 Python 代码时该怎么办呢?
列表的操作
Python 给我们提供了非常实用的列表运算符、操作函数和方法(关于函数和方法的概念后面有细说),我们直接来看下表:
列表的运算:
序号 | 与运算符结合 | 说明 |
---|---|---|
1 | + | 两个列表可以相加,[1,2,3] + [4,5,6] = [1,2,3,4,5,6] |
2 | * | 一个列表可以乘于一个整数 ["hello"]*4 = ["hello","hello","hello","hello"] |
3 | in | 成员运算符,比如 3 in [1,2,3] = True。但 3 in [21,23,5] = False 。这个运算符在判断一个元素是否在列表里的时候特别好用。 |
4 | for i in [1,2,3,4,5,6]: | 在 for 循环的时候使用,这种用法大家应该不陌生了,需要注意的是。这里的 in 和上面的 in 语义不一样。前者就是判断,“我在不在一个列表里?” 后者是 “我把列表里的元素一个一个的取出来”。 |
列表的函数
序号 | 函数 | 说明 |
---|---|---|
1 | len(list) | 计算一个 list 的长度 |
2 | max(list) | 计算出一个 list 里最大的值 |
3 | min(list) | 计算出一个 list 里最小的值 |
4 | list() | 把一个可以 列表化的对象,列表化 |
下面是一些有趣的例子:
看到这里,我相信有的小伙伴在思考了,max,min这两个内置函数,能不能用在字符串列表和混合列表里? 不如我们现在打开交互器,试一试?
从上面的实验可以看出,纯字符串的列表,是可以通过 max(),min()函数计算出结果来的。它的计算方式和我们之前讲解的 字符串 比大小问题类似 (穿越到过去查看该问题)。但如果是混合的 list 要使用 max(),min()函数,就会引发一个 TypeError 的错误。结论已经显而易见了。
关于 list() 这个函数,根据官方文档说明,只要是可以迭代的对象(后续内容会为大家解释,什么叫可迭代的对象),都可以使用 list() 这个函数来列表化,我们打开交互器再试试:
上面的实验中,“hello word” 和 range(10),它们都是可以迭代的对象,所以可以列表化,但 98 这个数字类型不是迭代对象,所以尝试 list(98) 的时候,Python 交互器,提示我们 'int' object is not iterable。
列表的方法
在前面的内容,就提到过函数和方法,对于初学者来说可能会把他们搞混,毕竟他们用法差不多,功能差不多。为了在后面的学习中,为了让大家不被函数和方法这两个东西混淆,这里先简单做一个简单的介绍。
函数这个概念最早来源于第二代编程语言(C语言等),它就是一段代码的封装,有输入,有输出,工程师们不需要知道它里面的原理和代码,只需要知道直接输入什么,然后会得到什么就可以了。比如 Len([1,2,3,4,5,6]),输入一个列表,得到列表的长度,中间发生了什么我们不需要知道。
根据最早我们对软件模型的定义,函数可以视为软件里面的软件。方法,是函数概念的扩展,它的作用和工作原理与函数都一样。只是方法需要依托于一个对象,那什么是对象呢?像Python这样面向对象的编程语言(OOP),所有变量均可视为是一个对象。好了,对于函数与方法的概念和知识目前先了解到这里就可以了,后面我们有专门的章节聊这个。
现在来看列表的方法(常用):
序列 | 方法 | 说明 |
---|---|---|
1 | list.append(obj) | 在列表的末尾加一个对象 |
2 | list.count(obj) | 统计某个对象在列表里的数量 |
3 | list.extend(obj) | 在列表末尾加一个其他列表,其中 obj 指的是可以迭代的对象。 |
4 | list.index(obj) | 查看这个对象在列表里的Index |
5 | list.insert(index,obj) | 在指定位置插入一个对象 |
6 | list.pop(index=-1) | 移除一个列表里的对象 |
7 | list.remove(obj) | 在列表里面移除一个指定对象 |
8 | list.reverse() | 翻转列表 |
9 | list.sort(key=None,reverse=False) | 给列表排序 |
10 | list.clear() | 清空列表 |
11 | list.copy() | 复制一个新的列表出来 |
上面一共11种常用的 list 列表方法,表中的 list.XXXXX() 是一种使用方法的 “公式”。Python的官方文档就是以这种方式来向大家介绍各种函数、方法该如何使用的。
在 “公式” 里,list 就代表了各种列表类型,它只要是一个列表对象就行,比如: word_list , [ "hello" , "word" , "!!!" ] 等。 “.” 是告诉 Python 我们要使用这对象里的某个方法, “.” 后面的 XXXXXX 就是这个方法的名称,方法名称后面 “()”圆括号内,代表的是我们需要给这个方法提供什么样的 参数,熟悉这种方式对我们后面的学习有很大的帮助。
现在我们来逐个看一下这些方法:
list.append(obj):这个方法是给一个列表对象末尾添加一个任意对象,看下面列子:
我们先通过 list_1.append(obj) 这个方法,给 list_1 这个列表追加了一个字符串,然后又给它追加了一个布尔类型的值 True ,当我们给 list_1 追加 [11,12,13,14,15] 的时候,需要注意!我们得到的结果并不是:
[1,2,3,4,5,6,"添加一个字符串",True,11,12,13,14,15 ]
而是
[1,2,3,4,5,6,"添加一个字符串",True,[11,12,13,14,15] ]
也就是说, list_1.append([11,12,13,14,15]) 不是把参数里的整数列表拼接到 list_1 里,而是把这个整数列表当成是个整体,放到列表最后面(追加)。很多程序员都会把这里搞混,写出一些不好排查的BUG。
最后一个例子是为 list_1 列表 追加了一个 range(5) 的迭代对象 。我们发现追加完后,list_1 列表里面追加的值并不是 [1,2,3,4,5] , 而是 range(5)迭代对象。这是因为迭代对象本身的特点,只有当迭代对象里面的元素被使用时,它才会展开计算,形成一个列表。Python 这样设计是为了提高程序代码执行的效率,我们也把这种策略叫做 “节省开销”。
在玩英雄联盟的时候,一个英雄会受到来自不同单位的各种攻击,系统就是通过类似 list 这样的数据类型来记录英雄被受到的伤害,然后通过 list.append(obj) 这样的方法来记录英雄受到的新伤害。
list.count(obj): 统计某个对象在列表里的数量。我们直接来看代码:
我们创建了 list_2 这个数字型的列表,然后通过 .count(obj)这个方法来看看,8 ,21 这两个整数在列表里有多少个? 这个方法很简单,值得注意的是,如果我们不给参数 list_2.count() Python交互器会给我们错误提示。
list.extend(obj):在列表末尾,添加其他列表。
在介绍 list.append(obj)的时候,我们试图把一个列表拼接到 list_1 里,但 append 方法没有拼接,而是把试图拼接的列表整体的放置到了最后,形成一个列表中的列表。如果我们想追加一个新列表到老列表里,就可以使用 list.extend(obj) 这个方法,我们来看具体代码:
我们创建了一个列表 list_3,然后用 extend(obj) 方法在它的末尾拼接了一个 [4,5,6] 的整数类型列表。最后一个实验,我们试图传一个整数类型的参数给 extend(obj)方法,然后Python解释器提醒我们,enxtend()需要的参数,不是普通的对象,而是一个可迭代的对象。
在前面介绍列表运算时,我们用 “+” 把两个列表拼接到了一起,那么问题来了,既然 “+” 拼接字符串那么好用并且又直观,那为啥我们还要用 extend(obj)这个方法 ? 我们来看下面的例子。
上面这个实验,我们创建了两个列表,和一个元祖 meta (后面会为大家讲解元祖这种复合数据)。然后我们尝试用 list_1 + list_2 ,这是行得通的。但我们用 list_1 + meta 就不行了,Python 交互器提示,不同类型是不能用 “+” 加号 进行运算的。那如果我们要执意把 meta 加到 list_1 后面该怎么办?
这时就要用到 list.extend(obj)这个方法了,我们知道只要传给 extend 的参数是可以迭代的,它就可以把这个迭代对象 追加到 list 后面。因为元祖 meta 是一个可迭代的对象,所以我们用
list_1.extend(meta)追加元组数据,得到的结果是:
[1,2,3,4,5,'one','two','three'] 。
此时此刻我想去一句台词:“东厂能做的,我们西厂也能做,东厂做不了的,我们西厂也能做!”
list.index(obj):查看这个对象在列表里的Index。
很多时候我们会想要知道一个对象在某个列表里的index(索引,或序号)是多少? 比如想知道某个学生信息在这个列表里面排名是多少等,我们直接来看下面的代码:
我们创建了一个 student_list 的列表,然后用 index 这个方法来看看 “李四” 在这个列表的什么位置(index是多少)。student_list.index(“李四”),返回的结果为 1,说明 “李四” 在这个列表的index是1(列表的 index 是从0 起步的),也就是在列表的第二个位置。然后我们试着在 student_list 里寻找 “赵六”,但Python提示我们,找不到这个人,所有操作都符合我们的预期。
list.insert(index,obj):在指定位置,插入指定对象:
在我们讲解 list.append(obj)的时候,大家肯定就又想到一个问题。list 列表有没有什么方法,可以在指定位置插入指定对象,而不是追加到最后一个?list.insert(index,obj)就是这样的方法。我们直接来看例子:
这个例子的操作是把 “赵六” 这个人插入到学生列表 st_list 里 “李四” 前面。我们先通过 st_index.index(“李四”),计算出 “李四” 在列表里的位置,然后再通过 insert 方法把 “赵六” 插入到 “李四” 的列表里。
在玩游戏高峰期的时候,我们都有等待进入服务器的经历,如果这个时候你是个 VIP,那么你就可以“插队”,在系统里面 “插队” 的操作就有点类似于 list 的 insert 方法。
list.pop(index=-1): 移除位置在 index 的列表对象。
这里值得注意的是 index=-1 这种描述方式,它的意思是,这个方法接受的是 列表 index 的参数,如果不给这个参数,方案就按照 index= -1 来处理(按照默认参数来处理)。我们直接来看例子:
通过上面的例子可以看到,st_list.pop()是直接使用,没给任何参数,它返回的是 “杨七”,也就是st_list的最后一个对象,为什么是最后一个呢?因为pop()不给参数的时候,默认参数是 -1 ,-1 指得就是列表里倒数第一个对象。
这个时候我们再看 st_list 列表, “杨七” 已经被移除了。紧接着我们给 pop 传入了参数, pop(2)返回的是 “王五”,“王五” 正式st_list列表index为 2 的对象。这个时候再看一下 st_list。我们发现它现在只有 张三、李四、赵六 这三个对象了。
大伙可能会想,我不知道想要删除的列表对象的 index 该怎么办?
list.remove(obj): 移除列表里的某个对象。
我们直接看例子:
还是 st_list 这个列表,这次我们没有使用 list.pop()这个方法,而是直接使用 list.remove()这个方法,它简单粗暴,直接把 “王五” , “李四” 传进去就可以把它们删除了。不过既然 remove 这个方法那么好用,为啥还有 pop 这个方法呢 ? 两个原因:
-
仅删除一个列表里的对象,remove 确实比 pop 方便。但要删除一系列的对象,我们用 for 循环结合 index 就可以更简单地实现目标(大家可以想象如何实现)。
-
pop方法在删除列表对象的时候,会返回这个被删除的对象,这种做法在某些场景里面会有妙用,比如我删除一个对象的时候,还要对它进行一波操作。
list.reverse():反转列表
我们直接看代码吧:
这个例子很简单,也充分说明了问题。list.reverse()翻转的方法就是把列表整个颠倒过来。在一些需要快速切换正序、倒序的列表里,这个方法简直好用到爆。
list.clear(),list.copy(),清楚和复制列表。
先来看清空列表的例子:
清空列表顾名思义,就是把列表里的对象都删除了,上面的代码也很简单。在我还没有接触过 clear 方法的时,我要清空列表,我会赋值一个空列表给它。我的方法是不是更简单? 但我这样的操作方法会给整个系统带来负担。
给已经存在的列表一个 “[]” 列表后,原来的列表信息它不会凭空消失,它会在一个大家找不到“角落“”地方放着,就像一个没有写收件人的快递一样,放在快递站。显然,它占用着资源,但又发挥不出自己的作用,一直到它触发了Python的垃圾回收机制,才会被彻底的解放。Python 在执行垃圾回收任务的时候会消耗很多精力,特别是类似这样垃圾很多时。像在C语言里面,如果我们这样操作很可能造成内存溢出,直接导致程序崩溃。
但我们如果用 .clear()这个方法来清除列表,就会避免这个问题,Python在执行.clear()的时候是真的把列表里的元素给“释放”了,而不是摆在一边不管。接下来我们来看 list.copy()的例子:
上面的例子就是列表的复制。有些时候我们在操作列表的时候,希望备份一个列表,用这个方法即可。
list.sort(key=None,reverse=False): 列表的排序。
我们先从最简单的用法来,直接看代码:
我们先创建了一个数字列表它是乱序的:
num_list = [1,321,-923,-123,-324,873,214,321,89]
然后用 .copy()方法复制了一个一模一样的列表 num_list_1,最后对其使用 .sort()默认的方法来进行排序,结果:[-923, -324, -123, 1, 89, 214, 321, 321, 873],这个时候这个数字列表已经不是乱序了,而是升序排列,如果我们要让其降序排列该怎么办?
同样的方法复制一个 num_list_2 = num_list.copy(),然后在 num_list_2 列表上使用 sort 排序方法, num_list_2.sort(reverse=True),和上面不一样的是,我们给 sort 接受参数的reverse入口传入了 True 这个 bool 形数值,然后再看结果:
[873, 321, 321, 214, 89, 1, -123, -324, -923]
这个就是我们想要的降序排序。从上面的例子可以看出,sort方法里 reverse 这个参数适用于告诉 sort 到底是要降序,还是升序的。
list.sort(key=None,reverse=False),告诉我们,sort 这个方法还有一个 key参数,这个参数的使用需要后面的知识才能很好地掌握,在这里我们只要知道它的目的就是让开发人员能自定义排序规则即可,因为在实际开发过程中,排序对象可能是字符串、或者其他对象,那么排序需要一些特定的规则。
列表(list)有一个兄弟类型元祖(tuple),它的作用和功能几乎都和列表一样,但它也有一些特点,我们来看看元祖(tuple)这种有意思的数据类型。
5 元祖
元祖大部分功能和列表都很像,元祖的优势是运算速度要快于列表,特别是在一些复杂的逻辑运算或超级多的数据里,速度的优势就明显了。元祖就像一个超级跑车,为了追求速度放弃了家用车的一些舒适性。
特别是元祖放弃了列表的一个重要特性,列表创建完后,列表里的对象可修改,元祖却不行。这些不同是因为元祖,列表的底层实现不一样,就像转子发动机和水平对置发动机都是发动机,但机械原理不同一样。元祖的底层设计更精密,内存、cup的使用更高效,虽然元祖里的创建后不能被修改,但它的高效还是让人爱不释手。
我们先来看一下元祖的创建:
-
元祖的创建是使用 “()” 小括号对来完成,其中 tuple_1 是一个空元祖,这个和空字符串、空列表、空字典用法一样。
-
tuple_2 是一个纯整数的元祖,tuple_3是一个纯字符串的元祖,tuple_4是一个混合元祖,它里面有整数、字符串、布尔值、还有函数。
-
tuple_5 是一个元祖嵌套元祖,tuple_6 是一个嵌套列表、字典、元祖套元祖的元祖,如果能很轻松的看懂这个元祖结构,那么这种不同类型混搭的结构难不倒你。
-
值得注意的是单个对象的元祖,tuple_7 = (1,) 的定义方法。我们看到 1后面多了一个 “,” 逗号。为什么呢? 我们打开交互器研究一下:
从上面的例子可以看出,如果我们不给后面加个 “,” 逗号,tuple_1,tuple_2 它们将会是普通的数字类型和字符串类型。
元祖的查看和列表一样,也是使用索引系统(index),通过中括号 “[]” 的方式来取值,但元祖里面的对象是不能被改变的,我们看下面的例子:
元祖查看内部对象的方法和列表一样,但我们试图尝试修改它内部对象值的时候,Python 交互器反馈了一个错误。
元祖的基本运算
元祖的一些基本运算和列表的差不多,我们用交互器尝试一下:
如果仔细敲一遍上面的代码,会发现这些操作和列表的是一样的。
元祖的函数,和方法
列表的函数、方法同样适用于元祖,只是哪些修改列表内对象值的方法,不能用在元祖上面,我们只要注意这点就行。
列表、元祖给我们提供一个组织数据的新思路,几乎所有的程序都需要这样的复合型数据类型,当然在面对下面这个问题的时候列表、元祖可能会变得有一些吃力,我们来看看这个问题。
“需要一种数据结构,它可以保存我们部门的客户信息,然后这个表可以增、删、查、改”
保存我们的客户信息?然后又可以增删查改,列表这样的数据类型再合适不过了,但我们马上会发现一个问题,列表里面的客户信息我们要如何设计? 下面要为大家介绍一种复合类型变量,用它来解决这个问题简直再合适不过了。
6 字典
看到字典这两个字,我们第一个反应就是我们小时候用的英文字典。
一个英文单词,对应一段单词的解释,这是字典这种组织信息的模式,这种模式在我们生活中无处不在,堪称经典,比如微信通讯录、淘宝商品橱柜、打卡记录等等。
那我们在Python里,要如何使用字典(dist)这种数据类型来组织客户信息呢?
字典数据类型的创建
我们直接来看下面的代码。
这段代码就是我们创建 字典(dist) 这种数据类型最基础的方式。仔细阅读可以发现在Python里,字典类型的创建使用 “{ }” 大括号对来完成的,里面是由 “key” : obj 这样的 键值对 来组织数据,然后用 “,” 隔开,比如:
-
第4行代码 “name” : “张三”,我们把 “张三” 这个字符串数据类型通过 “:” 与 “name” 这个键值(key) 对应起来。
-
第5行代码 “age” : 23,我们把数字类型23 通过 “:” 与 “age” 这个键值对应起来。
-
第6~7行代码,以此类推。
关于对键值对理解,我们可以把它理解成 "一个门配一把钥匙",钥匙就是 键值(key),门就是Python里面的对象,我们也可以把键值对理解成 变量与值的关系。但需要注意的是,在字典里面键值是唯一的,也就是说在 customer 这个字典对象里,“name” 这个键值只能有一个。
我们创建好这个字典后,该如何使用它呢?
-
第11~13行是字典数据类型使用的经典例子,它有点像列表取值的使用 list[index] ,只不过 index 索引 变成了 “keyname” 建值的名称。
-
第17行我们尝试查看一个不存在的 key "sex",然后运行代码时系统报错,这个错误会终止程序的运行。(python 的交互器也会输出这样的错误,但我们可以接着使用交互器。)
有小伙伴可能会问,customer 这个字典的定义能不能写在一行? 当然可以!
为了代码的可读性,我们通常会用多行来描述一个字段,Python 官方也是这么推荐的。
现在再来看看更多创建字典的例子:
-
第3行,我们创建了一个空字典,这种创建方式和创建空列表一样。
-
第6~20行,我们更深入的定义了 customer 这个字典,并且把 company 这个key嵌套了一个新的字典结构,再在 address 这个 key 里嵌套了新的字典结构。简单说就是字典中的字典。因为 键值可以对应任何Python对象,所以字典里可以包含列表,列表里也可以包含字典,这种我中有你,你中有我的状态可以构建出复杂的数据结构(太复杂也不太好)。
-
第23~25行代码告诉我们嵌套字典该如何准确地获取某个键的值。
Python 为了让开发人员更好的使用字典这种数据类型,同样提供了很多有用的函数和方法,我们来逐个看一下。
可用在字典身上的函数
序号 | 函数 | 描述 |
---|---|---|
1 | len(obj) | obj的长度,这里的obj指的是可迭代对象。 |
2 | str(obj) | 把字典、列表转换成字符串类型,这在web开发里经常会用到。 |
3 | type(obj) | 判断这对象的类型。 |
其实这三个内置函数同样可以用在列表上面,我们直接来看代码:
len(),str()这两个方法很简单,都是用于列表和字典,str()还可以把基础数据类型转换成字符串。type()这个函数可以用在 Python 里的绝大部分对象里,特别是在一些我们要明确对象类型的场景,会频繁用到这个函数,我们在看一些例子:
我们可以看到,给type()这个函数很多Python对象,然后 type 告诉我们这个参数是什么类型的对象,甚至最后一个 type(print),它告诉我们这个参数是内置函数或者方法。
字典的方法
我们接着来看一下字典对象的常用方法。
序列 | 方法名称 | 说明 |
---|---|---|
1 | dict.clear() | 清楚一个字典,和 list.clear()意义效果一样。 |
2 | dict.copy() | 复制一个字典。 |
3 | dict.get(key,default=None) | 通过字典的 key 来获取对应的值。 |
4 | dict.items() | 把这个字典里的键值对,变成元祖对 (key,value)。然后放在一个列表里。如果是嵌套的字典,只对第一层字典有效。 |
5 | dict.keys() | 把这个字典的key拿出来,以列表的形式重新组合在一起。如果是嵌套的字典,只对第一层字典有效。 |
6 | dict.setdefault(key,default=None) | 给字典设置一个键值对,然后返回设置的 key 值。 |
7 | dict.update(dict2) | 如果dict2里有dict的 key,就更新这个key的值。 |
8 | dict.values() | 新建一个视图列表,然后把这个字典里的值逐一放到列表里。如果是嵌套的字典,只对第一层字典有效。 |
9 | dict.pop(key,[default]) | 给定一个key,返回这个列表里对应key的值,然后把它从列表里移除,如果没有这个 key 则返回 default 这个值。 |
10 | dict.popitem() | 返回列表最后一个键值对,然后将其从列表里移除。 |
现在打开我们的交互器,来逐个实验一下:
dict.clear()与 dict.copy():
清空字典和复制字典,这两个方法和列表里的 list.clear()与 list.copy()一样,我们打开交互器尝试一下。
上面的例子大家应该很熟悉了,所以就不多多介绍了,我们接着看其他的。
dict.get(key,default=None),dict.setdefault(key,default=None):
在介绍列表的时候,想要修改列表的值可以这样来操作 list[index] = obj ,这样的方式同样可以用在字典里面,我们来看下面的例子:
这样的操作是不是既简单又直观?但它有一个缺陷,如果我们提供给字典的 key 字典里没有,会导致python 交互器报错,在系统软件里,可能会造成一个软件的崩溃。在修改字典的键值对时,有没有更好的方法呢? 肯定有,解决方案就是我们的 get(),setdefault() 这两个方法。我们直接来看例子:
在上面的例子,可以看到 dict.get("name") 返回了 "yumaoqiu" 这个值,是因为 dict 这个字典里有 “name” 这个 key。
然后我们尝试执行 dict.get(“sex”)。在dict里并没有 "sex" 这可 key,但Python交互器没有报错,而是返回了一个 None(这个值就是啥也没有的意思),也就是说使用 .get()这样的方法是“不会出错的”。
最后我们在使用 .get("sex","man")方法,Python在执行这条语句的时候,不但没有报错,还输出了一个“man”。 这就是使用 dict.get() 的好处,它可以让程序变得“不严谨”。
接着来看看 dict.setdefault()这个方法:
我们会发现,使用 "[]" 中括号的方式去访问一个字典里不存在的 key 会让Python报错,但用 “[]” 中括号给字典里不存在的 key 设置值时不存在这个问题,而是直接把这个不存在 key 和它的值,直接加入到字典里。
从上面的例子可以看出 dict.setdefault()的效果和直接使用 “[]” 中括号的方法差不多,只是前者可以给字典设置 None(空值)。如果我们想一次设置多个 key 的值怎么办? 字典为我们提供了下面的方法。
dict.update(dict2):
这个方法能让我们一次为一个字典变量设置多个 key 值,我们直接来看下面的代码:
可以看到,我们新创建了一个 customer_2 字典变量,里面的 键值对 就是想为 customer 更新的键值。然后让 customer 调用 update 方法,把 customer_2 作为参数传给该方法。执行完后我们再看看 customer 这个字典。我们发现它的 "name"、"company" 、"age” 这三个 key 的值被改变了。假如我们要更新的键值对不存在会发生什么呢 ? 什么也不会发生。
假如我们要删除存在的键值对该怎么办 ? 字典为我们提供了下面的两种方法。
dict.pop(key,[default]),dict.popitem():
我们直接来看代码:
一开始,我们试图通过 pop 删除 customer 里没有的 key "sex"。这样操作会导致python报错。然后我们在使用 pop 的时候给了个默认值 “man”(删除的时候没有这个 key 就返回 “man”) 。这个时候就不会报错了,并返回 "man"。
最后我们尝试了使用 pop 来删除一个存在的 key , customer.pop(‘name’),删除成功后 pop 方返回了被删除 key 的值。
dict.popitem()的使用就要比 pop简单多了,因为它不需要任何参数,它始终会删除一个字典对象的最后一个键值对,然后返回这 key 的值,让你看这个键值对最后一眼 :)。
上面的例子我们使用 popitem()从最后一个键值对,逐渐的把 customer的键值对删除了,直到customer变成了一个空字典。在空的字典里使用 popitem()会引起 python 报错。
dict.items():
我们直接来打开交互器感受一下这个方法:
从上面的例子可以看出来,items()这个方法把这个字典里的键值对,变成元祖对 (key,value),然后放在一个视图列表对象里。如果是嵌套的字典,只对第一层字典有效。这样做意义何在?
items()这个方法返回的不是一般的列表,它是一个有列表特性的视图对象。什么是视图对象呢?简单理解就是这个对象里的值和生成它的对象的值是绑定的。在上面的例子里,customer.items()会生成一个新的视图对象,这个视图对象里的值会根据 customer 的值改变而改变。这样的机制在开发UI界面时非常有用。
关于视图对象的知识,后面会为大家详细的说明(跳转链接),下面要介绍的 dict.values()和dict.keys()也是视图列表对象。
dict.values(),dict.keys():
我们直接来看代码:
dict.values()是新建一个视图列表,然后把这个字典里的值逐一放到列表里。如果是嵌套的字典,只对第一层字典有效。dict.keys()是把这个字典的 key 拿出来,以视图列表的形式重新组合在一起。如果是嵌套的字典,只对第一层字典有效。字典类型,可以让Python程序员设计出非常强大的数据结构,若再结合 列表、元祖等数据类型那几乎是可以模拟出我们现实生活中所有的信息结构来。
有些时候,我们可能需要解决这样的问题,“ 消除整数列表里重复的值!”。
列表可以胜任这个需求,在创建列表后,我们遍历这个列表,并使用 list.count(obj)这个方法来寻找返回值 大于 1 的对象,找到后把它移除列表就可以了。当然 python 提供给我们了更好的解决方案,那就是使用集合变量。
7 集合
延续上面的问题,我们用列表 、集合来做个对比,看下面代码:
看上面的例子,num_list 是一个有重复整数的列表,我们用 for 循环和 list.count(obj)方法花了三行代码将其去重,然后打印最终结果。之后,我们使用 set()函数把 num_list_2 列表转换成集合去重,然后打印出结果用了 1行代码。
两种方法都可以去重,显然大部分程序员都会选择后者,因为它代码少又简单。在一些复杂的数据运算领域,比如 “求两个国家的人,都打过疫苗的人有多少?”,“Mac用户和windows用户都喜欢的软件是哪些?” 等等 。这类问题有三个特点:
-
数据量庞大。
-
站在更抽象的概念上来做运算,比如 “国家的人”,“Mac的用户”。
-
用 列表 或 元祖 来处理这些问题会 “力不从心”,就像我们用雕刻刀来小洋芋皮一样。
Python 还有一些近代的编程语言,为了解决这类问题就延伸出来 set 这样的数据类型。它能大大提高解决上面问题的效率,这就是集合这种数据类型存在的意义。接下来,我们看看在python中如何创建一个集合。
集合的创建
直接看代码:
-
第3行代码,我们通过 “{}” 大括号对,创建了一个整数集合,就像创建列表、和元祖一样。
-
第4行代码,同样是创建了一个整数集合,只不过我们使用了Python的内置函数 set(),它可以把列表、元祖、可迭代的对象(包括空对象,但不能是可变对象),转换成集合。
-
第8~9行代码,使用同样的方法创建了字符串集合。
-
第12行代码,我们创建了一个混合集合,这里可能有小伙伴会问。为啥没有列表和字典? 因为Python规定可变数组,不能作为集合的对象,因为集合要保证自己的运速度。
-
第17行代码,我们使用内置行数 set()设置了一个空集合。为什么我们不用 “{}” 来创建空集合呢?因为 “{}” 代表的是空字典。
-
第20行我们尝试在创建集合时给一些重复的数字,然而Python并不会报错,只是自动的把重复的整数删除了。
-
第21行代码,我们打印出来的结果是 {1,2,3}
为了效率集合做出了一些牺牲,一个是唯一性(在集合里,重复就是浪费),一个是无序(随机抓一个苹果起来总比按照苹果的成熟度来抓苹果要更快一些)。唯一性我们上面的例子已经体现了,现在我们来看看无序的特性:
通过上面的例子,我们可以看出在创建集合的时候,给的参数都是有序的 “hello word” ,当生成了集合后,就不存在顺序可言了。如果你关闭交互器,重新再打开,在操作一次会发现集合里的顺序又不一样了,这就是集合的无序,没想到无序可以提高大数据的操作性能把。
集合这样的数据类型被设计出来,还有一个目的就是用于集合运算。我们生活中有很多集合运算的例子、很有趣。比如有两个水果篮,它们之间会有这样的运算:
集合的运算
合并两个水果篮,然后我们来看看合并后的水果篮有哪些水果 ?
“|” 这个是集合的合并运算,就是把两个集合里面的对象合并起来,并且去重,下面的图片很形象的说明了集合的合并运算:
两个水果篮相减,我们来看看水果篮的变化:
“- ” 减号,可以让两个集合相减,集合的相减 a-b 代表着 “不同时包含于a和b的元素”。我们来看看集合相减,集合之间发生了什么样的变化。
两个水果篮相交,查看这两个水果篮里都有的水果会是什么? 我们直接来看代码:
“&” 符号可以让两个集合进行相交运算,把两个集合同时拥有的对象给找出来,我们来看看下图相交运算的图示。
集合的相交运算其实我们都在用,在遇到心仪对象时,我们都潜意识的去寻找与对方相同的经历、故事。当然,有些时候我们也会去寻找自己和对方完全不一样的经历,在集合里它有一个非常难懂的定义 “不同时包含于a和b的对象”。我们把这样的集合运算叫做 “组合” 运算(注意它和前面的合并运算是完全不同的)。
我们直接来看 “组合” 运算的例子:
在上面的例子中,我们使用 “^” 符号来计算两个集合之间的组合运算。关于组合运算,我们可以来看看下图的说明解释:
集合里面的 “合并”、“相减”、“相交”、“组合” 这四种最基本的集合运算方法,给我们在处理集合问题时带来了极大的便利。除了这些运算,Python 还内置了这些集合函数。
集合的函数
下图是常用的集合函数
序号 | 函数 | 说明 |
---|---|---|
1 | len(set) | 计算一个 集合 的长度 |
2 | max(set) | 计算出一个 集合 里最大的值 |
3 | min(set) | 计算出一个 集合 里最小的值 |
4 | list(set) | 把一个集合列表化 |
这些内置函数大家应该都很熟悉了吧,它们可以用在列表上面、也可以用在元祖上面,在这里就不去详细地给出例子了,集合自身也带了丰富的方法。
集合的方法
下面是常用的集合方法。
序号 | 方法名 | 说明 |
---|---|---|
1 | add(obj) | 给集合添加元素。 |
2 | clear() | 清楚一个集合。 |
3 | copy() | 复制一个集合。 |
4 | difference() | 集合的减法。 |
5 | difference_update() | 集合的减法(直接更新原始集合) |
6 | discard() | 移除一个集合类对象的方法,如果想要移除的对象不存在,不报错。 |
7 | intersection() | 返回集合的交际 |
8 | intersection_update() | 返回集合的交际 |
9 | isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False |
10 | issubset() | 集合是否是参数集合的子集 |
11 | issuperset() | 集合是否是参数集合的超集 |
12 | pop() | 随即移除对象 |
13 | remove() | 移除指定对象,如果想要移除的对象不存在,报错。 |
14 | symmetric_difference() | 返回两个集合中不重复的元素集合。 |
15 | symmetric_difference_update() | 返回两个集合中不重复的元素集合。 |
16 | union() | 返回两个集合的并集 |
17 | update() | 给集合添加元素 |
乍一看集合的方法有点多,但认真学习过列表、集合等复合类型的小伙伴对这些应该有点 “感觉” 了。我们现在来具体看看。
set.add(),update()
集合是不可变的,但我们可以通过集合的运算(合并、组合、相减、相交)来改变集合里面的对象,当然,集合也给我们提供了一种方法 set.add()方便我们创建集合后,增加集合的数量。看代码:
通过 set.add()我们逐步给这集合添加对象,但需要注意,我们只能一个对象一个对象的来,不能直接加一个其他集合,若想加集合,我们可以使用set.update()方法。那假如我们要删除集合里的对象呢?
remove(obj)、discard(obj)、pop()
集合内部给我们提供了三种方法来删除集合里面的对象,这三种方法有啥区别? 我们来看代码:
通过上面的实验会发现,remove(obj)在试图删除不存在的对象时会报错,但 discard(obj)方法不会。pop(obj) 是随机删除集合里面的元素,然后把这个元素返回给我们,clear()就不用多说了,这个方法列表、元祖、字典都有。集合对象也给开发人员提供了另外一种解决集合合并、相减、交际、组合的方案,就是通过运算方法。
union(set)、difference(set)、intersection(set)、symmetric_difference(set)
上面这四个方法,分别对应着集合的 合并、相减、相交、组合 运算,我们来看看代码:
上面的代码之所以可以这样来写是因为 union 、difference、intersection、symmetric_difference 这四个方法它们不会去改变原有字典的内容,而是把运算结果变成一个新集合返回给我们。列如上面的例子,basket_a.union(basket_b),这个方法运算完后 basket_a 内的对象不会有任何变化。
difference、intersection、symmetric_difference,还有三个变体 difference_update、intersection_update、symmetric_difference_update。这三个方法执行完后会改变原来集合里的内容并且没有返回值,我们来看代码:
issubset(set),issuperset(set)
有些时候我们会遇到这样的问题 “判断双11晚上下订单的人是不是包含了刷订单的数据?” 集合里面的 issubset,issuperset 这两个方法完全就是为这类问题(判断一个集合是否是另外一个集合的子集,或者反过来)量身定做的!我们来看看代码例子:
总结
列表、元祖、字典、集合,这四种复合数据类型都是让开发人员能设计出功能强大的数据结构的。这些数据结构能为我们解决工作中的需求提供很大帮助。作为一个Python工程师,这四种复合数据结构必须熟悉的掌握,熟悉到看到问题就可以很快联想到需要什么样的符合类型、什么样的内置方法可以合理的解决眼前的问题。
在 Python 语言或其他现代语言里面,还有一种复合类型叫做类,它可以说是所有现代编程语言的核心概念,类可以让工程师们更好的把现实生活中的信息组合起来,换句话说,我们生活的世界完全可以用类来模拟,每一个细节都可以。其实整个Python语言、Python的开发生态、各种开发框架、开发包,都是站在类这种概念上来设计的。
类不仅仅是一套概念、它也是软件设计的思想、同时也是 Python 语言内核重要的组成部分。我把类的教学安排到了后后面,一方面是因为它需要一些基础知识的支撑,学起来才爽快。另一方面当把类这部分的内容学完后,再来回顾现在学习的知识,你会对它们有更深入的了解。
8 语法结构的进阶
关键字、缩进、变量、顺序结构、选择结构、循环结构 等基础的知识已经足够让我们去开发一个功能强大的软件了。但在一些特定的场合、或者 一些特别的例子中,我们需要一些特别的手法或技巧来补足基础语法中的一些 “蹩手蹩脚” 的情况,现在我们就来看看这些进阶常用的语法。
9 异常捕获
还记得当时在用 windows95 的时候,无论玩游戏、写东西、看东西、就算随便点几个按钮可能都会出现下面的画面:
那会的 windows95 给人很神奇的感觉、但也不够健壮(形容软件的容错能力)。我们都知道软件不可能是没有BUG的,越是复杂的系统越是这样,如果系统内部发生错误就造成整个系统崩溃那么用户还不都跑了?
为了解决这个问题,不同类型的软件有自己的解决方案,比如 web 应用通过微服务来增加系统的健壮性,坏了一个微服务还有另外几个,就像5,6个人抬桌子一样,一个人累到了桌子不至于掉下来。对于数据存储的系统,数据被备份到世界各地,一个地方的数据库毁了,整个系统的数据还在。
这些都是比较宏观层面的,对于微观层面就是代码层面,很多现代语言都提供了一种机制让程序发生错误后乃然可以正常的运行,这就是异常捕获机制。
什么是异常,如何捕获它?
什么是异常,我们要搞懂它,异常就是那些用符合规范的语法编写出来的逻辑错误,这些能产生异常的代码在 python 语法这个角度来看是正确的代码,能通过IDE的语法检测,一般情况下不影响程序的运行,直到引起异常情况出现的那一刻。我们来看看这段代码:
我们来运行一下这段代码,然后输入 15,3。
目前看来一切正常。我们再来看下面这几个输入数字:
刚才运行的好好的程序,现在报错了,是因为我们尝试拿 0 来做除数。我们都知道 0 拿来做除数是毫无意义的,在我们的代码里0来做除数引发了这段代码的异常。好了,那我们如何用异常的捕获机制来强化我们这段代码的健壮性?看代码:
-
第7行代码,我们使用了 try 这个关键字,意思是告诉Python,下面这段代码可能会发生异常。
-
第8~9行代码,没有触发异常时执行的代码。
-
第10行代码,如果捕获到了 ZeroDivisionError 这个异常,将会执行第11行的代码。
上面这个例子是最简单的异常捕获模式,它让我们这个很小的程序拥有了针对 ZeroDivisionError 错误问题的免疫力,让我们的程序变得更健壮了。我们运行程序,在尝试用 0 来做除数。
这次程序没有出错,我们使用 0 来做除数时,程序提醒我们 “0不能作为除数”。那假如我们输入的时候是输入一个 字符串,而不是数字会怎样 ?
这次除数 我们输入了 “十五” ,然后导致程序出错,报出 ValueError 的错误。其实我们知道我们在写 y = float(input("输入除数")) 这行代码的时候就意识到,假如我们输入的不是数字,那么 float 这个函数就会报类型转换错误,那我们如何捕获它呢 ? 看代码:
我们对上面的代码进行了改造!
-
第5~6行我们把 输入除数和被除数也放到了 try 里。
-
第11行 增加了对 ValueError 错误值的捕获。
-
第12行 捕获到了这个错误值输出的内容。
我们运行一下代码,故意输入一些错误值,看看结果。
可以看到,我们的程序已经对 ValueError 这种错误进行免疫了,并且也给出了 try except 的另外一种模式,这种模式可以允许我们捕获各种各样的错误。Python 给我们提供了哪些异常呢? 我们看看下面这张表:
异常 | 描述 |
---|---|
AttributeError | 试图访问一个对象没有的属性 |
IOError | 无法打开一个文件,或者输入输出异常 |
ImportError | 无法引入一个包 |
IndexError | 下标出错,比如给了一个不存在的下标 |
KeyError | 试图范围一个不存在的 key |
ValueError | 一个错误的值 |
ZeroDivisionError | 除数为零 |
上面只是Python给我们提供可能发生的异常的一小部分,毕竟实际工作中错误会有很多种大大小小见过没见过的,详细的列表我们可以去看官方文档。在具体工作中,会遇到这样的情况,“感觉会发生什么错误,但又很难确定是什么样的错误?”该怎么办? 还是拿上面的代码来做改进。
-
第5行代码是一个 while 循环语句,我们可以看出来这里是一个无限的循环,这样做的目的是想来验证下面的代码已经是万无一失了,无论你输入什么进来,系统都会照样无限循环下去,因为任何可能发生的错误,都被捕获到了。
-
第11行代码就是为什么我们可以捕获到任何错误的原因,我们捕获一个叫做 Exception 的错误异常,它包含了 ValueError、ZeroDivisionError等等Python所有的错误,Exception 后面的 as 是给这个总的错误一个别名 e。
-
然后在第12行代码,把这个错误输出出来,看看到底是什么问题。
我们来运行一下这个 “健壮” 的程序:
可以看到,只要我们不想结束,程序就不会结束,无论我们输入什么,程序都不会因为错误而退出,假如没有 try.....except会怎样? 建议小伙伴们自己试试看。
我们再来仔细看看上面代码里 Exception ,它其实是一个错误类(又是类!其实类的概念无处不在,后面会专门为大家介绍类的知识!),它包括了若干的子错误描述,或者说它是一个总的错误,举个例子:
-
我放了个错误
-
我闯了红灯
-
我剩了很多饭菜
我说 “我闯了红灯” 并不是 “我剩了很多饭菜”。但我说 我放了个错误,那可能是 “我闯了红灯”,也可能是 “我剩了很多的饭菜”。这就是为什么 Exception 可以包括很多错误的原理。其实这是类里的一个概念,后面会为大家详细的说明。
try....except 的延伸写法:
try....except 还延伸出来了其他的模式,在某些场景下面特别好用,我们来逐个看看:
第 13-14 行,我们基于上面的例子,在 try 语法结构里,加入了 finally 这个关键词,它的作用就是无论最终有没有异常被捕获,最后都会执行 finally 下面的语句。我们运行下代码看看效果。
可以看到每一次循环都会输出 “--------------------” 来。这种try模式用法在我们操作文件,操作数据库的时候特别有用,因为这些操作都需要我们在最后把文件关闭,把数据库连接断开。我们稍微改进一下这段代码:
我们在第13行代码里加入了一个 else 关键词(if ,while里也有 else 关键词),它的意思是如果 try 没有捕获到异常错误,那么就会执行第 14 行的代码。这个 else 与 finally 的区别就是,finally 下面的代码无论如何都会被执行,而 else 下面的代码只有没捕获到异常的时候才会被执行,我们运行一下这段代码看看。
可以看到只要我们输入的参数不会触发代码异常,代码就会在控制台输出 “你输入的参数很棒,没有问题!”
手动抛出异常
Python内置的异常已经很丰富了,但它主要是围绕开发逻辑来设计的,比如录入学生信息的时候,一个学生的年龄被录入了500岁,这在我们认知常识里面是明显有问题的,这样的异常我们该如何来捕获呢?
遇到这样的场景时,我们只能在代码里告诉Python这种情况是种异常,然后手动抛出它,来看代码:
我们重点看第 6~7 行代码,在这里只要age超过了500,就会触发 第7行代码的执行。第7行代码 用了一个 raise 关键字告诉 Python 我们要在这里抛出一个异常。这里我们抛出了 Exception 这个异常类,这是最基础的异常类,包括了很多异常类。我们运行一下这段代码看看:
可以看到,我们输入年龄为500,系统就告诉我们,“你应该不是人类”。其实这样的效果我们使用 if...else...选择结构语法也可以完成,是因为我们举的例子比较简单,实际开发工作中我们通常要通过类的理念来设计自己项目里的异常(后面会为大家介绍到),这样的做法在团队开发里或跨语言开发里面时会带来很多便利的。
断言,有些时候我们反而要严肃一点
在 spaceX 公司发射火箭时,会启动一个系统检测程序,主要是用于检测火箭的各种状态。当检测程序发现火箭里的某个部件不对,或者哪里有风险的时候会让整个火箭发生程序终止运行。 这个检测程序里就用到了断言的概念。
有些任务是非常惧怕风险和不确定的,比如上面的火箭发射、一场重大的手术、一次特技表演。在这些事情开始前一旦发现了任何风险,就不会让这件事情开始!在Python里要做到这一点,我们可以使用断言,我们看下面的例子:
上面的代码我们模拟了一下火箭发生前的环境监测。
-
第4~7行用四个变量 temp、humidity、wind_speed、fuel分别模拟了温度、湿度、风速、燃油量。
-
第10~20行 通过 assert 关键词和它后面的运算表达式来检测环境参数是不是在发射需要的环境条件里面? 每一个 assert 加后面的表达式就构成了一个 断言,如果 assert 后面的表达式为 True 断言通过并且什么也不会发生,如果 assert 后面的表达式为 False 断言不通过并且抛出异常,程序终止!
我们运行一下上面的代码看看:
程序检测到 室外温度后 就抛出异常,并且没有继续往下检测了,程序还抛出了 AssertionError 这个异常类,并且输出了 assert 18.6 < humidity <= 22.6 ,提醒我们目前的湿度不符合发射条件。
我相信看到这里有一些小伙伴们已经反应过来了,看代码:
第14行的代码,等价于 第18~19行代码,其实断言就是 if 语句 与 手动抛出异常的 “简写” 。断言的用法场景还有很多,在能大大减少软件测试成本的驱动测试领域,断言就是我们经常用到的工具。
10 能打破循环的 break 和 continue
这两个关键字是在某些特别的场景下,让代码的执行跳出循环(for,while),或跳过循环的部分代码,也就是说它打破了原有的代码执行顺序,为啥要这么做呢?我们看看下面的代码:
如果小伙伴们一直认真看前面内容,上面的代码应该很容易看懂。在第14行有一个 break 关键字,它的作用就是告诉 python 代码执行到这里可以跳出这次 for 循环了。这个break包含在一个选择结构里,当我们在程序提示的时候输入 y ,我们就可以让这个循环结束。
这里请大家思考一个问题,如果我们不用 break 这个关键字要如何实现上面的代码呢?其实是有很多办法的,但有了 break 关键字这个问题就变简单了。
现在我们来看看 continue 的用法,还是先看代码:
我们关注第7行代码,当num是一个偶数的时候 continue被执行,然后它下面的代码(for循环类)不再被执行,也就是说当前循环剩下的代码被直接跳过,跳到下一个循环阶段开始执行代码(第4行代码)。
也许小伙伴们会想,我们用 if 语句也可以做到,在这里确实是这样。一般使用 continue 的时候都是 if 语句很难 “写” 的时候。其实也可以理解 在Python里 continue 是 if 语句的一个补充。
11 迭代的概念,迭代器
在之前的学习里,我们多次提到 迭代 这个概念。其实迭代这个概念它不是Python专有的东西、在产品开发、软件工程、甚至公司的战略规划里都会用到这个概念。鼎鼎大名的敏捷开发就是基于迭代这个概念来发展的。如果我们硬是要给迭代一个定义,迭代应该是:
-
可以重复的事情。
-
定期执行的。
-
这些重复的事情里,有些细节会不一样,但它们目标是一样的。
举几个生活中迭代的例子:
-
细胞的新陈代谢
-
每天早上起来漱口
-
然后朝九晚六的工作
-
一局王者荣耀
-
一个 for 循环
-
我们自身的成长(从出生到成年人)
现在我们来看看在python里,迭代使用的样子:
-
第2~6行的代码大家再熟悉不过了,标准的 list 遍历用法。
-
第9行代码,可以看到我们通过 iter() Python内部函数,把 list_num 放到了 iter_1 这个迭代器对象里。
-
第12-23行代码,我们通过内部函数 next() 让迭代器逐一的把 list_num 里面的值输出出来。
-
执行到第23行,我们会接受到一个错误:
这个错误告诉我们,已经没有什么可以迭代了。
我初次接触到 iter()、和 next() 的用法是感觉他们有点多余,在实际的工作中这两个方法确实也用得少(可能是我开发的项目原因,这样用迭代的方式少),到目前为止我感觉它们最大的意义就是让开发人员检测自定义迭代对象是否能很好地工作的。
Python给我们提供了一种“协议”,我们在设计类的时候按照这样的“协议”就可以开发出能被迭代的类(讲到类的时候会和大家谈到)。为了检测我们设计的类,迭代是否顺畅,这个时候 iter() 和 next() 就派上用场了。Python 基于迭代的概念,还延伸出来了一个更聪明的迭代方式,在面对一些特殊场景的时候,它有奇效!堪称一剂猛药!
12 生成器
有些时候我会去公司附近买早点吃,我一般会买包子或者煎饼,卖包子的老板是一对老两口,和他们闲聊得知早上3点就要起来做包子,有鲜肉的、白菜的、洋芋的等等。5:30拿着做好的包子到店里开始蒸包子,然后陆陆续续就有人来买,直到中午,这时候可能还会有一些包子没卖完,这些包子只能贱卖给养猪的朋友。这种情况很难预判,几乎每天都会有包子白做,极端的时候可能1/3的包子都白做,在软件设计里面这样的模式可能会影响软件的效率,增加软件的开销。比如我们事先已经运算出了1000W个数据,但其中只有100个被用到。
这时候再看看买煎饼的小妹,所有的食材都准备好了,来一个人做一个煎饼,并且根据每个人不同的需求还可以搭配出各种花样来。我默默的观察发现,煎饼小妹早上10点就收摊了,收摊的时候不存在有剩下煎饼的困惑,它的食材用的刚刚好,不浪费。在软件开发领域,这种模式就是事先准备好执行代码的运算逻辑,需要的时候再计算!
卖包子的模式中,资源用的好不好取决于买包子的人多不多,但卖煎饼的模式中就不受这样的影响,这是煎饼模式最大优点。当然煎饼模式也有缺点,就是每次卖的时候都要制作一个煎饼,而不是提前都做好然后再卖,这种情况在市场稳定的时候,煎饼模式的缺点就出来了。
好了说那么多就是想告诉大家,我们现在要讲的生成器,就很像煎饼模式,先看代码实例:
第2行我们创建了一个 list_num 列表。
第5行我们通过一个特别的语法创建了一个生成器 gen。这个特殊的语法乍一看有点神秘,但其实它特别简单。我们先看这个语法的后面一部分 for num in list_num,单独看这部分代码我们很快就理解,它是一个 for 循环迭代,前面这部分代码 (num * 2)/ (num ** 2) 是基于 num 的一个运算法则,也就是 “煎饼” 的制作流程。
(num * 2)/(num ** 2) for num in list_num #也可以理解成这样: for num in list_num:(num * 2)/(num ** 2)
在 Python 里面我们用 “()” 括号把 (num * 2)/(num ** 2) for num in list_num 括起来就是告诉Python 这是一个生成器,目前它什么也不做只是先把运算代码 (num * 2)/(num ** 2) 准备好 。当它在第10行代码需要使用的时候,(num * 2)/(num ** 2) 这段运算代码才会被执行,然后给出结果。下面是这个生成器计算的结果:
其实在这么小的数据样本和这么简单的运算法则下,生成器的使用优势很微弱,如果是100W级的数据,科学计算的运算法则,生成器的优势是惊人的。我们再来看看生成器更多的例子:
我们之前已经和 python 的函数、方法打过交道了,现在是时候好好的聊聊它们了,相信我了解它后你会对编程开发有新的认识。
这篇关于六 Python 自学进阶,如果想要打牢基础,应该收藏它的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!