diff --git a/第001课:初识Python.md b/第001课:初识Python.md
deleted file mode 100644
index 2cef409..0000000
--- a/第001课:初识Python.md
+++ /dev/null
@@ -1,73 +0,0 @@
-## 第001课:初识Python
-
-### Python简介
-
-Python是由荷兰人吉多·范罗苏姆(Guido von Rossum,后面都称呼他为Guido)发明的一种编程语言。
-
-#### Python的历史
-
-1. 1989年圣诞节:Guido开始写Python语言的编译器。
-2. 1991年2月:第一个Python解释器诞生,它是用C语言实现的,可以调用C语言的库函数。
-3. 1994年1月:Python 1.0正式发布。
-4. 2000年10月:Python 2.0发布,Python的整个开发过程更加透明,生态圈开始慢慢形成。
-5. 2008年12月:Python 3.0发布,引入了诸多现代编程语言的新特性,但并不完全兼容之前的Python代码。
-
-> **说明**:大多数软件的版本号一般分为三段,形如A.B.C,其中A表示大版本号,当软件整体重写升级或出现不向后兼容的改变时,才会增加A;B表示功能更新,出现新功能时增加B;C表示小的改动(例如:修复了某个Bug),只要有修改就增加C。
-
-#### Python的优点
-
-Python的优点很多,简单为大家列出几点。
-
-1. 简单明确,跟其他很多语言相比,Python更容易上手。
-2. 开放源代码,拥有强大的社区和生态圈。
-3. 能够在Windows、macOS、Linux等各种系统上运行。
-
-#### Python的应用领域
-
-目前Python在**Web服务器应用开发**、云基础设施开发、**网络数据采集**(爬虫)、**数据分析**、量化交易、**机器学习**、**深度学习**、**自动化测试**、**自动化运维**等领域都有用武之地。
-
-### 安装Python环境
-
-想要开始你的Python编程之旅,首先得在计算机上安装Python环境,简单的说就是得安装运行Python程序的工具,通常也称之为Python解释器。我们强烈建议大家安装Python 3的环境,很明显它是目前更好的选择。
-
-#### Windows环境
-
-可以在[Python官方网站]()找到下载链接并下载Python 3的安装程序。
-
-![](res/download-python.png)
-
-对于Windows操作系统,可以下载“executable installer”。需要注意的是,如果在Windows 7环境下安装Python 3,需要先安装Service Pack 1补丁包,大家可以在Windows的“运行”中输入`winver`命令,从弹出的窗口上可以看到你的系统是否安装了该补丁包。如果没有该补丁包,一定要先通过“Windows Update”或者类似“CCleaner”这样的工具自动安装该补丁包,安装完成后通常需要重启你的Windows系统,然后再开始安装Python环境。
-
-![](res/winver.png)
-
-双击运行刚才下载的安装程序,会打开Python环境的安装向导。在执行安装向导的时候,记得勾选“Add Python 3.x to PATH”选项,这个选项会帮助我们将Python的解释器添加到PATH环境变量中(不理解没关系,照做就行),具体的步骤如下图所示。
-
-![](res/cpython-installation-1.png)
-
-![](res/cpython-installation-2.png)
-
-![](res/cpython-installation-3.png)
-
-![](res/cpython-installation-4.png)
-
-安装完成后可以打开Windows的“命令行提示符”工具并输入`python --version`或`python -V`来检查安装是否成功,命令行提示符可以在“运行”中输入`cmd`来打开或者在“开始菜单”的附件中找到它。如果看了Python解释器对应的版本号(如:Python 3.7.8),说明你的安装已经成功了,如下图所示。
-
-![](res/cpython-installation-5.png)
-
-> **说明**:如果安装过程显示安装失败或执行上面的命令报错,很有可能是因为你的Windows系统缺失了一些动态链接库文件而导致的问题。如果系统显示api-ms-win-crt\*.dll文件缺失,可以在[微软官网](https://www.microsoft.com/zh-cn/download/details.aspx?id=48145)下载Visual C++ Redistributable for Visual Studio 2015文件进行修复,64位的系统需要下载有x64标记的安装文件。如果是因为安装游戏时更新了Windows的DirectX之后导致某些动态链接库文件缺失问题,可以下载一个[DirectX修复工具]()进行修复。
-
-#### macOS环境
-
-macOS自带了Python 2,但是我们需要安装和使用的是Python 3。可以通过Python官方网站提供的[下载链接]()找到适合macOS的“macOS installer”来安装Python 3,安装过程基本不需要做任何勾选,直接点击“下一步”即可。安装完成后,可以在macOS的“终端”工具中输入`python3`命令来调用Python 3解释器,因为如果直接输入`python`,将会调用Python 2的解释器。
-
-> **说明**:如果对安装Python环境有任何疑问,可以参考我们在**百度云盘**上提供的**视频讲解**。视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao。
-
-### 总结
-
-到这里,大家已经对Python语言有一个基本的了解,知道它可以做很多的事情,所以也值得我们去学习。要用Python做开发,首先需要在自己的计算机上安装Python环境,上面我们为大家介绍了macOS和Windows两种环境下Python 3环境的安装方法,希望大家都能顺利的安装成功,以便开启我们后续的学习。如果安装过程中遇到任何问题,可以联系博主加入我们的免费体验课或答疑群。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第002课:第一个Python程序.md b/第002课:第一个Python程序.md
deleted file mode 100644
index dd115cf..0000000
--- a/第002课:第一个Python程序.md
+++ /dev/null
@@ -1,86 +0,0 @@
-## 第002课:第一个Python程序
-
-在上一课中,我们已经了解了Python语言并安装了运行Python程序所需的环境,相信大家已经迫不及待的想开始自己的Python编程之旅了。首先我们来看看应该在哪里编写我们的Python程序。
-
-### 编写代码的工具
-
-#### 交互式环境
-
-我们打开Windows的“命令行提示符”工具,输入命令`python`然后回车就可以进入到Python的交互式环境中。所谓交互式环境,就是我们输入一行代码回车,代码马上会被执行,如果代码有产出结果,那么结果会被显示在窗口中。例如:
-
-```Bash
-Python 3.7.6
-Type "help", "copyright", "credits" or "license" for more information.
->>> 2 * 3
-6
->>> 2 + 3
-5
-```
-
-> **提示**:使用macOS系统的用户需要打开“终端”工具,输入`python3`进入交互式环境。
-
-如果希望退出交互式环境,可以在交互式环境中输入`quit()`,如下所示。
-
-```Bash
->>> quit()
-```
-
-#### 文本编辑器 - Visual Studio Code
-
-Visual Studio Code(通常简称为VS Code)是一个由微软开发能够在Windows、 Linux和macOS等操作系统上运行的代码编辑神器。它支持语法高亮、自动补全、多点编辑、运行调试等一系列便捷功能,而且能够支持多种编程语言。现阶段,**强烈建议**大家使用VS Code来编写Python代码。关于VS Code的下载、安装和使用,推荐大家阅读知乎上名为[《VScode安装使用》]()的文章。下图是使用VS Code的用户主界面。
-
-![](res/vscode.png)
-
-#### 集成开发环境 - PyCharm
-
-如果用Python开发商业项目,我们推荐大家使用更为专业的工具PyCharm。PyCharm是捷克的JetBrains公司开发的用于Python项目开发的集成开发环境(IDE),所谓集成开发环境就是说工具中提供了代码编写、代码运行和调试、代码分析、代码版本控制等各种功能,因此特别适合商业项目的开发。在[JetBrains的官方网站]()上提供了PyCharm的[下载链接](),其中社区版(Community)是免费的但功能相对弱小,专业版(Professional)功能非常强大,但需要按年或月付费使用,新用户可以试用30天时间。关于如何使用PyCharm来进行Python开发,我们在后续的课程中为大家进行讲解。
-
-### hello, world
-
-按照行业惯例,我们学习任何一门编程语言写的第一个程序都是输出`hello, world`,因为这段代码是伟大的丹尼斯·里奇(C语言之父,和肯·汤普森一起开发了Unix操作系统)和布莱恩·柯尼汉(awk语言的发明者)在他们的不朽著作*The C Programming Language*中写的第一段代码。
-
-```Python
-print('hello, world')
-```
-
-### 运行程序
-
-建议大家用VS Code来书写上面的代码,完成代码编辑后将其保存在你容易找到的目录下,我们将上面的代码命名为`hello.py`。如果你使用的是Windows操作系统,接下来可以在你保存代码的目录下先按住键盘上的`shift`键再点击鼠标右键,这时候鼠标右键菜单中会出现“命令行提示符”选项,点击该选项就可以打开“命令行提示符”工具,我们输入下面的命令。
-
-```Shell
-python hello.py
-```
-
-> **提醒**:我们也可以在任意位置打开“命令行提示符”或“终端”工具,然后将需要执行的Python代码通过拖拽的方式拖入到“命令行提示符”或“终端”中,这样相当于指定了文件的绝对路径来运行该文件中的Python代码。再次提醒,macOS系统要通过`python3`命令来运行该程序。
-
-你可以尝试将上面程序单引号中的`hello, world`换成其他内容;你也可以尝试着多写几个这样的语句,看看会运行出怎样的结果。需要提醒大家,上面代码中的`print('hello, world')`就是一条完整的语句,我们用Python写程序,最好每一行代码中只有一条语句。虽然使用`;`s分隔符可以将多个语句写在一行代码中,但是最好不要这样做,因为代码会变得非常难看。
-
-### 注释你的代码
-
-注释是编程语言的一个重要组成部分,用于在源代码中解释代码的作用从而增强程序的可读性。当然,我们也可以将源代码中暂时不需要运行的代码段通过注释来去掉,这样当你需要重新使用这些代码的时候,去掉注释符号就可以了。简单的说,**注释会让代码更容易看懂但不会影响程序的执行结果**。
-
-Python中有两种形式的注释:
-
-1. 单行注释:以#和空格开头,可以注释掉从`#`开始后面一整行的内容。
-2. 多行注释:三个引号开头,三个引号结尾,通常用于添加多行说明性内容。
-
-```Python
-"""
-第一个Python程序 - hello, world
-
-Version: 0.1
-Author: 骆昊
-"""
-# print('hello, world')
-print("你好,世界!")
-```
-
-### 总结
-
-到这里,我们已经把第一个Python程序运行起来了,是不是很有成就感?只要你坚持学习下去,再过一段时间,我就可以带着大家用Python程序制作小游戏、写美女图片爬虫、开发微信机器人。**写程序本身就是一件很酷的事情**,在未来编程就像英语一样,**对很多人来说或都是必须要掌握的技能**。
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第010课:函数和字符串的应用.md b/第010课:函数和字符串的应用.md
deleted file mode 100644
index 1a61e35..0000000
--- a/第010课:函数和字符串的应用.md
+++ /dev/null
@@ -1,125 +0,0 @@
-## 第010课:函数和字符串的应用
-
-前面两节课,我们介绍了函数和字符串。在讲解今天的内容之前,先来回答一个可能会让大家感到费解的问题:为什么字符串类型(`str`)可以通过调用方法的方式进行操作,而之前我们用到的数值类型(如`int`、`float`)却没有可以调用的方法。在Python中,数值类型是标量类型,也就是说这种类型的变量没有可以访问的内部结构;而字符串类型是一种结构化的、非标量类型,所以才会有一系列的方法可供调用。如果对这一点感到困惑,那就继续学习吧,等学习完面向对象编程的知识后,你就能找到这些问题的答案了。
-
-接下来我们通过一些案例来为大家讲解函数和字符串的应用。
-
-### 经典小案例
-
-#### 例子1:设计一个生成指定长度验证码的函数。
-
-> **说明**:验证码由数字和英文大小写字母构成。
-
-```Python
-import random
-
-ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
-
-
-def generate_code(code_len=4):
- """生成指定长度的验证码
- :param code_len: 验证码的长度(默认4个字符)
- :return: 由大小写英文字母和数字构成的随机验证码字符串
- """
- code = ''
- for _ in range(code_len):
- # 产生0到字符串长度减1范围的随机数作为索引
- index = random.randrange(0, len(ALL_CHARS))
- # 利用索引运算从字符串中取出字符并进行拼接
- code += ALL_CHARS[index]
- return code
-```
-
-我们用下面的代码生成10组随机验证码来测试上面的函数。
-
-```Python
-for _ in range(10):
- print(generate_code())
-```
-
-上面的函数其实还有一种更为简单的写法,直接利用`random`模块的随机抽样函数从字符串中取出指定数量的字符,然后利用字符串的`join`方法将选中的那些字符拼接起来。此外,可以利用Python标准库中的`string` 模块来获得数字和英文字母的字面常量。
-
-```Python
-import random
-import string
-
-ALL_CHARS = string.digits + string.ascii_letters
-
-
-def generate_code(code_len=4):
- """生成指定长度的验证码
- :param code_len: 验证码的长度(默认4个字符)
- :return: 由大小写英文字母和数字构成的随机验证码字符串
- """
- return ''.join(random.choices(ALL_CHARS, k=code_len))
-```
-
-> **说明**:`random`模块的`sample`和`choices`函数都可以实现随机抽样,`sample`实现无放回抽样,这意味着抽样取出的字符是不重复的;`choices`实现有放回抽样,这意味着可能会重复选中某些字符。这两个函数的第一个参数代表抽样的总体,而参数`k`代表抽样的数量。
-
-#### 例子2:设计一个函数返回给定文件名的后缀名。
-
-> **说明**:文件名通常是一个字符串,而文件的后缀名指的是文件名中最后一个`.`后面的部分,也称为文件的扩展名,它是某些操作系统用来标记文件类型的一种机制,例如在Windows系统上,后缀名`exe`表示这是一个可执行程序,而后缀名`txt`表示这是一个纯文本文件。需要注意的是,在Linux和macOS系统上,文件名可以以`.`开头,表示这是一个隐藏文件,像`.gitignore`这样的文件名,`.`后面并不是后缀名,这个文件没有后缀名或者说后缀名为`''`。
-
-```Python
-def get_suffix(filename):
- """获取文件名的后缀名
- :param filename: 文件名
- :return: 文件的后缀名
- """
- # 从字符串中逆向查找.出现的位置
- pos = filename.rfind('.')
- # 通过切片操作从文件名中取出后缀名
- return filename[pos + 1:] if pos > 0 else ''
-```
-
-可以用下面的代码对上面的函数做一个简单的测验。
-
-```Python
-print(get_suffix('readme.txt')) # txt
-print(get_suffix('readme.txt.md')) # md
-print(get_suffix('.readme')) #
-print(get_suffix('readme.')) #
-print(get_suffix('readme')) #
-```
-
-上面的`get_suffix`函数还有一个更为便捷的实现方式,就是直接使用`os.path`模块的`splitext`函数,这个函数会将文件名拆分成带路径的文件名和扩展名两个部分,然后返回一个二元组(下节课会讲到元组),二元组中的第二个元素就是文件的后缀名(包含`.`),如果要去掉后缀名中的`.`,可以做一个字符串的切片操作,代码如下所示。
-
-```Python
-from os.path import splitext
-
-
-def get_suffix(filename):
- return splitext(filename)[1][1:]
-```
-
-#### 例子3:在终端中显示跑马灯(滚动)文字。
-
-> **说明**:实现跑马灯文字的原理非常简单,把当前字符串的第一个字符放到要输出的内容的最后面,把从第二个字符开始后面的内容放到要输出的内容的最前面,通过循环重复这个操作,就可以看到滚动起来的文字。两次循环之间的间隔可以通过`time`模块的`sleep`函数来实现,而清除屏幕上之前的输出可以使用`os`模块的`system`函数调用系统清屏命令来实现。
-
-```Python
-import os
-import time
-
-content = '北 京 欢 迎 你 为 你 开 天 辟 地 '
-while True:
- # Windows清除屏幕上的输出
- # os.system('cls')
- # macOS清除屏幕上的输出
- os.system('clear')
- print(content)
- # 休眠0.2秒(200毫秒)
- time.sleep(0.2)
- content = content[1:] + content[0]
-```
-
-> **提示**:我们之前建议大家暂时用VS Code来编写Python代码,如果你已经提前开始使用PyCharm了,需要提醒大家,PyCharm的运行窗口无法用上面的方式做清屏处理。建议在“命令行提示符”或“终端”(PyCharm中的“Terminal”相当于就是Windows系统的“命令行提示符”或macOS系统的“终端”)中运行该程序。
-
-### 简单的总结
-
-在写代码尤其是开发商业项目的时候,一定要有意识的**将相对独立且重复出现的功能封装成函数**,这样不管是自己还是团队的其他成员都可以通过调用函数的方式来使用这些功能。字符串是非常重要的数据类型,**字符串的常用运算和方法需要掌握**,因为一般的商业项目中,处理字符串比处理数值的操作要更多。
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第013课:列表和元组的应用.md b/第013课:列表和元组的应用.md
deleted file mode 100644
index 8215ea0..0000000
--- a/第013课:列表和元组的应用.md
+++ /dev/null
@@ -1,185 +0,0 @@
-## 第013课:列表和元组的应用
-
-列表和元组在编写应用程序时都非常有用,我们通过下面几个案例帮助大家熟悉列表和元组的使用方法。
-
-### 经典的案例
-
-#### 案例1:成绩表和平均分统计。
-
-> **说明**:录入5个学生3门课程的考试成绩,计算每个学生的平均分和每门课的平均分。
-
-这个案例我们在之前说到过,而且提醒过大家在使用嵌套列表时应该避开的坑,下面我们给出完整的代码。
-
-```Python
-"""
-录入5个学生3门课程的考试成绩
-计算每个学生的平均分和每门课的平均分
-
-Version: 0.1
-Author: 骆昊
-"""
-names = ['关羽', '张飞', '赵云', '马超', '黄忠']
-courses = ['语文', '数学', '英语']
-# 用生成式创建嵌套的列表保存5个学生3门课程的成绩
-scores = [[0] * len(courses) for _ in range(len(names))]
-# 录入数据
-for i, name in enumerate(names):
- print(f'请输入{name}的成绩 ===>')
- for j, course in enumerate(courses):
- scores[i][j] = float(input(f'{course}: '))
-print()
-print('-' * 5, '学生平均成绩', '-' * 5)
-# 计算每个人的平均成绩
-for index, name in enumerate(names):
- avg_score = sum(scores[index]) / len(courses)
- print(f'{name}的平均成绩为: {avg_score:.1f}分')
-print()
-print('-' * 5, '课程平均成绩', '-' * 5)
-# 计算每门课的平均成绩
-for index, course in enumerate(courses):
- # 用生成式从scores中取出指定的列创建新列表
- curr_course_scores = [score[index] for score in scores]
- avg_score = sum(curr_course_scores) / len(names)
- print(f'{course}的平均成绩为:{avg_score:.1f}分')
-```
-
-上面对列表进行遍历的时候,使用了`enumerate`函数,这个函数非常有用。我们之前讲过循环遍历列表的两种方法,一种是通过索引循环遍历,一种是直接遍历列表元素。通过`enumerate`处理后的列表在循环遍历时会取到一个二元组,解包之后第一个值是索引,第二个值是元素,下面是一个简单的对比。
-
-```Python
-items = ['Python', 'Java', 'Go', 'Swift']
-
-for index in range(len(items)):
- print(f'{index}: {items[index]}')
-
-for index, item in enumerate(items):
- print(f'{index}: {item}')
-```
-
-#### 案例2:设计一个函数返回指定日期是这一年的第几天。
-
-> **说明**:这个案例源于著名的*The C Programming Language*上的例子。
-
-```Python
-"""
-计算指定的年月日是这一年的第几天
-
-Version: 0.1
-Author: 骆昊
-"""
-def is_leap_year(year):
- """判断指定的年份是不是闰年,平年返回False,闰年返回True"""
- return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
-
-
-def which_day(year, month, date):
- """计算传入的日期是这一年的第几天
- :param year: 年
- :param month: 月
- :param date: 日
- """
- # 用嵌套的列表保存平年和闰年每个月的天数
- days_of_month = [
- [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
- [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- ]
- # 布尔值False和True可以转换成整数0和1,因此
- # 平年会选中嵌套列表中的第一个列表(2月是28天)
- # 闰年会选中嵌套列表中的第二个列表(2月是29天)
- days = days_of_month[is_leap_year(year)]
- total = 0
- for index in range(month - 1):
- total += days[index]
- return total + date
-
-
-print(which_day(1980, 11, 28)) # 333
-print(which_day(1981, 12, 31)) # 365
-print(which_day(2018, 1, 1)) # 1
-print(which_day(2016, 3, 1)) # 61
-```
-
-#### 案例3:实现双色球随机选号。
-
-> **说明**:双色球属乐透型彩票范畴,由中国福利彩票发行管理中心统一组织发行,在全国范围内销售。红球号码范围为01~33,蓝球号码范围为01~16。双色球每期从33个红球中开出6个号码,从16个蓝球中开出1个号码作为中奖号码,双色球玩法即是竞猜开奖号码的6个红球号码和1个蓝球号码。
-
-这个题目的思路是用一个列表保存红色球的号码,然后通过`random`模块的`sample`函数实现无放回抽样,这样就可以抽中6个不重复的红色球号码。红色球需要排序,可以使用列表的`sort`方法,显示的时候一位数前面需要做补`0`的操作,可以用字符串格式化的方式来处理。
-
-```Python
-"""
-双色球随机选号
-
-Version: 0.1
-Author: 骆昊
-"""
-from random import randint, sample
-
-
-def display(balls):
- """输出列表中的双色球号码"""
- for index, ball in enumerate(balls):
- if index == len(balls) - 1:
- print('|', end=' ')
- print(f'{ball:0>2d}', end=' ')
- print()
-
-
-def random_select():
- """随机选择一组号码"""
- # 用生成式生成1到33号的红色球
- red_balls = [x for x in range(1, 34)]
- # 通过无放回随机抽样的方式选中6个红色球
- selected_balls = sample(red_balls, 6)
- # 对红色球进行排序
- selected_balls.sort()
- # 用1到16的随机数表示选中的蓝色球并追加到列表中
- selected_balls.append(randint(1, 16))
- return selected_balls
-
-
-n = int(input('机选几注: '))
-for _ in range(n):
- display(random_select())
-```
-
-> **提示**:彩票的本质是:**虚构一个不劳而获的事,去忽悠一群想不劳而获的人,最终养活一批真正不劳而获的人**。所以,**珍爱生命,远离各种形式的赌博**。
-
-#### 案例4:幸运的女人。
-
-> **说明**:有15个男人和15个女人乘船在海上遇险,为了让一部分人活下来,不得不将其中15个人扔到海里,有个人想了个办法让大家围成一个圈,由某个人开始从1报数,报到9的人就扔到海里面,他后面的人接着从1开始报数,报到9的人继续扔到海里面,直到将15个人扔到海里。最后15个女人都幸免于难,15个男人都被扔到了海里。问这些人最开始是怎么站的,哪些位置是男人,哪些位置是女人。
-
-上面这个问题其实就是著名的约瑟夫环问题。我们可以通过一个列表来保存这30个人是死是活的状态,例如用布尔值`True`表示活着的人,用`False`表示被扔到海里的人。最开始的时候列表中的30个元素都是`True`,然后我们通过循环的方式去执行报数,找到要扔到海里的人并将对应的列表元素标记为`False`,循环会执行到将列表中的15个元素标记为`False`,循环的过程中,列表的索引始终在`0`到`29`的范围,超过`29`就回到`0`,这样刚好可以形成一个闭环。
-
-```Python
-"""
-幸运的女人(约瑟夫环问题)
-
-Version: 0.1
-Author: 骆昊
-"""
-persons = [True] * 30
-# counter - 扔到海里的人数
-# index - 访问列表的索引
-# number - 报数的数字
-counter, index, number = 0, 0, 0
-while counter < 15:
- if persons[index]:
- number += 1
- if number == 9:
- persons[index] = False
- counter += 1
- number = 0
- index += 1
- index %= 30
-for person in persons:
- print('女' if person else '男', end='')
-```
-
-### 简单的总结
-
-**列表和元组都很重要**,学会这两种数据类型,我们能做的事情又多了很多。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
diff --git a/第019课:使用PyCharm开发Python应用程序.md b/第019课:使用PyCharm开发Python应用程序.md
deleted file mode 100644
index 31cd2cd..0000000
--- a/第019课:使用PyCharm开发Python应用程序.md
+++ /dev/null
@@ -1,89 +0,0 @@
-## 第019课:使用PyCharm开发Python应用程序
-
-坚持学习完前18课的小伙伴应该已经感受到了,随着我们对Python语言的认知在逐步加深,我们写的代码也越来越复杂了。“工欲善其事,必先利其器”,如果希望能够更快更好的写出代码,选择一个称手的开发工具是很有必要的。虽然我们之前推荐大家使用的VS Code已经很好了,但是对于复杂应用程序或者商业项目开发来说,我们还有更好的选择,它就是JetBrains公司出品的PyCharm。
-
-### PyCharm的下载和安装
-
-可以在[JetBrains公司的官方网站]()找到PyCharm的[下载链接](https://www.jetbrains.com/pycharm/download/),有两个可供下载的版本,一个是社区版(PyCharm CE),一个是专业版(PyCharm Professional)。社区版在Apache许可证下发布,可以免费使用;专业版在专用许可证下发布,需要购买授权后才能使用,但新用户可以试用30天。很显然,专业版提供了更为强大的功能和对企业级开发的各种支持,但是对于初学者来说,社区版已经足够强大和好用了。安装PyCharm只需要直接运行下载的安装程序,然后持续的点击“Next”(下一步)按钮就可以啦。下面是我在Windows系统下安装PyCharm的截图,安装完成后点击“Finish”(结束)按钮关闭安装向导,然后可以通过双击桌面的快捷方式来运行PyCharm。
-
-![](res/pycharm-installation.png)
-
-### 首次使用的设置
-
-第一次使用PyCharm时,会有一个导入设置的向导,如果之前没有使用PyCharm或者没有保存过设置的就直接选择“Do not import settings”进入下一步即可,下面是我在macOS系统下第一次使用PyCharm时的截图。
-
-![](./res/pycharm-import-settings.png)
-
-专业版的PyCharm是需要激活的,**强烈建议大家在条件允许的情况下支付费用来支持优秀的产品**,如果不用做商业用途或者不需要使用PyCharm的高级功能,我们可以暂时选择试用30天或者使用社区版的PyCharm。如果你是一名学生,希望购买PyCharm来使用,可以看看[教育优惠官方申请指南](https://sales.jetbrains.com/hc/zh-cn/articles/207154369)。如下图所示,我们需要点击“Evaluate”按钮来试用专业版PyCharm。
-
-![](./res/pycharm-activation.png)
-
-接下来是选择UI主题,可以根据个人喜好进行选择,深色的主题比较护眼而浅色的主题对比度更好。
-
-![](./res/pycharm-ui-themes.png)
-
-再接下来是创建可以在“终端”或“命令行提示符”中运行PyCharm的启动脚本,当然也可以不做任何勾选,直接点击“Next: Featured plugins”按钮进入下一环节。
-
-![](./res/pycharm-create-launcher.png)
-
-然后可以选择需要安装哪些插件,我们可以暂时什么都不安装,等需要的时候再来决定。
-
-![](./res/pycharm-install-plugins.png)
-
-最后点击上图右下角的“Start using PyCharm”(开始使用PyCharm)就可以开启你的PyCharm之旅了。
-
-### 用PyCharm创建项目
-
-启动PyCharm之后会来到一个欢迎页,在欢迎页上我们可以选择“Create New Project”(创建新项目)、“Open”(打开已有项目)和“Get from Version Control”(从版本控制系统中检出项目)。
-
-![](./res/pycharm-welcome.png)
-
-如果选择了“Create New Project”来创建新项目就会打一个创建项目的向导页。下图所示是PyCharm专业版创建新项目的向导页,可以看出专业版支持的项目类型非常的多,而社区版只能创建纯Python项目(Pure Python),没有这一系列的选项。
-
-![](./res/pycharm-project-wizard.png)
-
-接下来,我们要为项目创建专属的虚拟环境,每个Python项目最好都在自己专属的虚拟环境中运行,因为每个项目对Python解释器和三方库的需求并不相同,虚拟环境对不同的项目进行了隔离。在上图所示的界面在,我们可以选择“New environment using Virtualenv”(新建虚拟环境),这里的“Virtualenv”是PyCharm默认选择的创建虚拟环境的工具,我们就保留这个默认的选项就可以了。
-
-项目创建完成后就可以开始新建各种文件来书写Python代码了,如下图所示。左侧是项目浏览器,可以看到刚才创建的项目文件夹以及虚拟环境文件夹。我们可以在项目上点击鼠标右键,选择“New”,在选择“Python File”来创建Python代码文件,下图中我们创建了两个Python文件,分别是`poker_game.py`和`salary_system.py`。当然,如果愿意,也可以使用复制粘贴的方式把其他地方的Python代码文件复制到项目文件夹下。
-
-![](./res/pycharm-workspace.png)
-
-在工作窗口点击鼠标右键可以在上下文菜单中找到“Run”选项,例如要运行`salary_system.py`文件,右键菜单会显示“Run 'salary_system'”选项,点击这个选项我们就可以运行Python代码啦,运行结果在屏幕下方的窗口可以看到,如下图所示。
-
-![](res/pycharm-run-result.png)
-
-### 常用操作和快捷键
-
-PyCharm为写Python代码提供了自动补全和高亮语法功能,这也是PyCharm作为集成开发环境(IDE)的基本功能。PyCharm的“File”菜单有一个“Settings”菜单项(macOS上是在“PyCharm”菜单的“Preferences…”菜单项),这个菜单项会打开设置窗口,可以在此处对PyCharm进行设置,如下图所示。
-
-![](res/pycharm-settings.png)
-
-PyCharm的菜单项中有一个非常有用的“Code”菜单,菜单中提供了自动生成代码、自动补全代码、格式化代码、移动代码等选项,这些功能对开发者来说是非常有用的,大家可以尝试使用这些菜单项或者记住它们对应的快捷键,例如在macOS上,格式化代码这个菜单项对应的快捷键是`alt+command+L`。除此之外,“Refactor”菜单也非常有用,它提供了一些重构代码的选项。所谓重构是在不改变代码执行结果的前提下调整代码的结构,这也是资深程序员的一项重要技能。还有一个值得一提的菜单是“VCS”,VCS是“Version Control System”(版本控制系统)的缩写,这个菜单提供了对代码版本管理的支持。版本控制的知识会在其他的课程中为大家讲解。
-
-下表列出了一些PyCharm中特别常用的快捷键,当然如果愿意,也可以通过设置窗口中“Keymap”菜单项自定义快捷键,PyCharm本身也针对不同的操作系统和使用习惯对快捷键进行了分组。
-
-| 快捷键 | 作用 |
-| --------------------------------------------- | -------------------------------------- |
-| `command + j` | 显示可用的代码模板 |
-| `command + b` | 查看函数、类、方法的定义 |
-| `ctrl + space` | 万能代码提示快捷键,一下不行按两下 |
-| `command + alt + l` | 格式化代码 |
-| `alt + enter` | 万能代码修复快捷键 |
-| `ctrl + /` | 注释/反注释代码 |
-| `shift + shift` | 万能搜索快捷键 |
-| `command + d` / `command + y` | 复制/删除一行代码 |
-| `command + shift + -` / `command + shift + +` | 折叠/展开所有代码 |
-| `F2` | 快速定位到错误代码 |
-| `command+ alt + F7` | 查看哪些地方用到了指定的函数、类、方法 |
-
-> **说明**:Windows系统下如果使用PyCharm的默认设置,可以将上面的`command`键换成`ctrl`键即可,唯一的例外是`ctrl + space`那个快捷键,因为它跟Windows系统切换输入法的快捷键是冲突的,所以在Windows系统下默认没有与之对应的快捷键。
-
-### 简单的总结
-
-PyCharm很强大,尤其是专业版的PyCharm,但是要用得很溜也是需要花时间的。建议大家一边使用一边总结,这样才会越来越顺手,工作效率也会随之而提升。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第01课:初识Python.md b/第01课:初识Python.md
new file mode 100644
index 0000000..d34cc8c
--- /dev/null
+++ b/第01课:初识Python.md
@@ -0,0 +1,77 @@
+## 第01课:初识Python
+
+### Python简介
+
+Python是由荷兰人吉多·范罗苏姆(Guido von Rossum)发明的一种编程语言,是目前世界上最受欢迎和拥有最多用户群体的编程语言。
+
+
+
+#### Python的历史
+
+1. 1989年圣诞节:Guido开始写Python语言的编译器。
+2. 1991年2月:第一个Python解释器诞生,它是用C语言实现的,可以调用C语言的库函数。
+3. 1994年1月:Python 1.0正式发布。
+4. 2000年10月:Python 2.0发布,Python的整个开发过程更加透明,生态圈开始慢慢形成。
+5. 2008年12月:Python 3.0发布,引入了诸多现代编程语言的新特性,但并不完全兼容之前的Python代码。
+6. 2020年1月:在Python 2和Python 3共存了11年之后,官方停止了对Python 2的更新和维护,希望用户尽快过渡到Python 3。
+
+> **说明**:大多数软件的版本号一般分为三段,形如A.B.C,其中A表示大版本号,当软件整体重写升级或出现不向后兼容的改变时,才会增加A;B表示功能更新,出现新功能时增加B;C表示小的改动(例如:修复了某个Bug),只要有修改就增加C。
+
+#### Python的优缺点
+
+Python的优点很多,简单为大家列出几点。
+
+1. 简单明确,跟其他很多语言相比,Python更容易上手。
+2. 能用更少的代码做更多的事情,提升开发效率。
+3. 开放源代码,拥有强大的社区和生态圈。
+4. 能够做的事情非常多,有极强的适应性。
+5. 能够在Windows、macOS、Linux等各种系统上运行。
+
+Python最主要的缺点是执行效率低,但是当我们更看重产品的开发效率而不是执行效率的时候,Python就是很好的选择。
+
+#### Python的应用领域
+
+目前Python在Web服务器应用开发、云基础设施开发、**网络数据采集**(爬虫)、**数据分析**、量化交易、**机器学习**、**深度学习**、自动化测试、自动化运维等领域都有用武之地。
+
+### 安装Python环境
+
+想要开始你的Python编程之旅,首先得在计算机上安装Python环境,简单的说就是得安装运行Python程序的工具,通常也称之为Python解释器。我们强烈建议大家安装Python 3的环境,很明显它是目前更好的选择。
+
+#### Windows环境
+
+可以在[Python官方网站](https://www.python.org/downloads/)找到下载链接并下载Python 3的安装程序。
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719222940.png)
+
+对于Windows操作系统,可以下载“executable installer”。需要注意的是,如果在Windows 7环境下安装Python 3,需要先安装Service Pack 1补丁包,大家可以在Windows的“运行”中输入`winver`命令,从弹出的窗口上可以看到你的系统是否安装了该补丁包。如果没有该补丁包,一定要先通过“Windows Update”或者类似“CCleaner”这样的工具自动安装该补丁包,安装完成后通常需要重启你的Windows系统,然后再开始安装Python环境。
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719222956.png)
+
+双击运行刚才下载的安装程序,会打开Python环境的安装向导。在执行安装向导的时候,记得勾选“Add Python 3.x to PATH”选项,这个选项会帮助我们将Python的解释器添加到PATH环境变量中(不理解没关系,照做就行),具体的步骤如下图所示。
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719223007.png)
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719223021.png)
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719223317.png)
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719223332.png)
+
+安装完成后可以打开Windows的“命令行提示符”工具(或“PowerShell”)并输入`python --version`或`python -V`来检查安装是否成功,命令行提示符可以在“运行”中输入`cmd`来打开或者在“开始菜单”的附件中找到它。如果看了Python解释器对应的版本号(如:Python 3.7.8),说明你的安装已经成功了,如下图所示。
+
+![](https://gitee.com/jackfrued/mypic/raw/master/20210719223350.png)
+
+> **说明**:如果安装过程显示安装失败或执行上面的命令报错,很有可能是因为你的Windows系统缺失了一些动态链接库文件或C构建工具导致的问题。可以在[微软官网](https://www.microsoft.com/zh-cn/download/details.aspx?id=48145)下载Visual C++ Redistributable for Visual Studio 2015文件进行修复,64位的系统需要下载有x64标记的安装文件。也可以通过下面的百度云盘地址获取修复工具,运行修复工具,按照如下图所示的方式进行修复,链接: https://pan.baidu.com/s/1iNDnU5UVdDX5sKFqsiDg5Q 提取码: cjs3。
+>
+> ![QQ20210711-0](https://gitee.com/jackfrued/mypic/raw/master/20210816234614.png)
+
+除此之外,你还应该检查一下Python的包管理工具是否已经可用,对应的命令是`pip --version`。
+
+#### macOS环境
+
+macOS自带了Python 2,但是我们需要安装和使用的是Python 3。可以通过Python官方网站提供的[下载链接]()找到适合macOS的“macOS installer”来安装Python 3,安装过程基本不需要做任何勾选,直接点击“下一步”即可。安装完成后,可以在macOS的“终端”工具中输入`python3`命令来调用Python 3解释器,因为如果直接输入`python`,将会调用Python 2的解释器。
+
+### 总结
+
+Python语言可以做很多的事情,也值得我们去学习。要使用Python语言,首先需要在自己的计算机上安装Python环境,也就是运行Python程序的Python解释器。
+
diff --git a/第025课:正则表达式的应用.md b/第025课:正则表达式的应用.md
deleted file mode 100644
index 6eeab26..0000000
--- a/第025课:正则表达式的应用.md
+++ /dev/null
@@ -1,165 +0,0 @@
-## 第025课:正则表达式的应用
-
-### 正则表达式相关知识
-
-在编写处理字符串的程时,经常会遇到在一段文本中查找符合某些规则的字符串的需求,正则表达式就是用于描述这些规则的工具,换句话说,我们可以使用正则表达式来定义字符串的匹配模式,即如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉。
-
-举一个简单的例子,如果你在Windows操作系统中使用过文件查找并且在指定文件名时使用过通配符(`*`和`?`),那么正则表达式也是与之类似的用 来进行文本匹配的工具,只不过比起通配符正则表达式更强大,它能更精确地描述你的需求,当然你付出的代价是书写一个正则表达式比使用通配符要复杂得多,因为任何给你带来好处的东西都需要你付出对应的代价。
-
-再举一个例子,我们从某个地方(可能是一个文本文件,也可能是网络上的一则新闻)获得了一个字符串,希望在字符串中找出手机号和座机号。当然我们可以设定手机号是11位的数字(注意并不是随机的11位数字,因为你没有见过“25012345678”这样的手机号),而座机号则是类似于“区号-号码”这样的模式,如果不使用正则表达式要完成这个任务就会比较麻烦。最初计算机是为了做数学运算而诞生的,处理的信息基本上都是数值,而今天我们在日常工作中处理的信息基本上都是文本数据,我们希望计算机能够识别和处理符合某些模式的文本,正则表达式就显得非常重要了。今天几乎所有的编程语言都提供了对正则表达式操作的支持,Python通过标准库中的`re`模块来支持正则表达式操作。
-
-关于正则表达式的相关知识,大家可以阅读一篇非常有名的博文叫[《正则表达式30分钟入门教程》](https://deerchao.net/tutorials/regex/regex.htm),读完这篇文章后你就可以看懂下面的表格,这是我们对正则表达式中的一些基本符号进行的扼要总结。
-
-| 符号 | 解释 | 示例 | 说明 |
-| ------------------------ | -------------------------------- | ------------------- | ------------------------------------------------------------ |
-| . | 匹配任意字符 | b.t | 可以匹配bat / but / b#t / b1t等 |
-| \\w | 匹配字母/数字/下划线 | b\\wt | 可以匹配bat / b1t / b_t等
但不能匹配b#t |
-| \\s | 匹配空白字符(包括\r、\n、\t等) | love\\syou | 可以匹配love you |
-| \\d | 匹配数字 | \\d\\d | 可以匹配01 / 23 / 99等 |
-| \\b | 匹配单词的边界 | \\bThe\\b | |
-| ^ | 匹配字符串的开始 | ^The | 可以匹配The开头的字符串 |
-| $ | 匹配字符串的结束 | .exe$ | 可以匹配.exe结尾的字符串 |
-| \\W | 匹配非字母/数字/下划线 | b\\Wt | 可以匹配b#t / b@t等
但不能匹配but / b1t / b_t等 |
-| \\S | 匹配非空白字符 | love\\Syou | 可以匹配love#you等
但不能匹配love you |
-| \\D | 匹配非数字 | \\d\\D | 可以匹配9a / 3# / 0F等 |
-| \\B | 匹配非单词边界 | \\Bio\\B | |
-| [] | 匹配来自字符集的任意单一字符 | [aeiou] | 可以匹配任一元音字母字符 |
-| [^] | 匹配不在字符集中的任意单一字符 | [^aeiou] | 可以匹配任一非元音字母字符 |
-| * | 匹配0次或多次 | \\w* | |
-| + | 匹配1次或多次 | \\w+ | |
-| ? | 匹配0次或1次 | \\w? | |
-| {N} | 匹配N次 | \\w{3} | |
-| {M,} | 匹配至少M次 | \\w{3,} | |
-| {M,N} | 匹配至少M次至多N次 | \\w{3,6} | |
-| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
-| (?#) | 注释 | | |
-| (exp) | 匹配exp并捕获到自动命名的组中 | | |
-| (? <name>exp) | 匹配exp并捕获到名为name的组中 | | |
-| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
-| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
-| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |
-| (?!exp) | 匹配后面不是exp的位置 | | |
-| (?a.\*?b | 将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 |
-| +? | 重复1次或多次,但尽可能少重复 | | |
-| ?? | 重复0次或1次,但尽可能少重复 | | |
-| {M,N}? | 重复M到N次,但尽可能少重复 | | |
-| {M,}? | 重复M次以上,但尽可能少重复 | | |
-
-> **说明:** 如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用`\`进行转义处理,例如想匹配小数点可以写成`\.`就可以了,因为直接写`.`会匹配任意字符;同理,想匹配圆括号必须写成`\(`和`\)`,否则圆括号被视为正则表达式中的分组。
-
-### Python对正则表达式的支持
-
-Python提供了`re`模块来支持正则表达式相关操作,下面是`re`模块中的核心函数。
-
-| 函数 | 说明 |
-| -------------------------------------------- | ------------------------------------------------------------ |
-| compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
-| match(pattern, string, flags=0) | 用正则表达式匹配字符串 成功返回匹配对象 否则返回None |
-| search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None |
-| split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
-| sub(pattern, repl, string, count=0, flags=0) | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数 |
-| fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
-| findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
-| finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
-| purge() | 清除隐式编译的正则表达式的缓存 |
-| re.I / re.IGNORECASE | 忽略大小写匹配标记 |
-| re.M / re.MULTILINE | 多行匹配标记 |
-
-> **说明:** 上面提到的`re`模块中的这些函数,实际开发中也可以用正则表达式对象的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,那么先通过`compile`函数编译正则表达式并创建出正则表达式对象无疑是更为明智的选择。
-
-下面我们通过一系列的例子来告诉大家在Python中如何使用正则表达式。
-
-#### 例子1:验证输入用户名和QQ号是否有效并给出对应的提示信息。
-
-```Python
-"""
-要求:用户名必须由字母、数字或下划线构成且长度在6~20个字符之间,QQ号是5~12的数字且首位不能为0
-"""
-import re
-
-username = input('请输入用户名: ')
-qq = input('请输入QQ号: ')
-# match函数的第一个参数是正则表达式字符串或正则表达式对象
-# match函数的第二个参数是要跟正则表达式做匹配的字符串对象
-m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
-if not m1:
- print('请输入有效的用户名.')
-# fullmatch函数要求字符串和正则表达式完全匹配
-# 所以正则表达式没有写起始符和结束符
-m2 = re.fullmatch(r'[1-9]\d{4,11}', qq)
-if not m2:
- print('请输入有效的QQ号.')
-if m1 and m2:
- print('你输入的信息是有效的!')
-```
-
-> **提示:** 上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了`r`),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作`\\`,例如表示数字的`\d`得书写成`\\d`,这样不仅写起来不方便,阅读的时候也会很吃力。
-
-#### 例子2:从一段文字中提取出国内手机号码。
-
-下面这张图是截止到2017年底,国内三家运营商推出的手机号段。
-
-![](./res/tel-start-number.png)
-
-```Python
-import re
-
-# 创建正则表达式对象,使用了前瞻和回顾来保证手机号前后不应该再出现数字
-pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
-sentence = '''重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
-不是15600998765,也是110或119,王大锤的手机号才是15600998765。'''
-# 方法一:查找所有匹配并保存到一个列表中
-tels_list = re.findall(pattern, sentence)
-for tel in tels_list:
- print(tel)
-print('--------华丽的分隔线--------')
-
-# 方法二:通过迭代器取出匹配对象并获得匹配的内容
-for temp in pattern.finditer(sentence):
- print(temp.group())
-print('--------华丽的分隔线--------')
-
-# 方法三:通过search函数指定搜索位置找出所有匹配
-m = pattern.search(sentence)
-while m:
- print(m.group())
- m = pattern.search(sentence, m.end())
-```
-
-> **说明:** 上面匹配国内手机号的正则表达式并不够好,因为像14开头的号码只有145或147,而上面的正则表达式并没有考虑这种情况,要匹配国内手机号,更好的正则表达式的写法是:`(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)`,国内好像已经有19和16开头的手机号了,但是这个暂时不在我们考虑之列。
-
-#### 例子3:替换字符串中的不良内容
-
-```Python
-import re
-
-sentence = 'Oh, shit! 你丫是傻叉吗? Fuck you.'
-purified = re.sub('fuck|shit|[傻煞沙][比屄逼叉缺吊屌碉雕]',
- '*', sentence, flags=re.IGNORECASE)
-print(purified) # Oh, *! 你丫是*吗? * you.
-```
-
-> **说明:**` re`模块的正则表达式相关函数中都有一个`flags`参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用[按位或运算符](http://www.runoob.com/python/python-operators.html#ysf5)进行叠加,如`flags=re.I | re.M`。
-
-#### 例子4:拆分长字符串
-
-```Python
-import re
-
-poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
-sentences_list = re.split(r'[,。, .]', poem)
-sentences_list = [sentence for sentence in sentences_list if sentence]
-for sentence in sentences_list:
- print(sentence)
-```
-
-### 简单的总结
-
-正则表达式在字符串的处理和匹配上真的非常强大,通过上面的例子相信大家已经感受到了正则表达式的魅力,当然写一个正则表达式对新手来说并不是那么容易,但是很多事情都是熟能生巧,大胆的去尝试就行了,有一个在线的[正则表达式测试工具](https://c.runoob.com/front-end/854)相信能够在一定程度上帮到大家。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第026课:用Python处理图像.md b/第026课:用Python处理图像.md
deleted file mode 100644
index b1ff2f4..0000000
--- a/第026课:用Python处理图像.md
+++ /dev/null
@@ -1,179 +0,0 @@
-## 第026课:用Python处理图像
-
-### 入门知识
-
-1. 颜色。如果你有使用颜料画画的经历,那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色,事实上这三种颜色就是美术中的三原色,它们是不能再分解的基本颜色。在计算机中,我们可以将红、绿、蓝三种色光以不同的比例叠加来组合成其他的颜色,因此这三种颜色就是色光三原色。在计算机系统中,我们通常会将一个颜色表示为一个RGB值或RGBA值(其中的A表示Alpha通道,它决定了透过这个图像的像素,也就是透明度)。
-
- | 名称 | RGB值 | 名称 | RGB值 |
- | :---------: | :-------------: | :----------: | :-----------: |
- | White(白) | (255, 255, 255) | Red(红) | (255, 0, 0) |
- | Green(绿) | (0, 255, 0) | Blue(蓝) | (0, 0, 255) |
- | Gray(灰) | (128, 128, 128) | Yellow(黄) | (255, 255, 0) |
- | Black(黑) | (0, 0, 0) | Purple(紫) | (128, 0, 128) |
-
-2. 像素。对于一个由数字序列表示的图像来说,最小的单位就是图像上单一颜色的小方格,这些小方块都有一个明确的位置和被分配的色彩数值,而这些一小方格的颜色和位置决定了该图像最终呈现出来的样子,它们是不可分割的单位,我们通常称之为像素(pixel)。每一个图像都包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小,大家如果爱好拍照或者自拍,对像素这个词就不会陌生。
-
-### 用Pillow处理图像
-
-Pillow是由从著名的Python图像处理库PIL发展出来的一个分支,通过Pillow可以实现图像压缩和图像处理等各种操作。可以使用下面的命令来安装Pillow。
-
-```Shell
-pip install pillow
-```
-
-Pillow中最为重要的是`Image`类,可以通过`Image`模块的`open`函数来读取图像并获得`Image`类型的对象。
-
-1. 读取和显示图像
-
- ```Python
- from PIL import Image
-
- # 读取图像获得Image对象
- image = Image.open('guido.jpg')
- # 通过Image对象的format属性获得图像的格式
- print(image.format) # JPEG
- # 通过Image对象的size属性获得图像的尺寸
- print(image.size) # (500, 750)
- # 通过Image对象的mode属性获取图像的模式
- print(image.mode) # RGB
- # 通过Image对象的show方法显示图像
- image.show()
- ```
-
- ![](res/image-show.png)
-
-2. 剪裁图像
-
- ```Python
- # 通过Image对象的crop方法指定剪裁区域剪裁图像
- image.crop((80, 20, 310, 360)).show()
- ```
-
- ![](./res/image-crop.png)
-
-3. 生成缩略图
-
- ```Python
- # 通过Image对象的thumbnail方法生成指定尺寸的缩略图
- image.thumbnail((128, 128))
- image.show()
- ```
-
- ![](./res/image-thumbnail.png)
-
-4. 缩放和黏贴图像
-
- ```Python
- # 读取骆昊的照片获得Image对象
- luohao_image = Image.open('luohao.png')
- # 读取吉多的照片获得Image对象
- guido_image = Image.open('guido.jpg')
- # 从吉多的照片上剪裁出吉多的头
- guido_head = guido_image.crop((80, 20, 310, 360))
- width, height = guido_head.size
- # 使用Image对象的resize方法修改图像的尺寸
- # 使用Image对象的paste方法将吉多的头粘贴到骆昊的照片上
- luohao_image.paste(guido_head.resize((int(width / 1.5), int(height / 1.5))), (172, 40))
- luohao_image.show()
- ```
-
- ![](./res/image-paste.png)
-
-5. 旋转和翻转
-
- ```Python
- image = Image.open('guido.jpg')
- # 使用Image对象的rotate方法实现图像的旋转
- image.rotate(45).show()
- # 使用Image对象的transpose方法实现图像翻转
- # Image.FLIP_LEFT_RIGHT - 水平翻转
- # Image.FLIP_TOP_BOTTOM - 垂直翻转
- image.transpose(Image.FLIP_LEFT_RIGHT).show()
- ```
-
- ![](./res/image-rotate.png)
-
- ![](./res/image-transpose.png)
-
-6. 操作像素
-
- ```Python
- for x in range(80, 310):
- for y in range(20, 360):
- # 通过Image对象的putpixel方法修改图像指定像素点
- image.putpixel((x, y), (128, 128, 128))
- image.show()
- ```
-
- ![](./res/image-putpixel.png)
-
-7. 滤镜效果
-
- ```Python
- from PIL import ImageFilter
-
- # 使用Image对象的filter方法对图像进行滤镜处理
- # ImageFilter模块包含了诸多预设的滤镜也可以自定义滤镜
- image.filter(ImageFilter.CONTOUR).show()
- ```
-
- ![](./res/image-filter.png)
-
-### 使用Pillow绘图
-
-Pillow中有一个名为`ImageDraw`的模块,该模块的`Draw`函数会返回一个`ImageDraw`对象,通过`ImageDraw`对象的`arc`、`line`、`rectangle`、`ellipse`、`polygon`等方法,可以在图像上绘制出圆弧、线条、矩形、椭圆、多边形等形状,也可以通过该对象的`text`方法在图像上添加文字。
-
-![](res/pillow-imagedraw.png)
-
-要绘制如上图所示的图像,完整的代码如下所示。
-
-```Python
-import random
-
-from PIL import Image, ImageDraw, ImageFont
-
-
-def random_color():
- """生成随机颜色"""
- red = random.randint(0, 255)
- green = random.randint(0, 255)
- blue = random.randint(0, 255)
- return red, green, blue
-
-
-width, height = 800, 600
-# 创建一个800*600的图像,背景色为白色
-image = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
-# 创建一个ImageDraw对象
-drawer = ImageDraw.Draw(image)
-# 通过指定字体和大小获得ImageFont对象
-font = ImageFont.truetype('Kongxin.ttf', 32)
-# 通过ImageDraw对象的text方法绘制文字
-drawer.text((300, 50), 'Hello, world!', fill=(255, 0, 0), font=font)
-# 通过ImageDraw对象的line方法绘制两条对角直线
-drawer.line((0, 0, width, height), fill=(0, 0, 255), width=2)
-drawer.line((width, 0, 0, height), fill=(0, 0, 255), width=2)
-xy = width // 2 - 60, height // 2 - 60, width // 2 + 60, height // 2 + 60
-# 通过ImageDraw对象的rectangle方法绘制矩形
-drawer.rectangle(xy, outline=(255, 0, 0), width=2)
-# 通过ImageDraw对象的ellipse方法绘制椭圆
-for i in range(4):
- left, top, right, bottom = 150 + i * 120, 220, 310 + i * 120, 380
- drawer.ellipse((left, top, right, bottom), outline=random_color(), width=8)
-# 显示图像
-image.show()
-# 保存图像
-image.save('result.png')
-```
-
-> **注意**:上面代码中使用的字体文件需要根据自己准备,可以选择自己喜欢的字体文件并放置在代码目录下。
-
-### 简单的总结
-
-使用Python语言做开发,除了可以用Pillow来处理图像外,还可以使用更为强大的OpenCV库来完成图形图像的处理,OpenCV(**Open** Source **C**omputer **V**ision Library)是一个跨平台的计算机视觉库,可以用来开发实时图像处理、计算机视觉和模式识别程序。在我们的日常工作中,有很多繁琐乏味的任务其实都可以通过Python程序来处理,编程的目的就是让计算机帮助我们解决问题,减少重复乏味的劳动。通过本章节的学习,相信大家已经感受到了使用Python程序绘图P图的乐趣,其实Python能做的事情还远不止这些,继续你的学习吧。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第029课:用Python操作PDF文件.md b/第029课:用Python操作PDF文件.md
deleted file mode 100644
index 00cadc7..0000000
--- a/第029课:用Python操作PDF文件.md
+++ /dev/null
@@ -1,140 +0,0 @@
-## 第029课:用Python操作PDF文件
-
-PDF是Portable Document Format的缩写,这类文件通常使用`.pdf`作为其扩展名。在日常开发工作中,最容易遇到的就是从PDF中读取文本内容以及用已有的内容生成PDF文档这两个任务。
-
-### 从PDF中提取文本
-
-在Python中,可以使用名为`PyPDF2`的三方库来读取PDF文件,可以使用下面的命令来安装它。
-
-```Bash
-pip install PyPDF2 -i https://pypi.doubanio.com/simple
-```
-
-`PyPDF2`没有办法从PDF文档中提取图像、图表或其他媒体,但它可以提取文本,并将其返回为Python字符串。
-
-```Python
-import PyPDF2
-
-reader = PyPDF2.PdfFileReader('test.pdf')
-page = reader.getPage(0)
-print(page.extractText())
-```
-
-当然,`PyPDF2`并不是什么样的PDF文档都能提取出文字来,这个问题就我所知并没有什么特别好的解决方法,尤其是在提取中文的时候。之前给成都一汽大众做企业内训的时候,就有学员提出了一个从报关信息的PDF文档中提取中文文本内容的需求,但是我尝试了多种方法都失败了。网上也有很多讲解从PDF中提取文字的文章,推荐大家自行阅读[《三大神器助力Python提取pdf文档信息》](https://cloud.tencent.com/developer/article/1395339)一文进行了解。
-
-要从PDF文件中提取文本也可以使用一个三方的命令行工具,具体的做法如下所示。
-
-```Bash
-pip install pdfminer.six
-pdf2text.py test.pdf
-```
-
-### 旋转和叠加页面
-
-上面的代码中通过创建`PdfFileReader`对象的方式来读取PDF文档,该对象的`getPage`方法可以获得PDF文档的指定页并得到一个`Page`对象,利用`Page`对象的`rotateClockwise`和`rotateCounterClockwise`方法可以实现页面的顺时针和逆时针方向旋转,代码如下所示。
-
-```Python
-import PyPDF2
-
-reader = PyPDF2.PdfFileReader('test.pdf')
-page = reader.getPage(0)
-page.rotateClockwise(90)
-writer = PyPDF2.PdfFileWriter()
-writer.addPage(page)
-with open('test-rotated.pdf', 'wb') as file:
- writer.write(file)
-```
-
-`Page`对象还有一个名为`mergePage`的方法,可以将一个页面和另一个页面进行叠加,对于叠加后的页面,我们还是使用`PdfFileWriter`对象的`addPage`将其添加到一个新的`PDF`文档中,有兴趣的读者可以自行尝试。
-
-### 加密PDF文件
-
-使用`PyPDF2`中的`PdfFileWrite`对象可以为PDF文档加密,如果需要给一系列的PDF文档设置统一的访问口令,使用Python程序来处理就会非常的方便。
-
-```Python
-import PyPDF2
-
-with open('test.pdf', 'rb') as file:
- # 通过PdfReader读取未加密的PDF文档
- reader = PyPDF2.PdfFileReader(file)
- writer = PyPDF2.PdfFileWriter()
- for page_num in range(reader.numPages):
- # 通过PdfReader的getPage方法获取指定页码的页
- # 通过PdfWriter方法的addPage将添加读取到的页
- writer.addPage(reader.getPage(page_num))
- # 通过PdfWriter的encrypt方法加密PDF文档
- writer.encrypt('foobared')
- # 将加密后的PDF文档写入指定的文件中
- with open('test-encrypted.pdf', 'wb') as file2:
- writer.write(file2)
-```
-
-> **提示**:按照上面的方法也可以读取多个PDF文件的多个页面并将其合并到一个PDF文件中。
-
-### 创建PDF文件
-
-创建PDF文档需要三方库`reportlab`的支持,该库的使用方法可以参考它的[官方文档](https://www.reportlab.com/docs/reportlab-userguide.pdf),安装的方法如下所示。
-
-```Bash
-pip install reportlab
-```
-
-下面通过一个例子为大家展示`reportlab`的用法。
-
-```Python
-import random
-
-from reportlab.lib import colors
-from reportlab.lib.pagesizes import A4
-from reportlab.pdfgen import canvas
-from reportlab.platypus import Table, TableStyle
-
-# 创建Canvas对象(PDF文档对象)
-doc = canvas.Canvas('demo.pdf', pagesize=A4)
-# 获取A4纸的尺寸
-width, height = A4
-# 读取图像
-image = canvas.ImageReader('guido.jpg')
-# 通过PDF文档对象的drawImage绘制图像内容
-doc.drawImage(image, (width - 250) // 2, height - 475, 250, 375)
-# 设置字体和颜色
-doc.setFont('Helvetica', 32)
-doc.setFillColorRGB(0.8, 0.4, 0.2)
-# 通过PDF文档对象的drawString输出字符串内容
-doc.drawString(10, height - 50, "Life is short, I use Python!")
-# 保存当前页创建新页面
-doc.showPage()
-# 准备表格需要的数据
-scores = [[random.randint(60, 100) for _ in range(3)] for _ in range(5)]
-names = ('Alice', 'Bob', 'Jack', 'Lily', 'Tom')
-for row, name in enumerate(names):
- scores[row].insert(0, name)
-scores.insert(0, ['Name', 'Verbal', 'Math', 'Logic'])
-# 创建一个Table对象(第一个参数是数据,第二个和第三个参数是列宽和行高)
-table = Table(scores, 50, 20)
-# 设置表格样式(对齐方式和内外边框粗细颜色)
-table.setStyle(TableStyle([
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
- ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.red),
- ('BOX', (0, 0), (-1, -1), 0.25, colors.black)
-]))
-table.split(0, 0)
-# 通过Table对象的drawOn在PDF文档上绘制表格
-table.drawOn(doc, (width - 200) // 2, height - 150)
-# 保存当前页创建新页面
-doc.showPage()
-# 保存PDF文档
-doc.save()
-```
-
-> **说明**:上面的代码使用了很多字面常量来指定位置和尺寸,在商业项目开发中应该避免这样的硬代码(hard code),因为这样的代码不仅可读性差,维护起来也是一场噩梦。如果项目中需要动态生成PDF文档且PDF文档的格式是相对固定的,可以将上面的字面常量处理成符号常量。记住:**符号常量优于字面常量**。Python语言没有内置定义常量的语法,但是可以约定变量名使用全大写字母的变量就是常量。
-
-### 简单的总结
-
-在学习完上面的内容之后,相信大家已经知道像合并多个PDF文件这样的工作应该如何用Python代码来处理了,赶紧自己动手试一试吧。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第02课:第一个Python程序.md b/第02课:第一个Python程序.md
new file mode 100644
index 0000000..776fc72
--- /dev/null
+++ b/第02课:第一个Python程序.md
@@ -0,0 +1,130 @@
+## 第02课:第一个Python程序
+
+在上一课中,我们已经了解了Python语言并安装了运行Python程序所需的环境,相信大家已经迫不及待的想开始自己的Python编程之旅了。首先我们来看看应该在哪里编写我们的Python程序。
+
+### 编写代码的工具
+
+#### 交互式环境
+
+我们打开Windows的“命令提示符”工具,输入命令`python`然后回车就可以进入到Python的交互式环境中。所谓交互式环境,就是我们输入一行代码回车,代码马上会被执行,如果代码有产出结果,那么结果会被显示在窗口中。例如:
+
+```Bash
+Python 3.7.6
+Type "help", "copyright", "credits" or "license" for more information.
+>>> 2 * 3
+6
+>>> 2 + 3
+5
+```
+
+> **提示**:使用macOS系统的用户需要打开“终端”工具,输入`python3`进入交互式环境。
+
+如果希望退出交互式环境,可以在交互式环境中输入`quit()`,如下所示。
+
+```Bash
+>>> quit()
+```
+
+#### 更好的交互式环境 - IPython
+
+Python默认的交互式环境用户体验并不怎么好,我们可以用IPython来替换掉它,因为IPython提供了更为强大的编辑和交互功能。我们可以使用Python的包管理工具`pip`来安装IPython,如下所示。
+
+```bash
+pip install ipython
+```
+
+> **温馨提示**:在使用上面的命令安装IPython之前,可以先通过`pip config set global.index-url https://pypi.doubanio.com/simple`命令将`pip`的下载源修改为国内的豆瓣网,否则下载安装的过程可能会非常的缓慢。
+
+可以使用下面的命令启动IPython,进入交互式环境。
+
+```bash
+ipython
+```
+
+#### 文本编辑器 - Visual Studio Code
+
+Visual Studio Code(通常简称为VSCode)是一个由微软开发能够在Windows、 Linux和macOS等操作系统上运行的代码编辑神器。它支持语法高亮、自动补全、多点编辑、运行调试等一系列便捷功能,而且能够支持多种编程语言。如果大家要选择一款高级文本编辑工具,强烈建议使用VSCode。关于VSCode的[下载](https://code.visualstudio.com/)、安装和使用,推荐大家阅读一篇名为[《VScode安装使用》]()的文章。
+
+#### 集成开发环境 - PyCharm
+
+如果用Python开发商业项目,我们推荐大家使用更为专业的工具PyCharm。PyCharm是由捷克一家名为[JetBrains](https://www.jetbrains.com/)的公司开发的用于Python项目开发的集成开发环境(IDE)。所谓集成开发环境,通常是指工具中提供了编写代码、运行代码、调试代码、分析代码、版本控制等一系列功能,因此特别适合商业项目的开发。在JetBrains的官方网站上提供了PyCharm的[下载链接](),其中社区版(Community)是免费的但功能相对弱小(其实已经足够强大了),专业版(Professional)功能非常强大,但需要按年或月付费使用,新用户可以试用30天时间。
+
+运行PyCharm,可以看到如下图所示的欢迎界面,可以选择“New Project”来创建一个新的项目。
+
+
+
+创建项目的时候需要指定项目的路径并创建运行项目的”虚拟环境“,如下图所示。
+
+
+
+项目创建好以后会出现如下图所示的画面,我们可以通过在项目文件夹上点击鼠标右键,选择“New”菜单下的“Python File”来创建一个Python文件,创建好的Python文件会自动打开进入可编辑的状态。
+
+![image-20210720133621079](https://gitee.com/jackfrued/mypic/raw/master/20210720133621.png)
+
+写好代码后,可以在编辑代码的窗口点击鼠标右键,选择“Run”菜单项来运行代码,下面的“Run”窗口会显示代码的执行结果,如下图所示。
+
+![image-20210720134039848](https://gitee.com/jackfrued/mypic/raw/master/20210720134039.png)
+
+PyCharm常用的快捷键如下表所示,我们也可以在“File”菜单的“Settings”中定制PyCharm的快捷键(macOS系统是在“PyCharm”菜单的“Preferences”中对快捷键进行设置)。
+
+表1. PyCharm常用快捷键。
+
+| 快捷键 | 作用 |
+| --------------------------------------- | -------------------------------------- |
+| `ctrl + j` | 显示可用的代码模板 |
+| `ctrl + b` | 查看函数、类、方法的定义 |
+| `ctrl + alt + l` | 格式化代码 |
+| `alt + enter` | 万能代码修复快捷键 |
+| `ctrl + /` | 注释/反注释代码 |
+| `shift + shift` | 万能搜索快捷键 |
+| `ctrl + d` / `ctrl + y` | 复制/删除一行代码 |
+| `ctrl + shift + -` / `ctrl + shift + +` | 折叠/展开所有代码 |
+| `F2` | 快速定位到错误代码 |
+| `ctrl + alt + F7` | 查看哪些地方用到了指定的函数、类、方法 |
+
+> **说明**:使用macOS系统,可以将上面的`ctrl`键换成`command`键,在macOS系统上,可以使用`ctrl + space`组合键来获得万能提示,在Windows系统上不能使用该快捷键,因为它跟Windows默认的切换输入法的快捷键是冲突的,需要重新设置。
+
+### hello, world
+
+按照行业惯例,我们学习任何一门编程语言写的第一个程序都是输出`hello, world`,因为这段代码是伟大的丹尼斯·里奇(C语言之父,和肯·汤普森一起开发了Unix操作系统)和布莱恩·柯尼汉(awk语言的发明者)在他们的不朽著作*The C Programming Language*中写的第一段代码。
+
+```Python
+print('hello, world')
+```
+
+### 运行程序
+
+如果不使用PyCharm这样的集成开发环境,我们可以将上面的代码命名为`hello.py`,对于Windows操作系统,可以在你保存代码的目录下先按住键盘上的`shift`键再点击鼠标右键,这时候鼠标右键菜单中会出现“命令提示符”选项,点击该选项就可以打开“命令提示符”工具,我们输入下面的命令。
+
+```Shell
+python hello.py
+```
+
+> **提醒**:我们也可以在任意位置打开“命令提示符”,然后将需要执行的Python代码通过拖拽的方式拖入到“命令提示符”中,这样相当于指定了文件的绝对路径来运行该文件中的Python代码。再次提醒,macOS系统要通过`python3`命令来运行该程序。
+
+你可以尝试将上面程序单引号中的`hello, world`换成其他内容;你也可以尝试着多写几个这样的语句,看看会运行出怎样的结果。需要提醒大家,上面代码中的`print('hello, world')`就是一条完整的语句,我们用Python写程序,最好每一行代码中只有一条语句。虽然使用`;`分隔符可以将多个语句写在一行代码中,但是最好不要这样做,因为代码会变得非常难看。
+
+### 注释你的代码
+
+注释是编程语言的一个重要组成部分,用于在源代码中解释代码的作用从而增强程序的可读性。当然,我们也可以将源代码中暂时不需要运行的代码段通过注释来去掉,这样当你需要重新使用这些代码的时候,去掉注释符号就可以了。简单的说,**注释会让代码更容易看懂但不会影响程序的执行结果**。
+
+Python中有两种形式的注释:
+
+1. 单行注释:以`#`和空格开头,可以注释掉从`#`开始后面一整行的内容。
+2. 多行注释:三个引号开头,三个引号结尾,通常用于添加多行说明性内容。
+
+```Python
+"""
+第一个Python程序 - hello, world
+
+Version: 0.1
+Author: 骆昊
+"""
+# print('hello, world')
+print("你好,世界!")
+```
+
+### 总结
+
+到这里,我们已经把第一个Python程序运行起来了,是不是很有成就感?只要你坚持学习下去,再过一段时间,我们就可以用Python制作小游戏、编写爬虫程序、完成办公自动化操作等。**写程序本身就是一件很酷的事情**,在未来编程就像英语一样,**对很多人来说或都是必须要掌握的技能**。
+
diff --git a/第037课:用Python程序操作MongoDB.md b/第037课:用Python程序操作MongoDB.md
deleted file mode 100644
index a4d9ab0..0000000
--- a/第037课:用Python程序操作MongoDB.md
+++ /dev/null
@@ -1,13 +0,0 @@
-## 第037课:用Python程序操作MongoDB
-
-
-
-### 简单的总结
-
-
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第038课:用Python程序绘制统计图表.md b/第038课:用Python程序绘制统计图表.md
deleted file mode 100644
index 0294c3a..0000000
--- a/第038课:用Python程序绘制统计图表.md
+++ /dev/null
@@ -1,13 +0,0 @@
-## 第038课:用Python程序绘制统计图表
-
-
-
-### 简单的总结
-
-
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第003课:Python语言元素之变量.md b/第03课:Python语言元素之变量.md
similarity index 72%
rename from 第003课:Python语言元素之变量.md
rename to 第03课:Python语言元素之变量.md
index 8462ba1..c3b8a82 100644
--- a/第003课:Python语言元素之变量.md
+++ b/第03课:Python语言元素之变量.md
@@ -1,6 +1,6 @@
-## 第003课:Python语言元素之变量
+## 第03课:Python语言元素之变量
-作为一个程序员,可能经常会被外行人问到两个问题,其一是“什么是(计算机)程序”,其二是“写(计算机)程序能做什么”,这里我们先对两个问题做一个回答。**程序是指令的集合**,**写程序就是用指令控制计算机做我们想让它做的事情**。那么,为什么要用Python语言来写程序呢?因为**Python语言简单优雅**,相比C、C++、Java这样的编程语言,**Python对初学者更加友好**,当然这并不是说Python不像其他语言那样强大,**Python几乎是无所不能的**,在第一节课的时候,我们就说到了Python可以用于服务器程序开发、云平台开发、数据分析、机器学习等各个领域。当然,Python语言还可以用来粘合其他语言开发的系统,所以也经常被戏称为“**胶水语言**”。
+作为一个程序员,可能经常会被外行问到两个问题,其一是“什么是(计算机)程序”,其二是“写(计算机)程序能做什么”,这里我先对这两个问题做一个回答。**程序是指令的集合**,**写程序就是用指令控制计算机做我们想让它做的事情**。那么,为什么要用Python语言来写程序呢?因为**Python语言简单优雅**,相比C、C++、Java这样的编程语言,**Python对初学者更加友好**,当然这并不是说Python不像其他语言那样强大,**Python几乎是无所不能的**,在第一节课的时候,我们就说到了Python可以用于服务器程序开发、云平台开发、数据分析、机器学习等各个领域。当然,Python语言还可以用来粘合其他语言开发的系统,所以也经常被戏称为“**胶水语言**”。
### 一些计算机常识
@@ -12,10 +12,10 @@
要想在计算机内存中保存数据,首先就得说一说变量这个概念。在编程语言中,**变量是数据的载体**,简单的说就是一块用来保存数据的内存空间,**变量的值可以被读取和修改**,这是所有计算和控制的基础。计算机能处理的数据有很多种类型,最常见的就是数值,除了数值之外还有文本、图形、音频、视频等各种各样的数据。虽然数据在计算机中都是以二进制形态存在的,但是我们可以用不同类型的变量来表示数据类型的差异。**Python中的数据类型很多**,而且也**允许我们自定义新的数据类型**(这一点在后面会讲到),这里我们需要先了解几种常用的数据类型。
-- 整型(int):Python中可以处理任意大小的整数,而且支持二进制(如`0b100`,换算成十进制是4)、八进制(如`0o100`,换算成十进制是64)、十进制(`100`)和十六进制(`0x100`,换算成十进制是256)的表示法。
-- 浮点型(float):浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如`123.456`)之外还支持科学计数法(如`1.23456e2`)。
-- 字符串型(str):字符串是以单引号或双引号括起来的任意文本,比如`'hello'`和`"hello"`。
-- 布尔型(bool):布尔值只有`True`、`False`两种值,要么是`True`,要么是`False`。
+- 整型(`int`):Python中可以处理任意大小的整数,而且支持二进制(如`0b100`,换算成十进制是4)、八进制(如`0o100`,换算成十进制是64)、十进制(`100`)和十六进制(`0x100`,换算成十进制是256)的表示法。
+- 浮点型(`float`):浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,浮点数除了数学写法(如`123.456`)之外还支持科学计数法(如`1.23456e2`)。
+- 字符串型(`str`):字符串是以单引号或双引号括起来的任意文本,比如`'hello'`和`"hello"`。
+- 布尔型(`bool`):布尔值只有`True`、`False`两种值,要么是`True`,要么是`False`。
### 变量命名
@@ -24,13 +24,13 @@
- 硬性规则:
- 规则1:变量名由**字母**、数字和**下划线**构成,数字不能开头。需要说明的是,这里说的字母指的是Unicode字符,Unicode称为万国码,囊括了世界上大部分的文字系统,这也就意味着中文、日文、希腊字母等都可以作为变量名中的字符,但是像`!`、`@`、`#`这些特殊字符是不能出现在变量名中的,而且我们强烈建议大家**尽可能使用英文字母**。
- 规则2:**大小写敏感**,简单的说就是大写的`A`和小写的`a`是两个不同的变量。
- - 规则3:变量名**不要跟Python语言的关键字**(有特殊含义的单词,后面会讲到)和**保留字**(如函数、模块等的名字)**发生重名的冲突**。
+ - 规则3:变量名**不要跟Python语言的关键字**(有特殊含义的单词,后面会讲到)和**保留字**(如已有的函数、模块等的名字)**发生重名的冲突**。
- 非硬性规则:
- 规则1:变量名通常使用小写英文字母,多个单词用下划线进行连接。
- - 规则2:受保护的变量用单个下划线开头(后面会讲到)。
- - 规则3:私有的变量用两个下划线开头(后面会讲到)。
+ - 规则2:受保护的变量用单个下划线开头。
+ - 规则3:私有的变量用两个下划线开头。
-当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名时做到**见名知意**也非常重要。
+规则2和规则3大家暂时不用理解,后面自然会明白的。当然,作为一个专业的程序员,给变量(事实上应该是所有的标识符)命名时做到**见名知意**也非常重要。
### 变量的使用
@@ -108,9 +108,3 @@ print(ord('a')) # 97
### 总结
在Python程序中,我们可以**使用变量来保存数据**,**变量有不同的类型**,**变量可以做运算**(下一课会有详细的讲解),**也可以通过内置函数来转换变量类型**。
-
->**温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao。也可以加**QQ交流群**询问。
->
->付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
->免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第004课:Python语言元素之运算符.md b/第04课:Python语言元素之运算符.md
similarity index 94%
rename from 第004课:Python语言元素之运算符.md
rename to 第04课:Python语言元素之运算符.md
index 8951890..2cd74c5 100644
--- a/第004课:Python语言元素之运算符.md
+++ b/第04课:Python语言元素之运算符.md
@@ -1,4 +1,4 @@
-## 第004课:Python语言元素之运算符
+## 第04课:Python语言元素之运算符
Python语言支持很多种运算符,我们先用一个表格为大家列出这些运算符,然后选择一些马上就会用到的运算符为大家进行讲解。
@@ -147,9 +147,3 @@ print(is_leap)
### 总结
通过上面的例子相信大家感受到了,学会使用运算符以及由运算符构成的表达式,就可以帮助我们解决很多实际的问题,**运算符和表达式对于任何一门编程语言都是非常重要的**。
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第005课:分支结构.md b/第05课:分支结构.md
similarity index 90%
rename from 第005课:分支结构.md
rename to 第05课:分支结构.md
index dd40118..3047dcb 100644
--- a/第005课:分支结构.md
+++ b/第05课:分支结构.md
@@ -1,4 +1,4 @@
-## 第005课:分支结构
+## 第05课:分支结构
### 应用场景
@@ -30,7 +30,9 @@ else:
如果要构造出更多的分支,可以使用`if...elif...else...`结构或者嵌套的`if...else...`结构,下面的代码演示了如何利用多分支结构实现分段函数求值。
-![](res/piecewise-function.png)
+$$
+f(x) = \begin{cases} 3x - 5, & x \gt 1 \\ x + 2, & -1 \le x \le 1 \\ 5x + 3, & x \lt -1 \end{cases}
+$$
```Python
"""
@@ -69,7 +71,7 @@ else:
print(f'f({x}) = {y}')
```
-> **说明:** 大家可以自己感受和评判一下这两种写法到底是哪一种更好。在[**Python之禅**](https://zhuanlan.zhihu.com/p/111843067)中有这么一句话:“**Flat is better than nested**”,之所以提倡代码“扁平化”是因为嵌套结构的嵌套层次如果很多,会严重的影响代码的可读性,所以能使用扁平化结构时就不要使用嵌套结构。
+> **说明:** 大家可以自己感受和评判一下这两种写法到底是哪一种更好。在[**Python之禅**](https://zhuanlan.zhihu.com/p/111843067)中有这么一句话:“**Flat is better than nested**”,之所以提倡代码“扁平化”,是因为代码嵌套的层次如果很多,会严重的影响代码的可读性,所以使用更为扁平化的结构在很多场景下都是较好的选择。
### 一些例子
@@ -141,11 +143,5 @@ else:
### 简单的总结
-学会了Python中的分支结构和循环结构,我们就可以用Python程序来解决很多实际的问题了。这一节课相信已经帮助大家记住了`if`、`elif`、`else`这几个关键字以及如何使用它们来构造分支结构,下一节课我们为大家介绍循环结构,学完这两次课你一定会发现,你能写出很多很多非常有意思的代码。坚持努力,继续加油!
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
+学会了Python中的分支结构和循环结构,我们就可以用Python程序来解决很多实际的问题了。这一节课相信已经帮助大家记住了`if`、`elif`、`else`这几个关键字以及如何使用它们来构造分支结构,下一节课我们为大家介绍循环结构,学完这两次课你一定会发现,你能写出很多很多非常有意思的代码。继续加油!
diff --git a/第006课:循环结构.md b/第06课:循环结构.md
similarity index 64%
rename from 第006课:循环结构.md
rename to 第06课:循环结构.md
index 3eb6767..709e366 100644
--- a/第006课:循环结构.md
+++ b/第06课:循环结构.md
@@ -1,14 +1,14 @@
-## 第006课:循环结构
+## 第06课:循环结构
### 应用场景
-我们在写程序的时候,一定会遇到需要重复执行某条或某些指令的场景。例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中,让机器人向球门方向移动就是一个需要重复的动作,当然这里还会用到上一课讲的分支结构来判断机器人是否持球以及是否进入射门范围。再举一个简单的例子,如果要实现每隔1秒中在屏幕上打印一次“hello, world”并持续打印一个小时,我们肯定不能够直接把`print('hello, world')`这句代码写3600遍,这里同样需要循环结构。
+我们在写程序的时候,一定会遇到需要重复执行某条指令或某些指令的场景。例如用程序控制机器人踢足球,如果机器人持球而且还没有进入射门范围,那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中,让机器人向球门方向移动就是一个需要重复的动作,当然这里还会用到上一课讲的分支结构来判断机器人是否持球以及是否进入射门范围。再举一个简单的例子,如果要实现每隔1秒中在屏幕上打印一次“hello, world”并持续打印一个小时,我们肯定不能够直接把`print('hello, world')`这句代码写3600遍,这里我们需要构造循环结构。
-循环结构就是程序中控制某条或某些指令重复执行的结构。在Python中构造循环结构有两种做法,一种是`for-in`循环,一种是`while`循环。
+所谓循环结构,就是程序中控制某条或某些指令重复执行的结构。在Python中构造循环结构有两种做法,一种是`for-in`循环,另一种是`while`循环。
### for-in循环
-如果明确的知道循环执行的次数,我们推荐使用`for-in`循环,例如计算1到100的和。 被`for-in`循环控制的语句块也是通过缩进的方式来确定的,这一点跟分支结构完全相同,大家看看下面的代码就明白了。
+如果明确的知道循环执行的次数,我们推荐使用`for-in`循环,例如输出100行的”hello, world“。 被`for-in`循环控制的语句块也是通过缩进的方式来构造的,这一点跟分支结构完全相同,大家看看下面的代码就明白了。
```Python
"""
@@ -23,12 +23,12 @@ for x in range(1, 101):
print(total)
```
-需要说明的是上面代码中的`range(1, 101)`可以用来构造一个从1到100的范围,当我们把这样一个范围放到`for-in`循环中,就可以通过前面的循环变量`x`依次取出从1到100的整数。当然,`range`的用法非常灵活,下面给出了一个例子:
+需要说明的是上面代码中的`range(1, 101)`可以用来构造一个从`1`到`100`的范围,当我们把这样一个范围放到`for-in`循环中,就可以通过前面的循环变量`x`依次取出从`1`到`100`的整数。当然,`range`的用法非常灵活,下面给出了一个例子:
- `range(101)`:可以用来产生0到100范围的整数,需要注意的是取不到101。
- `range(1, 101)`:可以用来产生1到100范围的整数,相当于前面是闭区间后面是开区间。
-- `range(1, 101, 2)`:可以用来产生1到100的奇数,其中2是步长,即每次数值递增的值。
-- `range(100, 0, -2)`:可以用来产生100到1的偶数,其中-2是步长,即每次数字递减的值。
+- `range(1, 101, 2)`:可以用来产生1到100的奇数,其中2是步长,即每次递增的值。
+- `range(100, 0, -2)`:可以用来产生100到1的偶数,其中-2是步长,即每次递减的值。
知道了这一点,我们可以用下面的代码来实现1~100之间的偶数求和。
@@ -47,9 +47,9 @@ print(total)
### while循环
-如果要构造不知道具体循环次数的循环结构,我们推荐使用`while`循环。`while`循环通过一个能够产生或转换出`bool`值的表达式来控制循环,表达式的值为`True`则继续循环;表达式的值为`False`则结束循环。
+如果要构造不知道具体循环次数的循环结构,我们推荐使用`while`循环。`while`循环通过一个能够产生`bool`值的表达式来控制循环,当表达式的值为`True`时则继续循环,当表达式的值为`False`时则结束循环。
-下面我们通过一个“猜数字”的小游戏来看看如何使用`while`循环。猜数字游戏的规则是:计算机出一个1到100之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息(大一点、小一点或猜对了),如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。
+下面我们通过一个“猜数字”的小游戏来看看如何使用`while`循环。猜数字游戏的规则是:计算机出一个`1`到`100`之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息(大一点、小一点或猜对了),如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。
```Python
"""
@@ -98,7 +98,7 @@ for i in range(1, 10):
print()
```
-很显然,在上面的代码中,外层循环用来控制一共会产生9行的输出,而内层循环用来控制每一行会输出多少列。内层循环中的输出就是九九表一行中的所有列,所以在内层循环完成时,有一个`print()`来实现换行输出的效果。
+很显然,在上面的代码中,外层循环用来控制一共会产生`9`行的输出,而内层循环用来控制每一行会输出多少列。内层循环中的输出就是九九表一行中的所有列,所以在内层循环完成时,有一个`print()`来实现换行输出的效果。
### 循环的例子
@@ -140,8 +140,6 @@ Author: 骆昊
x = int(input('x = '))
y = int(input('y = '))
-if x > y:
- x, y = y, x # Python中可以用这样的方式来交换两个变量的值
for factor in range(x, 0, -1):
if x % factor == 0 and y % factor == 0:
print(f'{x}和{y}的最大公约数是{factor}')
@@ -152,9 +150,3 @@ for factor in range(x, 0, -1):
### 简单的总结
学会了Python中的分支结构和循环结构,我们就可以解决很多实际的问题了。通过这节课的学习,大家应该已经知道了可以用`for`和`while`关键字来构造循环结构。**如果知道循环的次数,我们通常使用**`for`**循环**;如果**循环次数不能确定,可以用**`while`**循环**。在循环中还**可以使用**`break`**来提前结束循环**。
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
diff --git a/第007课:分支和循环结构的应用.md b/第07课:分支和循环结构的应用.md
similarity index 90%
rename from 第007课:分支和循环结构的应用.md
rename to 第07课:分支和循环结构的应用.md
index ddd4e20..b1f25ce 100644
--- a/第007课:分支和循环结构的应用.md
+++ b/第07课:分支和循环结构的应用.md
@@ -1,12 +1,12 @@
-## 第007课:分支和循环结构的应用
+## 第07课:分支和循环结构的应用
-通过上两节课的学习,大家对Python中的分支和循环结构已经有了感性的认识。**分支和循环结构**的重要性不言而喻,它**是构造程序逻辑的基础**,对于初学者来说也是相对困难的部分。大部分初学者在学习了分支和循环结构后都能理解它们的用途和用法,但是遇到实际问题的时候又无法下手;**看懂别人的代码很容易,但是要自己写出同样的代码却又很难**。如果你也有同样的问题和困惑,千万不要沮丧,这只是因为你才刚刚开始编程之旅,**你的练习量还没有达到让你可以随心所欲的写出代码的程度**,只要加强编程练习,这个问题迟早都会解决的。下面我们就为大家讲解一些经典的案例。
+通过上两节课的学习,大家对Python中的分支和循环结构已经有了感性的认识。**分支和循环结构**的重要性不言而喻,它**是构造程序逻辑的基础**,对于初学者来说也是比较困难的部分。大部分初学者在学习了分支和循环结构后都能理解它们的用途和用法,但是遇到实际问题的时候又无法下手;**看懂别人的代码很容易,但是要自己写出同样的代码却又很难**。如果你也有同样的问题和困惑,千万不要沮丧,这只是因为你才刚刚开始编程之旅,**你的练习量还没有达到让你可以随心所欲的写出代码的程度**,只要加强编程练习,这个问题迟早都会解决的。下面我们就为大家讲解一些经典的案例。
### 经典小案例
#### 例子1:寻找水仙花数。
-> **说明**:水仙花数也被称为超完全数字不变数、自恋数、自幂数、阿姆斯特朗数,它是一个3位数,该数字每个位上数字的立方之和正好等于它本身,例如:13 + 53 + 33 = 153。
+> **说明**:水仙花数也被称为超完全数字不变数、自恋数、自幂数、阿姆斯特朗数,它是一个3位数,该数字每个位上数字的立方之和正好等于它本身,例如:$ 153=1^3+5^3+3^3 $。
这个题目的关键是将一个三位数拆分为个位、十位、百位,这一点利用Python中的`//`(整除)和`%`(求模)运算符其实很容易做到,代码如下所示。
@@ -127,16 +127,14 @@ print('你破产了, 游戏结束!')
Version: 0.1
Author: 骆昊
"""
-# 前两个数都是1
-a, b = 1, 1
-print(a, b, end=' ')
-# 通过递推公式算出后面的18个数
-for _ in range(18):
+
+a, b = 0, 1
+for _ in range(20):
a, b = b, a + b
- print(b, end=' ')
+ print(a)
```
-#### 例子5:打印素数。
+#### 例子5:打印100以内的素数。
> **说明**:素数指的是只能被1和自身整除的正整数(不包括1)。
@@ -165,9 +163,3 @@ for num in range(2, 100):
还是那句话:**分支结构和循环结构非常重要**,是构造程序逻辑的基础,**一定要通过大量的练习来达到融会贯通**。刚才讲到的CRAPS赌博游戏那个例子可以作为一个标准,如果你能很顺利的完成这段代码,那么分支和循环结构的知识你就已经掌握了。
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
diff --git a/第011课:常用数据结构之列表.md b/第08课:常用数据结构之列表.md
similarity index 87%
rename from 第011课:常用数据结构之列表.md
rename to 第08课:常用数据结构之列表.md
index 3a9137c..ab83b07 100644
--- a/第011课:常用数据结构之列表.md
+++ b/第08课:常用数据结构之列表.md
@@ -1,6 +1,6 @@
-## 第011课:常用数据结构之列表
+## 第08课:常用数据结构之列表
-在开始本节课的内容之前,我们先给大家一个编程任务,将一颗色子掷6000次,统计每个点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以用1到6均匀分布的随机数来模拟掷色子,然后用6个变量分别记录每个点数出现的次数,相信大家都能写出下面的代码。
+在开始本节课的内容之前,我们先给大家一个编程任务,将一颗色子掷`6000`次,统计每个点数出现的次数。这个任务对大家来说应该是非常简单的,我们可以用`1`到`6`均匀分布的随机数来模拟掷色子,然后用`6`个变量分别记录每个点数出现的次数,相信大家都能写出下面的代码。
```Python
import random
@@ -33,7 +33,7 @@ print(f'5点出现了{f5}次')
print(f'6点出现了{f6}次')
```
-看看上面的代码,相信大家一定觉得它非常的“笨重”和“丑陋”,更可怕的是,如果要统计掷2颗或者更多的色子统计每个点数出现的次数,那就需要定义更多的变量,写更多的分支结构。讲到这里,相信大家一定想问:有没有办法用一个变量来保存多个数据,有没有办法用统一的代码对多个数据进行操作?答案是肯定的,在Python中我们可以通过容器类型的变量来保存和操作多个数据,我们首先为大家介绍列表(list)这种新的数据类型。
+看看上面的代码,相信大家一定觉得它非常的“笨重”和“丑陋”,更可怕的是,如果要统计掷两颗或者更多的色子统计每个点数出现的次数,那就需要定义更多的变量,写更多的分支结构。讲到这里,相信大家一定想问:有没有办法用一个变量来保存多个数据,有没有办法用统一的代码对多个数据进行操作?答案是肯定的,在Python中我们可以通过容器类型的变量来保存和操作多个数据,我们首先为大家介绍列表(list)这种新的数据类型。
### 定义和使用列表
@@ -46,7 +46,7 @@ items1 = [35, 12, 99, 68, 55, 87]
items2 = ['Python', 'Java', 'Go', 'Kotlin']
```
-除此以外,还可以通过Python内置的`list`函数将其他序列变成列表。准确的说,`list`并不是一个函数,而是创建列表对象的构造器(后面会讲到对象和构造器这两个概念)。
+除此以外,还可以通过Python内置的`list`函数将其他序列变成列表。准确的说,`list`并不是一个普通的函数,它是创建列表对象的构造器(后面会讲到对象和构造器这两个概念)。
```Python
items1 = list(range(1, 10))
@@ -172,7 +172,7 @@ print(items) # []
需要提醒大家,在使用`remove`方法删除元素时,如果要删除的元素并不在列表中,会引发`ValueError`异常,错误消息是:`list.remove(x): x not in list`。在使用`pop`方法删除元素时,如果索引的值超出了范围,会引发`IndexError`异常,错误消息是:`pop index out of range`。
-从列表中删除元素其实还有一种方式,就是使用Python中的`del`关键字后面跟要删除的元素,这种做法跟使用`pop`方法指定索引删除元素没有实质性的区别,但后者会返回删除的元素,前者在性能上略优(`del`对应字节码指令是`DELETE_SUBSCR`,而`pop`对应的字节码指令是`CALL_METHOD`和`POP_TOP`)。
+从列表中删除元素其实还有一种方式,就是使用Python中的`del`关键字后面跟要删除的元素,这种做法跟使用`pop`方法指定索引删除元素没有实质性的区别,但后者会返回删除的元素,前者在性能上略优(`del`对应字节码指令是`DELETE_SUBSCR`,而`pop`对应的字节码指令是`CALL_METHOD`和`POP_TOP`,不理解就跳过,不用管它!!!)。
```Python
items = ['Python', 'Java', 'Go', 'Kotlin']
@@ -280,7 +280,8 @@ print(scores) # [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
```Python
# 嵌套的列表需要多次索引操作才能获取元素
scores[0][0] = 95
-print(scores) # [[95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0]]
+print(scores)
+# [[95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0]]
```
我们不去过多的解释为什么会出现这样的问题,如果想深入研究这个问题,可以通过[Python Tutor]()网站的可视化代码执行功能,看看创建列表时计算机内存中发生了怎样的变化,下面的图就是在这个网站上生成的。建议大家不去纠结这个问题,现阶段只需要记住不能用`[[0] * 3] * 5]`这种方式来创建嵌套列表就行了。那么创建嵌套列表的正确做法是什么呢,下面的代码会给你答案。
@@ -288,20 +289,10 @@ print(scores) # [[95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0], [95, 0, 0]]
```Python
scores = [[0] * 3 for _ in range(5)]
scores[0][0] = 95
-print(scores) # [[95, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
+print(scores)
+# [[95, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
```
-![](res/embedded-list.png)
-
-在讲完下节课的知识点后,我们会把这个案例写得更为完整一些,实现录入5个学生3门课程的成绩,统计出每个学生和每门课程的平均分。
-
### 简单的总结
-Python中的列表底层是一个可以动态扩容的数组,列表元素在内存中也是连续存储的,所以可以实现随机访问(通过一个有效的索引获取到对应的元素且操作时间与列表元素个数无关)。我们暂时不去触碰这些底层存储细节以及列表每个方法的渐近时间复杂度(执行这个方法耗费的时间跟列表元素个数的关系),等需要的时候再告诉大家。现阶段,大家只需要知道**列表是容器**,可以**保存各种类型的数据**,**可以通过索引操作列表元素**就可以了。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
+Python中的列表底层是一个可以动态扩容的数组,列表元素在内存中也是连续存储的,所以可以实现随机访问(通过一个有效的索引获取到对应的元素且操作时间与列表元素个数无关)。我们暂时不去触碰这些底层存储细节以及列表每个方法的渐近时间复杂度(执行这个方法耗费的时间跟列表元素个数的关系),等需要的时候再告诉大家。现阶段,大家只需要知道**列表是容器**,可以**保存各种类型的数据**,**可以通过索引操作列表元素**,知道这些就足够了。
diff --git a/第012课:常用数据结构之元组.md b/第09课:常用数据结构之元组.md
similarity index 77%
rename from 第012课:常用数据结构之元组.md
rename to 第09课:常用数据结构之元组.md
index 4e6a9c4..4226a3b 100644
--- a/第012课:常用数据结构之元组.md
+++ b/第09课:常用数据结构之元组.md
@@ -1,4 +1,4 @@
-## 第012课:常用数据结构之元组
+## 第09课:常用数据结构之元组
上一节课为大家讲解了Python中的列表,它是一种容器型数据类型,我们可以通过定义列表类型的变量来保存和操作多个元素。当然,Python中容器型的数据类型肯定不止列表一种,接下来我们为大家讲解另一种重要的容器型数据类型,它的名字叫元组(tuple)。
@@ -105,7 +105,7 @@ i, j, k, l, *m = a
print(i, j, k, l, m) # 1 10 100 1000 []
```
-需要说明一点,解包语法对所有的序列都成立,这就意味着对字符串、列表以及我们之前讲到的`range`函数返回的范围序列都可以使用解包语法。大家可以尝试运行下面的代码,看看会出现怎样的结果。
+需要说明一点,解包语法对所有的序列都成立,这就意味着对列表以及我们之前讲到的`range`函数返回的范围序列都可以使用解包语法。大家可以尝试运行下面的代码,看看会出现怎样的结果。
```Python
a, b, *c = range(1, 10)
@@ -116,21 +116,6 @@ a, *b, c = 'hello'
print(a, b, c)
```
-现在我们可以反过来思考一下函数的可变参数,可变参数其实就是将多个参数打包成了一个元组,可以通过下面的代码来证明这一点。
-
-```Python
-def add(*args):
- print(type(args), args)
- total = 0
- for val in args:
- total += val
- return total
-
-
-add(1, 10, 20) # (1, 10, 20)
-add(1, 2, 3, 4, 5) # (1, 2, 3, 4, 5)
-```
-
#### 例子2:交换两个变量的值。
交换两个变量的值是编程语言中的一个经典案例,在很多编程语言中,交换两个变量的值都需要借助一个中间变量才能做到,如果不用中间变量就需要使用比较晦涩的位运算来实现。在Python中,交换两个变量`a`和`b`的值只需要使用如下所示的代码。
@@ -147,27 +132,6 @@ a, b, c = b, c, a
需要说明的是,上面并没有用到打包和解包语法,Python的字节码指令中有`ROT_TWO`和`ROT_THREE`这样的指令可以实现这个操作,效率是非常高的。但是如果有多于三个变量的值要依次互换,这个时候没有直接可用的字节码指令,执行的原理就是我们上面讲解的打包和解包操作。
-#### 例子3:让函数返回多个值。
-
-有的时候一个函数执行完成后可能需要返回多个值,这个时候元组类型应该是比较方便的选择。例如,编写一个找出列表中最大值和最小的函数。
-
-```Python
-def find_max_and_min(items):
- """找出列表中最大和最小的元素
- :param items: 列表
- :return: 最大和最小元素构成的二元组
- """
- max_one, min_one = items[0], items[0]
- for item in items:
- if item > max_one:
- max_one = item
- elif item < min_one:
- min_one = item
- return max_one, min_one
-```
-
-上面函数的`return`语句中有两个值,这两个值会组装成一个二元组然后返回。所以调用`find_max_and_min`函数会得到这个二元组,如果愿意也可以通过解包语法将二元组中的两个值分别赋给两个变量。
-
### 元组和列表的比较
这里还有一个非常值得探讨的问题,Python中已经有了列表类型,为什么还需要元组这样的类型呢?这个问题对于初学者来说似乎有点困难,不过没有关系,我们先抛出观点,大家可以一边学习一边慢慢体会。
@@ -201,10 +165,4 @@ def find_max_and_min(items):
### 简单的总结
-**列表和元组都是容器型的数据类型**,即一个变量可以保存多个数据。**列表是可变数据类型**,**元组是不可变数据类型**,所以列表添加元素、删除元素、清空、排序等方法对于元组来说是不成立的。但是列表和元组都可以进行**拼接**、**成员运算**、**索引和切片**这些操作,就如同之前讲到的字符串类型一样,因为字符串就是字符按一定顺序构成的序列,在这一点上三者并没有什么区别。我们**推荐大家使用列表的生成式语法来创建列表**,它很好用,也是Python中非常有特色的语法。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+**列表和元组都是容器型的数据类型**,即一个变量可以保存多个数据。**列表是可变数据类型**,**元组是不可变数据类型**,所以列表添加元素、删除元素、清空、排序等方法对于元组来说是不成立的。但是列表和元组都可以进行**拼接**、**成员运算**、**索引和切片**这些操作,后面我们要讲到的字符串类型也是这样,因为字符串就是字符按一定顺序构成的序列,在这一点上三者并没有什么区别。我们**推荐大家使用列表的生成式语法来创建列表**,它很好用,也是Python中非常有特色的语法。
diff --git a/第009课:常用数据结构之字符串.md b/第10课:常用数据结构之字符串.md
similarity index 83%
rename from 第009课:常用数据结构之字符串.md
rename to 第10课:常用数据结构之字符串.md
index 87eb633..91ac34c 100644
--- a/第009课:常用数据结构之字符串.md
+++ b/第10课:常用数据结构之字符串.md
@@ -1,17 +1,17 @@
-## 第009课:字符串的使用
+## 第10课:字符串的使用
第二次世界大战促使了现代电子计算机的诞生,世界上的第一台通用电子计算机叫ENIAC(电子数值积分计算机),诞生于美国的宾夕法尼亚大学,占地167平米,重量27吨,每秒钟大约能够完成约5000次浮点运算,如下图所示。ENIAC诞生之后被应用于导弹弹道的计算,而数值计算也是现代电子计算机最为重要的一项功能。
-![](res/ENIAC.jpg)
+
随着时间的推移,虽然数值运算仍然是计算机日常工作中最为重要的组成部分,但是今天的计算机还要处理大量的以文本形式存在的信息。如果我们希望通过Python程序来操作本这些文本信息,就必须要先了解字符串这种数据类型以及与它相关的知识。
### 字符串的定义
所谓**字符串**,就是**由零个或多个字符组成的有限序列**,一般记为:
-
-![](res/string-definition.png)
-
+$$
+s = a_1a_2 \cdots a_n \,\,\,\,\, (0 \le n \le \infty)
+$$
在Python程序中,如果我们把单个或多个字符用单引号或者双引号包围起来,就可以表示一个字符串。字符串中的字符可以是特殊符号、英文字母、中文字符、日文的平假名或片假名、希腊字母、[Emoji字符]()等。
```Python
@@ -97,7 +97,7 @@ print(ord('王'), ord('大'), ord('锤')) # 29579 22823 38180
print(s3 > s4, s3 <= s4) # True False
```
-需要强调一下的是,字符串的比较运算比较的是字符串的内容,Python中还有一个`is`运算符(身份运算符),如果用`is`来比较两个字符串,它比较的是两个变量对应的字符串是否在内存中相同的位置(内存地址),简单的说就是两个变量是否对应内存中的同一个字符串。看看下面的代码就比较清楚`is`运算符的作用了。
+需要强调一下的是,字符串的比较运算比较的是字符串的内容,Python中还有一个`is`运算符(身份运算符),如果用`is`来比较两个字符串,它比较的是两个变量对应的字符串对象的内存地址(不理解先跳过),简单的说就是两个变量是否对应内存中的同一个字符串。看看下面的代码就比较清楚`is`运算符的作用了。
```Python
s1 = 'hello world'
@@ -126,7 +126,7 @@ print(s2 in s1) # False
```Python
s = 'hello, world'
-print(len(s)) # 12
+print(len(s)) # 12
print(len('goodbye, world')) # 14
```
@@ -207,7 +207,7 @@ print(s[::-1]) # 654321cba
print(s[::-2]) # 642ca
```
-#### 循环遍历
+#### 循环遍历每个字符
如果希望从字符串中取出每个字符,可以使用`for`循环对字符串进行遍历,有两种方式。
@@ -307,7 +307,7 @@ print(s2.isalnum()) # True
#### 格式化字符串
-在Python中,字符串类型可以通过`center`、`ljust`、`rjust`方法做居中、左对齐和右对齐的处理。
+在Python中,字符串类型可以通过`center`、`ljust`、`rjust`方法做居中、左对齐和右对齐的处理。如果要在字符串的左侧补零,也可以使用`zfill`方法。
```Python
s = 'hello, world'
@@ -318,6 +318,9 @@ print(s.center(20, '*')) # ****hello, world****
print(s.rjust(20)) # hello, world
# ljust方法以宽度20将字符串左对齐并在右侧填充~
print(s.ljust(20, '~')) # hello, world~~~~~~~~
+# 在字符串的左侧补零
+print('33'.zfill(5)) # 00033
+print('-33'.zfill(5)) # -0033
```
我们之前讲过,在用`print`函数输出字符串时,可以用下面的方式对字符串进行格式化。
@@ -352,8 +355,8 @@ print(f'{a} * {b} = {a * b}')
| `3.1415926` | `{:+.2f}` | `'+3.14'` | 带符号保留小数点后两位 |
| `-1` | `{:+.2f}` | `'-1.00'` | 带符号保留小数点后两位 |
| `3.1415926` | `{:.0f}` | `'3'` | 不带小数 |
-| `123` | `{:0>10d}` | `0000000123` | 左边补`0`,补够10位 |
-| `123` | `{:x<10d}` | `123xxxxxxx` | 右边补`x` ,补够10位 |
+| `123` | `{:0>10d}` | `'0000000123'` | 左边补`0`,补够10位 |
+| `123` | `{:x<10d}` | `'123xxxxxxx'` | 右边补`x` ,补够10位 |
| `123` | `{:>10d}` | `' 123'` | 左边补空格,补够10位 |
| `123` | `{:<10d}` | `'123 '` | 右边补空格,补够10位 |
| `123456789` | `{:,}` | `'123,456,789'` | 逗号分隔格式 |
@@ -370,17 +373,57 @@ s = ' jackfrued@126.com \t\r\n'
print(s.strip()) # jackfrued@126.com
```
+#### 替换操作
+
+如果希望用新的内容替换字符串中指定的内容,可以使用`replace`方法,代码如下所示。`replace`方法的第一个参数是被替换的内容,第二个参数是替换后的内容,还可以通过第三个参数指定替换的次数。
+
+```Python
+s = 'hello, world'
+print(s.replace('o', '@')) # hell@, w@rld
+print(s.replace('o', '@', 1)) # hell@, world
+```
+
+#### 拆分/合并操作
+
+可以使用字符串的`split`方法将一个字符串拆分为多个字符串(放在一个列表中),也可以使用字符串的`join`方法将列表中的多个字符串连接成一个字符串,代码如下所示。
+
+```Python
+s = 'I love you'
+words = s.split()
+print(words) # ['I', 'love', 'you']
+print('#'.join(words)) # I#love#you
+```
+
+需要说明的是,`split`方法默认使用空格进行拆分,我们也可以指定其他的字符来拆分字符串,而且还可以指定最大拆分次数来控制拆分的效果,代码如下所示。
+
+```Python
+s = 'I#love#you#so#much'
+words = s.split('#')
+print(words) # ['I', 'love', 'you', 'so', 'much']
+words = s.split('#', 3)
+print(words) # ['I', 'love', 'you', 'so#much']
+```
+
+#### 编码/解码操作
+
+Python中除了字符串`str`类型外,还有一种表示二进制数据的字节串类型(`bytes`)。所谓字节串,就是**由零个或多个字节组成的有限序列**。通过字符串的`encode`方法,我们可以按照某种编码方式将字符串编码为字节串,我们也可以使用字节串的`decode`方法,将字节串解码为字符串,代码如下所示。
+
+```Python
+a = '骆昊'
+b = a.encode('utf-8')
+c = a.encode('gbk')
+print(b, c) # b'\xe9\xaa\x86\xe6\x98\x8a' b'\xc2\xe6\xea\xbb'
+print(b.decode('utf-8'))
+print(c.decode('gbk'))
+```
+
+注意,如果编码和解码的方式不一致,会导致乱码问题(无法再现原始的内容)或引发`UnicodeDecodeError`错误导致程序崩溃。
+
#### 其他方法
-除了上面讲到的方法外,字符串类型还有很多方法,如拆分、合并、编码、解码等,这些方法等我们用到的时候再为大家进行续点讲解。对于字符串类型来说,还有一个常用的操作是对字符串进行匹配检查,即检查字符串是否满足某种特定的模式。例如,一个网站对用户注册信息中用户名和邮箱的检查,就属于模式匹配检查。实现模式匹配检查的工具叫做正则表达式,Python语言通过标准库中的`re`模块提供了对正则表达式的支持,我们会在后续的课程中为大家讲解这个知识点。
+对于字符串类型来说,还有一个常用的操作是对字符串进行匹配检查,即检查字符串是否满足某种特定的模式。例如,一个网站对用户注册信息中用户名和邮箱的检查,就属于模式匹配检查。实现模式匹配检查的工具叫做正则表达式,Python语言通过标准库中的`re`模块提供了对正则表达式的支持,我们会在后续的课程中为大家讲解这个知识点。
### 简单的总结
知道如何表示和操作字符串对程序员来说是非常重要的,因为我们需要处理文本信息,Python中操作字符串可以用拼接、切片等运算符,也可以使用字符串类型的方法。
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
diff --git a/第014课:常用数据结构之集合.md b/第11课:常用数据结构之集合.md
similarity index 75%
rename from 第014课:常用数据结构之集合.md
rename to 第11课:常用数据结构之集合.md
index 32e133f..173b043 100644
--- a/第014课:常用数据结构之集合.md
+++ b/第11课:常用数据结构之集合.md
@@ -1,12 +1,12 @@
-## 第014课:常用数据结构之集合
+## 第11课:常用数据结构之集合
-在学习了列表和元组之后,我们再来学习一种容器型的数据类型,它的名字叫集合(set)。说到集合这个词大家一定不会陌生,在数学课本上就有这个概念。通常我们对集合的定义是“**把一定范围的、确定的、可以区别的事物当作一个整体来看待**”,集合中的各个事物通常称为集合的元素。集合应该满足以下特性:
+在学习了列表和元组之后,我们再来学习一种容器型的数据类型,它的名字叫集合(set)。说到集合这个词大家一定不会陌生,在数学课本上就有这个概念。通常我们对集合的定义是“**把一定范围的、确定的、可以区别的事物当作一个整体来看待**”,集合中的各个事物通常称为集合的**元素**。集合应该满足以下特性:
1. **无序性**:一个集合中,每个元素的地位都是相同的,元素之间是无序的。
-2. **互异性**:一个集合中,任何两个元素都认为是不相同的,即每个元素只能出现一次。
-3. **确定性**:给定一个集合,任给一个元素,该元素或者属于或者不属于该集合,二者必居其一,不允许有模棱两可的情况出现。
+2. **互异性**:一个集合中,任何两个元素都是不相同的,即元素在集合中只能出现一次。
+3. **确定性**:给定一个集合和一个任意元素,该元素要么属这个集合,要么不属于这个集合,二者必居其一,不允许有模棱两可的情况出现。
-Python程序中的集合跟数学上的集合是完全一致的,需要强调的是上面所说的无序性和互异性。无序性说明集合中的元素并不像列中的元素那样一个挨着一个,可以通过索引的方式实现随机访问(随机访问指的是给定一个有效的范围,随机抽取出一个数字,然后通过这个数字获取到对应的元素),所以Python中的**集合肯定不能够支持索引运算**。另外,集合的互异性决定了**集合中不能有重复元素**,这一点也是集合区别于列表的关键,说得更直白一些就是,Python中的集合类型具有去重特性。当然,Python中的集合一定是支持`in`和`not in`成员运算的,这样就可以确定一个元素是否属于集合,也就是上面所说的集合的确定性。**集合的成员运算在性能上要优于列表的成员运算**,这是集合的底层存储特性决定的,此处我们暂时不做讨论,先记下这个结论即可。
+Python程序中的集合跟数学上的集合是完全一致的,需要强调的是上面所说的无序性和互异性。无序性说明集合中的元素并不像列中的元素那样一个挨着一个,可以通过索引实现随机访问(随机访问指的是给定一个有效的范围,随机抽取出一个数字,然后通过这个数字可以获取到对应的元素),所以Python中的**集合肯定不能够支持索引运算**。另外,集合的互异性决定了**集合中不能有重复元素**,这一点也是集合区别于列表的关键,说得更直白一些就是,Python中的集合类型会对其中的元素做去重处理。Python中的集合一定是支持`in`和`not in`成员运算的,这样就可以确定一个元素是否属于集合,也就是上面所说的集合的确定性。**集合的成员运算在性能上要优于列表的成员运算**,这是集合的底层存储特性(哈希存储)决定的,此处我们暂时不做讨论,大家可以先记下这个结论。
### 创建集合
@@ -21,6 +21,7 @@ print(len(set1)) # 3
# 创建集合的构造器语法(后面会讲到什么是构造器)
set2 = set('hello')
print(set2) # {'h', 'l', 'o', 'e'}
+
# 将列表转换成集合(可以去掉列表中的重复元素)
set3 = set([1, 2, 3, 3, 2, 1])
print(set3) # {1, 2, 3}
@@ -34,7 +35,7 @@ for elem in set4:
print(elem)
```
-需要提醒大家,集合中的元素必须是`hashable`类型。所谓`hashable`类型指的是能够计算出哈希码的数据类型,你可以暂时将哈希码理解为和变量对应的唯一的ID值。通常不可变类型都是`hashable`类型,如整数、浮点、字符串、元组等,而可变类型都不是`hashable`类型,因为可变类型无法确定唯一的ID值,所以也就不能放到集合中。集合本身也是可变类型,所以集合不能够作为集合中的元素,这一点请大家一定要注意。
+需要提醒大家,集合中的元素必须是`hashable`类型。所谓`hashable`类型指的是能够计算出哈希码的数据类型,大家可以暂时将哈希码理解为和变量对应的唯一的ID值。通常不可变类型都是`hashable`类型,如整数、浮点、字符串、元组等,而可变类型都不是`hashable`类型,因为可变类型无法确定唯一的ID值,所以也就不能放到集合中。集合本身也是可变类型,所以集合不能够作为集合中的元素,这一点在使用集合的时候一定要注意。
### 集合的运算
@@ -90,9 +91,9 @@ print((set1 | set2) - (set1 & set2)) # {1, 3, 5, 7, 8, 10}
通过上面的代码可以看出,对两个集合求交集,`&`运算符和`intersection`方法的作用是完全相同的,使用运算符的方式更直观而且代码也比较简短。相信大家对交集、并集、差集、对称差这几个概念是比较清楚的,如果没什么印象了可以看看下面的图。
-![](res/set.png)
+
-集合的交集、并集、差集运算还可以跟赋值运算一起构成复合运算,如下所示。
+集合的交集、并集、差集运算还可以跟赋值运算一起构成复合赋值运算,如下所示。
```Python
set1 = {1, 3, 5, 7}
@@ -110,7 +111,7 @@ print(set1) # {3, 6}
#### 比较运算
-两个集合可以用`==`和`!=`进行相等性判断,如果两个集合中的元素完全相同,那么`==`比较的结果就是`True`,否则就是`False`。如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集,即对于∀a∈A,均有a∈B,则A⊆B。A是B的子集,反过来也可以称B是A的超集。如果A是B的子集且A不等于B,那么A就是B的真子集。Python为集合类型提供了判断子集和超集的运算符,其实就是我们非常熟悉的`<`和`>`运算符,代码如下所示。
+两个集合可以用`==`和`!=`进行相等性判断,如果两个集合中的元素完全相同,那么`==`比较的结果就是`True`,否则就是`False`。如果集合`A`的任意一个元素都是集合`B`的元素,那么集合`A`称为集合`B`的子集,即对于$ \forall{a} \in {A}$,均有$ {a} \in {B} $,则$ {A} \subseteq {B} $,`A`是`B`的子集,反过来也可以称`B`是`A`的超集。如果`A`是`B`的子集且`A`不等于`B`,那么`A`就是`B`的真子集。Python为集合类型提供了判断子集和超集的运算符,其实就是我们非常熟悉的`<`和`>`运算符,代码如下所示。
```Python
set1 = {1, 3, 5}
@@ -135,6 +136,7 @@ Python中的集合是可变类型,我们可以通过集合类型的方法为
```Python
# 创建一个空集合
set1 = set()
+
# 通过add方法添加元素
set1.add(33)
set1.add(55)
@@ -145,6 +147,7 @@ print(set1) # {33, 1, 100, 55, 1000, 10}
set1.discard(100)
set1.discard(99)
print(set1) # {1, 10, 33, 55, 1000}
+
# 通过remove方法删除指定元素,建议先做成员运算再删除
# 否则元素如果不在集合中就会引发KeyError异常
if 10 in set1:
@@ -156,6 +159,7 @@ print(set1.pop())
# clear方法可以清空整个集合
set1.clear()
+
print(set1) # set()
```
@@ -185,9 +189,3 @@ print(set1 < set2) # False
### 简单的总结
Python中的集合底层使用了**哈希存储**的方式,对于这一点我们暂时不做介绍,在后面的课程有需要的时候再为大家讲解集合的底层原理,现阶段大家只需要知道**集合是一种容器**,元素必须是`hashable`类型,与列表不同的地方在于集合中的元素**没有序**、**不能用索引运算**、**不能重复**。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
diff --git a/第015课:常用数据结构之字典.md b/第12课:常用数据结构之字典.md
similarity index 85%
rename from 第015课:常用数据结构之字典.md
rename to 第12课:常用数据结构之字典.md
index f59cfd7..86df711 100644
--- a/第015课:常用数据结构之字典.md
+++ b/第12课:常用数据结构之字典.md
@@ -1,6 +1,6 @@
-## 第015课:常用数据结构之字典
+## 第12课:常用数据结构之字典
-迄今为止,我们已经为大家介绍了Python中的三种容器型数据类型,但是这些数据类型还不足以帮助我们解决所有的问题。例如,我们要保存一个人的信息,包括姓名、年龄、体重、单位地址、家庭住址、本人手机号、紧急联系人手机号等信息,你会发现我们之前学过的列表、元组和集合都不是最理想的选择。
+迄今为止,我们已经为大家介绍了Python中的三种容器型数据类型,但是这些数据类型仍然不足以帮助我们解决所有的问题。例如,我们要保存一个人的信息,包括姓名、年龄、体重、单位地址、家庭住址、本人手机号、紧急联系人手机号等信息,你会发现我们之前学过的列表、元组和集合都不是最理想的选择。
```Python
person1 = ['王大锤', 55, 60, '科华北路62号', '中同仁路8号', '13122334455', '13800998877']
@@ -22,12 +22,14 @@ Python程序中的字典跟现实生活中的字典很像,它以键值对(
```Python
xinhua = {
- '麓': '山脚下', '路': '道,往来通行的地方;方面,地区:南~货,外~货;种类:他俩是一~人',
- '蕗': '甘草的别名', '潞': '潞水,水名,即今山西省的浊漳河;潞江,水名,即云南省的怒江'
+ '麓': '山脚下',
+ '路': '道,往来通行的地方;方面,地区:南~货,外~货;种类:他俩是一~人',
+ '蕗': '甘草的别名',
+ '潞': '潞水,水名,即今山西省的浊漳河;潞江,水名,即云南省的怒江'
}
print(xinhua)
person = {
- 'name': '王大锤', 'age': 55, 'weight': 60, 'office': '科华北路62号',
+ 'name': '王大锤', 'age': 55, 'weight': 60, 'office': '科华北路62号',
'home': '中同仁路8号', 'tel': '13122334455', 'econtact': '13800998877'
}
print(person)
@@ -75,7 +77,7 @@ if 'age' in person:
person['age'] = 25
# 通过索引操作向person字典中存入新的键值对
person['tel'] = '13122334455'
-person['signature'] = '你的男朋友是一个盖世垃圾,他会踏着五彩祥云去赢取你的闺蜜'
+person['signature'] = '你的男朋友是一个盖世垃圾,他会踏着五彩祥云去迎娶你的闺蜜'
print('name' in person, 'tel' in person) # True True
# 检查person字典中键值对的数量
print(len(person)) # 6
@@ -88,7 +90,7 @@ for key in person:
### 字典的方法
-字典类型的方法基本上都跟字典的键值对操作相关,可以通过下面的例子来了解这些方法的使用。例如,我们要用一个字典来保存学生的信息,我们可以使用学生的学号作为字典中的键,通过学号做索引运算就可以得到对应的学生;我们可以把字典中键对应的值也做成一个字典,这样就可以用多组键值对分别存储学生的姓名、性别、年龄、籍贯等信息,代码如下所示。
+字典类型的方法基本上都跟字典的键值对操作相关,可以通过下面的例子来了解这些方法的使用。例如,我们要用一个字典来保存学生的信息,我们可以使用学生的学号作为字典中的键,通过学号做索引运算就可以得到对应的学生;我们可以把字典的值也做成一个字典,这样就可以用多组键值对分别存储学生的姓名、性别、年龄、籍贯等信息,代码如下所示。
```Python
# 字典中的值又是一个字典(嵌套的字典)
@@ -126,10 +128,7 @@ print(stu2) # {}
key, value = students.popitem()
print(key, value) # 1003 {'name': '武则天', 'sex': False, 'age': 20, 'place': '四川广元'}
-# setdefault可以更新字典中的键对应的值或向字典中存入新的键值对
-# setdefault方法的第一个参数是键,第二个参数是键对应的值
-# 如果这个键在字典中存在,更新这个键之后会返回原来与这个键对应的值
-# 如果这个键在字典中不存在,方法将返回第二个参数的值,默认为None
+# setdefault可以向字典中存入新的键值对或返回指定的键对应的值
result = students.setdefault(1005, {'name': '方启鹤', 'sex': True})
print(result) # {'name': '方启鹤', 'sex': True}
print(students) # {1001: {...}, 1005: {...}}
@@ -188,10 +187,4 @@ print(stocks2)
### 简单的总结
-Python程序中的字典跟现实生活中字典非常像,允许我们**以键值对的形式保存数据**,再**通过键索引对应的值**。这是一种非常**有利于数据检索**的数据类型,底层原理我们在后续的课程中再研究。再次提醒大家注意,**字典中的键必须是不可变类型**,字典中的值可以是任意类型。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+Python程序中的字典跟现实生活中字典非常像,允许我们**以键值对的形式保存数据**,再**通过键索引对应的值**。这是一种非常**有利于数据检索**的数据类型,底层原理我们在后续的课程中为大家讲解。再次提醒大家注意,**字典中的键必须是不可变类型**,字典中的值可以是任意类型。
diff --git a/第008课:函数和模块.md b/第13课:函数和模块.md
similarity index 82%
rename from 第008课:函数和模块.md
rename to 第13课:函数和模块.md
index e9f48b0..3db76c7 100644
--- a/第008课:函数和模块.md
+++ b/第13课:函数和模块.md
@@ -1,14 +1,14 @@
-## 第008课:函数和模块
+## 第13课:函数和模块
在讲解本节课的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。
-
-![](res/equation.png)
-
-你可能已经想到了,这个问题其实等同于将8个苹果分成四组且每组至少一个苹果有多少种方案,因此该问题还可以进一步等价于在分隔8个苹果的7个空隙之间插入三个隔板将苹果分成四组有多少种方案,也就是从7个空隙选出3个空隙放入隔板的组合数,所以答案是`C(7,3)=35`。组合数的计算公式如下所示。
-
-![](res/combination.png)
-
-根据我们前面学习的知识,可以用循环做累乘的方式来计算阶乘,那么通过下面的Python代码我们就可以计算出组合数`C(M,N)`的值,代码如下所示。
+$$
+x_1 + x_2 + x_3 + x_4 = 8
+$$
+你可能已经想到了,这个问题其实等同于将`8`个苹果分成四组且每组至少一个苹果有多少种方案,因此该问题还可以进一步等价于在分隔`8`个苹果的`7`个空隙之间插入三个隔板将苹果分成四组有多少种方案,也就是从`7`个空隙选出`3`个空隙放入隔板的组合数,所以答案是$ C_7^3=35 $。组合数的计算公式如下所示。
+$$
+C_M^N = \frac {M!} {N!(M-N)!}
+$$
+根据我们前面学习的知识,可以用循环做累乘的方式来计算阶乘,那么通过下面的Python代码我们就可以计算出组合数$ C_M^N $的值,代码如下所示。
```Python
"""
@@ -28,11 +28,11 @@ fn = 1
for num in range(1, n + 1):
fn *= num
# 计算m-n的阶乘
-fm_n = 1
+fk = 1
for num in range(1, m - n + 1):
- fm_n *= num
+ fk *= num
# 计算C(M,N)的值
-print(fm // fn // fm_n)
+print(fm // fn // fk)
```
### 函数的作用
@@ -73,11 +73,13 @@ n = int(input('n = '))
print(fac(m) // fac(n) // fac(m - n))
```
+> **说明**:事实上,Python标准库的`math`模块中有一个名为`factorial`的函数已经实现了求阶乘的功能,我们可以直接使用该函数来计算阶乘。**将来我们使用的函数,要么是自定义的函数,要么是Python标准库或者三方库中提供的函数**。
+
### 函数的参数
#### 参数的默认值
-如果函数中没有`return`语句,那么函数默认返回代表空值的`None`。另外,在定义函数时,函数也可以没有自变量,但是函数名后面的圆括号是必须有的。Python中还允许函数的参数拥有默认值,我们可以把上一课“CRAPS赌博游戏”的摇色子获得点数的功能封装成函数,代码如下所示。
+如果函数中没有`return`语句,那么函数默认返回代表空值的`None`。另外,在定义函数时,函数也可以没有自变量,但是函数名后面的圆括号是必须有的。Python中还允许函数的参数拥有默认值,我们可以把之前讲过的一个例子“CRAPS赌博游戏”中摇色子获得点数的功能封装成函数,代码如下所示。
```Python
"""
@@ -128,7 +130,7 @@ print(add(c=50, a=100, b=200)) # 350
#### 可变参数
-接下来,我们还可以实现一个对任意多个数求和的`add`函数,因为Python语言中的函数可以通过星号表达式语法来支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数。将来我们以团队协作的方式开发商业项目时,很有可能要设计函数给其他人使用,但有的时候我们并不知道函数的调用者会向该函数传入多少个参数,这个时候可变参数就可以派上用场。下面的代码演示了用可变参数实现对任意多个数求和的`add`函数。
+接下来,我们还可以实现一个对任意多个数求和的`add`函数,因为Python语言中的函数可以通过星号表达式语法来支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入`0`个或任意多个参数。将来我们以团队协作的方式开发商业项目时,很有可能要设计函数给其他人使用,但有的时候我们并不知道函数的调用者会向该函数传入多少个参数,这个时候可变参数就可以派上用场。下面的代码演示了用可变参数实现对任意多个数求和的`add`函数。
```Python
"""
@@ -144,7 +146,8 @@ def add(*args):
total = 0
# 可变参数可以放在for循环中取出每个参数的值
for val in args:
- total += val
+ if type(val) in (int, float):
+ total += val
return total
@@ -261,11 +264,11 @@ Python标准库中还有一类函数是不需要`import`就能够直接使用的
| `chr` | 将Unicode编码转换成对应的字符,例如:`chr(8364)`会返回`'€'`。 |
| `hex` | 将一个整数转换成以`'0x'`开头的十六进制字符串,例如:`hex(123)`会返回`'0x7b'`。 |
| `input` | 从输入中读取一行,返回读到的字符串。 |
-| `len` | 获取字符串、列表等的长度。 |
-| `max` | 返回多个参数或一个可迭代对象(后面会讲)中的最大值,例如:`max(12, 95, 37)`会返回`95`。 |
-| `min` | 返回多个参数或一个可迭代对象(后面会讲)中的最小值,例如:`min(12, 95, 37)`会返回`12`。 |
+| `len` | 获取字符串、列表等的长度。 |
+| `max` | 返回多个参数或一个可迭代对象中的最大值,例如:`max(12, 95, 37)`会返回`95`。 |
+| `min` | 返回多个参数或一个可迭代对象中的最小值,例如:`min(12, 95, 37)`会返回`12`。 |
| `oct` | 把一个整数转换成以`'0o'`开头的八进制字符串,例如:`oct(123)`会返回`'0o173'`。 |
-| `open` | 打开一个文件并返回文件对象(后面会讲)。 |
+| `open` | 打开一个文件并返回文件对象。 |
| `ord` | 将字符转换成对应的Unicode编码,例如:`ord('€')`会返回`8364`。 |
| `pow` | 求幂运算,例如:`pow(2, 3)`会返回`8`;`pow(2, 0.5)`会返回`1.4142135623730951`。 |
| `print` | 打印输出。 |
@@ -276,10 +279,4 @@ Python标准库中还有一类函数是不需要`import`就能够直接使用的
### 简单的总结
-**函数是功能相对独立且会重复使用的代码的封装**。学会使用定义和使用函数,就能够写出更为优质的代码。当然,Python语言的标准库中已经为我们提供了大量的模块和常用的函数,用好这些模块和函数就能够用更少的代码做更多的事情。
-
-> **温馨提示**:学习中如果遇到困难,可以看看我们为大家录制的入门视频,视频链接:https://pan.baidu.com/s/1Tu8wy9IExP_Co6CurVr2Pg,密码:rbao,也可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+**函数是对功能相对独立且会重复使用的代码的封装**。学会使用定义和使用函数,就能够写出更为优质的代码。当然,Python语言的标准库中已经为我们提供了大量的模块和常用的函数,用好这些模块和函数就能够用更少的代码做更多的事情;如果这些模块和函数不能满足我们的要求,我们就需要自定义函数,然后用模块的概念来管理这些自定义函数。
\ No newline at end of file
diff --git a/第14课:函数的应用.md b/第14课:函数的应用.md
new file mode 100644
index 0000000..162ed47
--- /dev/null
+++ b/第14课:函数的应用.md
@@ -0,0 +1,162 @@
+## 第14课:函数的应用
+
+接下来我们通过一些案例来为大家讲解函数的应用。
+
+### 经典小案例
+
+#### 案例1:设计一个生成验证码的函数。
+
+> **说明**:验证码由数字和英文大小写字母构成,长度可以用参数指定。
+
+```Python
+import random
+import string
+
+ALL_CHARS = string.digits + string.ascii_letters
+
+
+def generate_code(code_len=4):
+ """生成指定长度的验证码
+
+ :param code_len: 验证码的长度(默认4个字符)
+ :return: 由大小写英文字母和数字构成的随机验证码字符串
+ """
+ return ''.join(random.choices(ALL_CHARS, k=code_len))
+```
+
+可以用下面的代码生成10组随机验证码来测试上面的函数。
+
+```Python
+for _ in range(10):
+ print(generate_code())
+```
+
+> **说明**:`random`模块的`sample`和`choices`函数都可以实现随机抽样,`sample`实现无放回抽样,这意味着抽样取出的字符是不重复的;`choices`实现有放回抽样,这意味着可能会重复选中某些字符。这两个函数的第一个参数代表抽样的总体,而参数`k`代表抽样的数量。
+
+#### 案例2:设计一个函数返回给定文件的后缀名。
+
+> **说明**:文件名通常是一个字符串,而文件的后缀名指的是文件名中最后一个`.`后面的部分,也称为文件的扩展名,它是某些操作系统用来标记文件类型的一种机制,例如在Windows系统上,后缀名`exe`表示这是一个可执行程序,而后缀名`txt`表示这是一个纯文本文件。需要注意的是,在Linux和macOS系统上,文件名可以以`.`开头,表示这是一个隐藏文件,像`.gitignore`这样的文件名,`.`后面并不是后缀名,这个文件没有后缀名或者说后缀名为`''`。
+
+```Python
+def get_suffix(filename, ignore_dot=True):
+ """获取文件名的后缀名
+
+ :param filename: 文件名
+ :param ignore_dot: 是否忽略后缀名前面的点
+ :return: 文件的后缀名
+ """
+ # 从字符串中逆向查找.出现的位置
+ pos = filename.rfind('.')
+ # 通过切片操作从文件名中取出后缀名
+ if pos <= 0:
+ return ''
+ return filename[pos + 1:] if ignore_dot else filename[pos:]
+```
+
+可以用下面的代码对上面的函数做一个简单的测验。
+
+```Python
+print(get_suffix('readme.txt')) # txt
+print(get_suffix('readme.txt.md')) # md
+print(get_suffix('.readme')) #
+print(get_suffix('readme.')) #
+print(get_suffix('readme')) #
+```
+
+上面的`get_suffix`函数还有一个更为便捷的实现方式,就是直接使用`os.path`模块的`splitext`函数,这个函数会将文件名拆分成带路径的文件名和扩展名两个部分,然后返回一个二元组,二元组中的第二个元素就是文件的后缀名(包含`.`),如果要去掉后缀名中的`.`,可以做一个字符串的切片操作,代码如下所示。
+
+```Python
+from os.path import splitext
+
+
+def get_suffix(filename, ignore_dot=True):
+ return splitext(filename)[1][1:]
+```
+
+> **思考**:如果要给上面的函数增加一个参数,用来控制文件的后缀名是否包含`.`,应该怎么做?
+
+#### 案例3:写一个判断给定的正整数是不是质数的函数。
+
+```Python
+def is_prime(num: int) -> bool:
+ """判断一个正整数是不是质数
+
+ :param num: 正整数
+ :return: 如果是质数返回True,否则返回False
+ """
+ for i in range(2, int(num ** 0.5) + 1):
+ if num % i == 0:
+ return False
+ return num != 1
+```
+
+#### 案例4:写出计算两个正整数最大公约数和最小公倍数的函数。
+
+代码一:
+
+```Python
+def gcd_and_lcm(x: int, y: int) -> int:
+ """求最大公约数和最小公倍数"""
+ a, b = x, y
+ while b % a != 0:
+ a, b = b % a, a
+ return a, x * y // a
+```
+
+代码二:
+
+```Python
+def gcd(x: int, y: int) -> int:
+ """求最大公约数"""
+ while y % x != 0:
+ x, y = y % x, x
+ return x
+
+
+def lcm(x: int, y: int) -> int:
+ """求最小公倍数"""
+ return x * y // gcd(x, y)
+```
+
+> **思考**:请比较上面的代码一和代码二,想想哪种做法是更好的选择。
+
+#### 案例5:写出计算一组样本数据描述性统计信息的函数。
+
+```Python
+import math
+
+
+def ptp(data):
+ """求极差(全距)"""
+ return max(data) - min(data)
+
+
+def average(data):
+ """求均值"""
+ return sum(data) / len(data)
+
+
+def variance(data):
+ """求方差"""
+ x_bar = average(data)
+ temp = [(num - x_bar) ** 2 for num in data]
+ return sum(temp) / (len(temp) - 1)
+
+
+def standard_deviation(data):
+ """求标准差"""
+ return math.sqrt(variance(data))
+
+
+def median(data):
+ """找中位数"""
+ temp, size = sorted(data), len(data)
+ if size % 2 != 0:
+ return temp[size // 2]
+ else:
+ return average(temp[size // 2 - 1:size // 2 + 1])
+```
+
+### 简单的总结
+
+在写代码尤其是开发商业项目的时候,一定要有意识的**将相对独立且重复出现的功能封装成函数**,这样不管是自己还是团队的其他成员都可以通过调用函数的方式来使用这些功能。
diff --git a/第020课:函数使用进阶.md b/第15课:函数使用进阶.md
similarity index 80%
rename from 第020课:函数使用进阶.md
rename to 第15课:函数使用进阶.md
index c158f90..273410a 100644
--- a/第020课:函数使用进阶.md
+++ b/第15课:函数使用进阶.md
@@ -1,43 +1,43 @@
-## 第020课:函数使用进阶
+## 第15课:函数使用进阶
-在之前的课程中,我们讲到过关于函数的知识,我们还讲到过Python中常用的数据类型,这些类型的变量都可以作为函数的参数或返回值;通过前几节课的学习,我们又知道了写在类中的函数通常称之为方法,它代表了类或者对象可以接收的消息。如果我们把这些知识汇总一下,我们的函数就可以做更多的事情。
+前面我们讲到了关于函数的知识,我们还讲到过Python中常用的数据类型,这些类型的变量都可以作为函数的参数或返回值,用好函数还可以让我们做更多的事情。
### 关键字参数
下面是一个判断传入的三条边长能否构成三角形的函数,在调用函数传入参数时,我们可以指定参数名,也可以不指定参数名,代码如下所示。
```Python
-def can_form_triangle(a, b, c):
+def is_triangle(a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
return a + b > c and b + c > a and a + c > b
# 调用函数传入参数不指定参数名按位置对号入座
-print(can_form_triangle(1, 2, 3))
+print(is_triangle(1, 2, 3))
# 调用函数通过“参数名=参数值”的形式按顺序传入参数
-print(can_form_triangle(a=1, b=2, c=3))
+print(is_triangle(a=1, b=2, c=3))
# 调用函数通过“参数名=参数值”的形式不按顺序传入参数
-print(can_form_triangle(c=3, a=1, b=2))
+print(is_triangle(c=3, a=1, b=2))
```
在没有特殊处理的情况下,函数的参数都是**位置参数**,也就意味着传入参数的时候对号入座即可,如上面代码的第7行所示,传入的参数值`1`、`2`、`3`会依次赋值给参数`a`、`b`、`c`。当然,也可以通过`参数名=参数值`的方式传入函数所需的参数,因为指定了参数名,传入参数的顺序可以进行调整,如上面代码的第9行和第11行所示。
-调用函数时,如果希望函数的调用者必须以`参数名=参数值`的方式传参,可以用**命名关键字参数**取代位置参数。所谓命名关键字参数,是在函数的参数列表中,写在`*`之后的参数,代码如下所示。
+调用函数时,如果希望函数的调用者必须以`参数名=参数值`的方式传参,可以用**命名关键字参数**(keyword-only argument)取代位置参数。所谓命名关键字参数,是在函数的参数列表中,写在`*`之后的参数,代码如下所示。
```Python
-def can_form_triangle(*, a, b, c):
+def is_triangle(*, a, b, c):
print(f'a = {a}, b = {b}, c = {c}')
return a + b > c and b + c > a and a + c > b
-# TypeError: can_form_triangle() takes 0 positional arguments but 3 were given
-# print(is_valid_for_triangle(3, 4, 5))
+# TypeError: is_triangle() takes 0 positional arguments but 3 were given
+# print(is_triangle(3, 4, 5))
# 传参时必须使用“参数名=参数值”的方式,位置不重要
-print(can_form_triangle(a=3, b=4, c=5))
-print(can_form_triangle(c=5, b=4, a=3))
+print(is_triangle(a=3, b=4, c=5))
+print(is_triangle(c=5, b=4, a=3))
```
-> **注意**:上面的`can_form_triangle`函数,参数列表中的`*`是一个分隔符,`*`前面的参数都是位置参数,而`*`后面的参数就是命名关键字参数。
+> **注意**:上面的`is_triangle`函数,参数列表中的`*`是一个分隔符,`*`前面的参数都是位置参数,而`*`后面的参数就是命名关键字参数。
我们之前讲过在函数的参数列表中可以使用**可变参数**`*args`来接收任意数量的参数,但是我们需要看看,`*args`是否能够接收带参数名的参数。
@@ -45,7 +45,8 @@ print(can_form_triangle(c=5, b=4, a=3))
def calc(*args):
result = 0
for arg in args:
- result += arg
+ if type(arg) in (int, float):
+ result += arg
return result
@@ -58,9 +59,11 @@ print(calc(a=1, b=2, c=3))
def calc(*args, **kwargs):
result = 0
for arg in args:
- result += arg
+ if type(arg) in (int, float):
+ result += arg
for value in kwargs.values():
- result += value
+ if type(value) in (int, float):
+ result += value
return total
@@ -82,9 +85,11 @@ print(calc(1, 2, c=3, d=4)) # 10
def calc(*args, init_value, op, **kwargs):
result = init_value
for arg in args:
- result = op(result, arg)
+ if type(arg) in (int, float):
+ result = op(result, arg)
for value in kwargs.values():
- result = op(result, value)
+ if type(value) in (int, float):
+ result = op(result, value)
return result
```
@@ -103,13 +108,13 @@ print(calc(1, 2, 3, init_value=0, op=add, x=4, y=5)) # 15
print(calc(1, 2, x=3, y=4, z=5, init_value=1, op=mul)) # 120
```
-通过对高阶函数的运用,`calc`函数不再和加法运算耦合,所以灵活性和通用性会变强,这是编程中一种常用的技巧,但是最初学者来说可能会稍微有点难以理解。需要注意的是,将函数作为参数和调用函数是有显著的区别的,**调用函数需要在函数名后面跟上圆括号,而把函数作为参数时只需要函数名即可**。上面的代码也可以不用定义`add`和`mul`函数,因为Python标准库中的`operator`模块提供了代表加法运算的`add`和代表乘法运算的`mul`函数,我们直接使用即可,代码如下所示。
+通过对高阶函数的运用,`calc`函数不再和加法运算耦合,所以灵活性和通用性会变强,这是一种解耦合的编程技巧,但是最初学者来说可能会稍微有点难以理解。需要注意的是,将函数作为参数和调用函数是有显著的区别的,**调用函数需要在函数名后面跟上圆括号,而把函数作为参数时只需要函数名即可**。上面的代码也可以不用定义`add`和`mul`函数,因为Python标准库中的`operator`模块提供了代表加法运算的`add`和代表乘法运算的`mul`函数,我们直接使用即可,代码如下所示。
```Python
import operator
-print(calc(init_value=0, op=operator.add, 1, 2, 3, x=4, y=5)) # 15
-print(calc(init_value=1, op=operator.mul, 1, 2, x=3, y=4, z=5)) # 120
+print(calc(1, 2, 3, init_value=0, op=operator.add, x=4, y=5)) # 15
+print(calc(1, 2, x=3, y=4, z=5, init_value=1, op=operator.mul)) # 120
```
Python内置函数中有不少高阶函数,我们前面提到过的`filter`和`map`函数就是高阶函数,前者可以实现对序列中元素的过滤,后者可以实现对序列中元素的映射,例如我们要去掉一个整数列表中的奇数,并对所有的偶数求平方得到一个新的列表,就可以直接使用这两个函数来做到,具体的做法是如下所示。
@@ -154,9 +159,11 @@ print(numbers2) # [144, 64, 3600, 2704]
def calc(*args, init_value=0, op=lambda x, y: x + y, **kwargs):
result = init_value
for arg in args:
- result = op(result, arg)
+ if type(arg) in (int, float):
+ result = op(result, arg)
for value in kwargs.values():
- result = op(result, value)
+ if type(value) in (int, float):
+ result = op(result, value)
return result
@@ -189,11 +196,4 @@ print(is_prime(9)) # False
### 简单的总结
-Python中的函数可以使用可变参数`*args`和关键字参数`**kwargs`来接收任意数量的参数,而且传入参数时可以带上参数名也可以没有参数名,可变参数会被处理成一个元组,而关键字参数会被处理成一个字典。Python中的函数也是对象,所以函数可以作为函数的参数和返回值,也就是说,在Python中我们可以使用高阶函数。如果我们要定义的函数非常简单,只有一行代码且不需要名字,可以将函数写成Lambda函数(匿名函数)的形式。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
+Python中的函数可以使用可变参数`*args`和关键字参数`**kwargs`来接收任意数量的参数,而且传入参数时可以带上参数名也可以没有参数名,可变参数会被处理成一个元组,而关键字参数会被处理成一个字典。**Python中的函数是一等函数,可以赋值给变量,也可以作为函数的参数和返回值**,这也就意味着我们可以在Python中使用高阶函数。如果我们要定义的函数非常简单,只有一行代码且不需要函数名,可以使用Lambda函数(匿名函数)。
diff --git a/第021课:函数的高级应用.md b/第16课:函数的高级应用.md
similarity index 77%
rename from 第021课:函数的高级应用.md
rename to 第16课:函数的高级应用.md
index c981e6e..6521533 100644
--- a/第021课:函数的高级应用.md
+++ b/第16课:函数的高级应用.md
@@ -1,4 +1,4 @@
-## 第021课:函数的高级应用
+## 第16课:函数的高级应用
在上一节课中,我们已经对函数进行了更为深入的研究,还探索了Python中的高阶函数和Lambda函数。在这些知识的基础上,这节课我们为大家分享两个和函数相关的内容,一个是装饰器,一个是函数的递归调用。
@@ -81,7 +81,7 @@ upload('Python从入门到住院.pdf')
上面的代码中已经没有重复代码了,虽然写装饰器会花费一些心思,但是这是一个一劳永逸的骚操作,如果还有其他的函数也需要记录执行时间,按照上面的代码如法炮制即可。
-在Python中,使用装饰器很有更为便捷的**语法糖**(编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强),可以用`@装饰器函数`将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同,下面是完整的代码。
+在Python中,使用装饰器很有更为便捷的**语法糖**(编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强,我们将其称之为“语法糖”或“糖衣语法”),可以用`@装饰器函数`将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同,下面是完整的代码。
```Python
import random
@@ -164,49 +164,11 @@ upload = upload.__wrapped__
upload('Python从新手到大师.pdf')
```
-**装饰器函数本身也可以参数化**,简单的说就是通过我们的装饰器也是可以通过调用者传入的参数来定制的,这个知识点我们在后面用上它的时候再为大家讲解。除了可以用函数来定义装饰器之外,通过定义类的方式也可以定义装饰器。如果一个类中有名为`__call__`的魔术方法,那么这个类的对象就可以像函数一样调用,这就意味着这个对象可以像装饰器一样工作,代码如下所示。
-
-```Python
-class RecordTime:
-
- def __call__(self, func):
-
- @wraps(func)
- def wrapper(*args, **kwargs):
- start = time.time()
- result = func(*args, **kwargs)
- end = time.time()
- print(f'{func.__name__}执行时间: {end - start:.3f}秒')
- return result
-
- return wrapper
-
-
-# 使用装饰器语法糖添加装饰器
-@RecordTime()
-def download(filename):
- print(f'开始下载{filename}.')
- time.sleep(random.randint(2, 6))
- print(f'{filename}下载完成.')
-
-
-def upload(filename):
- print(f'开始上传{filename}.')
- time.sleep(random.randint(4, 8))
- print(f'{filename}上传完成.')
-
-
-# 直接创建对象并调用对象传入被装饰的函数
-upload = RecordTime()(upload)
-download('MySQL从删库到跑路.avi')
-upload('Python从入门到住院.pdf')
-```
-
-上面的代码演示了两种添加装饰器的方式,由于`RecordTime`是一个类,所以需要先创建对象,才能把对象当成装饰器来使用,所以提醒大家注意`RecordTime`后面的圆括号,那是调用构造器创建对象的语法。如果为`RecordTime`类添加一个`__init__`方法,就可以实现对装饰器的参数化,刚才我们说过了,这个知识点等用上的时候再为大家讲解。使用装饰器还可以装饰一个类,为其提供额外的功能,这个知识点也等我们用到的时候再做讲解。
+**装饰器函数本身也可以参数化**,简单的说就是通过我们的装饰器也是可以通过调用者传入的参数来定制的,这个知识点我们在后面用到它的时候再为大家讲解。
### 递归调用
-Python中允许函数嵌套定义,也允许函数之间相互调用,而且一个函数还可以直接或间接的调用自身。函数自己调用自己称为递归调用,那么递归调用有什么用处呢?现实中,有很多问题的定义本身就是一个递归定义,例如我们之前讲到的阶乘,非负整数`N`的阶乘是`N`乘以`N-1`的阶乘,即`N! = N * (N-1)!`,定义的左边和右边都出现了阶乘的概念,所以这是一个递归定义。既然如此,我们可以使用递归调用的方式来写一个求阶乘的函数,代码如下所示。
+Python中允许函数嵌套定义,也允许函数之间相互调用,而且一个函数还可以直接或间接的调用自身。函数自己调用自己称为递归调用,那么递归调用有什么用处呢?现实中,有很多问题的定义本身就是一个递归定义,例如我们之前讲到的阶乘,非负整数`N`的阶乘是`N`乘以`N-1`的阶乘,即$ N! = N \times (N-1)! $,定义的左边和右边都出现了阶乘的概念,所以这是一个递归定义。既然如此,我们可以使用递归调用的方式来写一个求阶乘的函数,代码如下所示。
```Python
def fac(num):
@@ -262,10 +224,4 @@ def fib(n):
### 简单的总结
-装饰器是Python中的特色语法,可以通过装饰器来增强现有的类或函数,这是一种非常有用的编程技巧。一些复杂的问题用函数递归调用的方式写起来真的很简单,但是函数的递归调用一定要注意收敛条件和递归公式,找到递归公式才有机会使用递归调用,而收敛条件确定了递归什么时候停下来。函数调用通过内存中的栈空间来保存现场和恢复现场,栈空间通常都很小,所以递归如果不能迅速收敛,很可能会引发栈溢出错误,从而导致程序的崩溃。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+装饰器是Python中的特色语法,**可以通过装饰器来增强现有的函数**,这是一种非常有用的编程技巧。一些复杂的问题用函数递归调用的方式写起来真的很简单,但是**函数的递归调用一定要注意收敛条件和递归公式**,找到递归公式才有机会使用递归调用,而收敛条件确定了递归什么时候停下来。函数调用通过内存中的栈空间来保存现场和恢复现场,栈空间通常都很小,所以**递归如果不能迅速收敛,很可能会引发栈溢出错误,从而导致程序的崩溃**。
diff --git a/第016课:面向对象编程入门.md b/第17课:面向对象编程入门.md
similarity index 74%
rename from 第016课:面向对象编程入门.md
rename to 第17课:面向对象编程入门.md
index 7c0c401..29312ac 100644
--- a/第016课:面向对象编程入门.md
+++ b/第17课:面向对象编程入门.md
@@ -1,28 +1,28 @@
-## 第016课:面向对象编程入门
+## 第17课:面向对象编程入门
-面向对象编程是一种非常流行的**编程范式**(programming paradigm),所谓编程范式就是**程序设计的方法学**,也就是程序员对程序的认知和理解。
+面向对象编程是一种非常流行的**编程范式**(programming paradigm),所谓编程范式就是**程序设计的方法论**,简单的说就是程序员对程序的认知和理解以及他们编写代码的方式。
-前面的课程中我们说过“**程序是指令的集合**”,运行程序时,程序中的语句会变成一条或多条指令,然后由CPU(中央处理器)去执行。为了简化程序的设计,我们又讲到了函数,**把相对独立且经常重复使用的代码放置到函数中**,在需要使用这些代码的时候调用函数即可。如果一个函数的功能过于复杂和臃肿,我们又可以进一步**将函数进一步拆分为多个子函数**来降低系统的复杂性。
+在前面的课程中,我们说过“**程序是指令的集合**”,运行程序时,程序中的语句会变成一条或多条指令,然后由CPU(中央处理器)去执行。为了简化程序的设计,我们又讲到了函数,**把相对独立且经常重复使用的代码放置到函数中**,在需要使用这些代码的时候调用函数即可。如果一个函数的功能过于复杂和臃肿,我们又可以进一步**将函数进一步拆分为多个子函数**来降低系统的复杂性。
-不知大家是否发现,我们所谓的编程其实是写程序的人按照计算机的工作方式通过代码控制机器完成任务。但是,计算机的工作方式与人类正常的思维模式是不同的,如果编程就必须抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,而“每个人都应该学习编程”这样的豪言壮语也就只能喊喊口号而已。不是说我们不能按照计算机的工作方式去编写代码,但是当我们需要开发一个复杂的系统时,这种方式会让代码过于复杂,从而导致开发和维护工作都变得举步维艰,这也就是上世纪60年代末,出现了“软件危机”、“软件工程”这些概念的原因。
+不知大家是否发现,我们的编程工作其实是写程序的人按照计算机的工作方式通过代码控制机器完成任务。但是,计算机的工作方式与人类正常的思维模式是不同的,如果编程就必须抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,而“每个人都应该学习编程”的豪言壮语也就只能喊喊口号而已。这里,我想说的并不是我们不能按照计算机的工作方式去编写代码,但是当我们需要开发一个复杂的系统时,这种方式会让代码过于复杂,从而导致开发和维护工作都变得举步维艰。
-随着软件复杂性的增加,解决“软件危机”就成了软件开发者必须直面的问题。诞生于上世纪70年代的Smalltalk语言让软件开发者看到了希望,因为它引入了一种新的编程范式叫面向对象编程。在面向对象编程的世界里,程序中的**数据和操作数据的函数是一个逻辑上的整体**,我们称之为**对象**,**对象可以接收消息**,解决问题的方法就是**创建对象并向对象发出各种各样的消息**;通过消息传递,程序中的多个对象可以协同工作,这样就能构造出复杂的系统并解决现实中的问题。当然,面向对象编程的雏形还可以向前追溯到更早期的Simula语言,但这不是我们现在要讨论的重点。
+随着软件复杂性的增加,编写正确可靠的代码会变成了一项极为艰巨的任务,这也是很多人都坚信“软件开发是人类改造世界所有活动中最为复杂的活动”的原因。如何用程序描述复杂系统和解决复杂问题,就成为了所有程序员必须要思考和直面的问题。诞生于上世纪70年代的Smalltalk语言让软件开发者看到了希望,因为它引入了一种新的编程范式叫面向对象编程。在面向对象编程的世界里,程序中的**数据和操作数据的函数是一个逻辑上的整体**,我们称之为**对象**,**对象可以接收消息**,解决问题的方法就是**创建对象并向对象发出各种各样的消息**;通过消息传递,程序中的多个对象可以协同工作,这样就能构造出复杂的系统并解决现实中的问题。当然,面向对象编程的雏形还可以向前追溯到更早期的Simula语言,但这不是我们现在要讨论的重点。
-> **说明:** 今天我们使用的很多高级程序设计语言都支持面向对象编程,但是面向对象编程也不是解决软件开发中所有问题的“银弹”,或者说在软件开发这个行业目前还找不到这种所谓的“银弹”。
+> **说明:** 今天我们使用的很多高级程序设计语言都支持面向对象编程,但是面向对象编程也不是解决软件开发中所有问题的“银弹”,或者说在软件开发这个行业目前还找不到这种所谓的“银弹”。关于这个问题,大家可以参考IBM360系统之父弗雷德里克·布鲁克斯所发表的论文《没有银弹:软件工程的本质性与附属性工作》或软件工程的经典著作《人月神话》一书。
### 类和对象
-如果要用一句话来概括面向对象编程,我认为下面的说法是相当精准的。
+如果要用一句话来概括面向对象编程,我认为下面的说法是相当精辟和准确的。
> **面向对象编程**:把一组数据和处理数据的方法组成**对象**,把行为相同的对象归纳为**类**,通过**封装**隐藏对象的内部细节,通过**继承**实现类的特化和泛化,通过**多态**实现基于对象类型的动态分派。
-这句话对初学者来说可能难以理解,但是我们先为大家圈出几个关键词:**对象**(object)、**类**(class)、**封装**(encapsulation)、**继承**(inheritance)、**多态**(polymorphism)。
+这句话对初学者来说可能不那么容易理解,但是我可以先为大家圈出几个关键词:**对象**(object)、**类**(class)、**封装**(encapsulation)、**继承**(inheritance)、**多态**(polymorphism)。
-我们先说说类和对象这两个词。在面向对象编程中,**类是一个抽象的概念,对象是一个具体的概念**。我们把同一类对象的共同特征抽取出来就是一个类,比如我们经常说的人类,这是一个抽象概念,而我们每个人就是人类的这个抽象概念下的具体的实实在在的存在,也就是一个对象。简而言之,**类是对象的蓝图和模板,对象是类的实例**。
+我们先说说类和对象这两个词。在面向对象编程中,**类是一个抽象的概念,对象是一个具体的概念**。我们把同一类对象的共同特征抽取出来就是一个类,比如我们经常说的人类,这是一个抽象概念,而我们每个人就是人类的这个抽象概念下的实实在在的存在,也就是一个对象。简而言之,**类是对象的蓝图和模板,对象是类的实例,是可以接受消息的实体**。
在面向对象编程的世界中,**一切皆为对象**,**对象都有属性和行为**,**每个对象都是独一无二的**,而且**对象一定属于某个类**。对象的属性是对象的静态特征,对象的行为是对象的动态特征。按照上面的说法,如果我们把拥有共同特征的对象的属性和行为都抽取出来,就可以定义出一个类。
-![](./res/object-feature.png)
+
### 定义类
@@ -126,14 +126,14 @@ class Student:
stu1 = Student('骆昊', 40)
print(stu1) # 骆昊: 40
-students = [stu1, Student('王小锤', 16), Student('王大锤', 25)]
-print(students) # [骆昊: 40, 王小锤: 16, 王大锤: 25]
+students = [stu1, Student('李元芳', 36), Student('王大锤', 25)]
+print(students) # [骆昊: 40, 李元芳: 36, 王大锤: 25]
```
### 面向对象的支柱
-面向对象编程有三大支柱,就是我们之前给大家划重点的时候圈出的三个词:封装、继承和多态。后面两个概念在下一节课中会详细说明,这里我们先说一下什么是封装。我自己对封装的理解是:**隐藏一切可以隐藏的实现细节,只向外界暴露简单的调用接口**。我们在类中定义的对象方法其实就是一种封装,这种封装可以让我们在创建对象之后,只需要给对象发送一个消息就可以执行方法中的代码,也就是说我们在只知道方法的名字和参数(方法的外部视图),不知道方法内部实现细节(方法的内部视图)的情况下就完成了对方法的使用。
+面向对象编程有三大支柱,就是我们之前给大家划重点的时候圈出的三个词:**封装**、**继承**和**多态**。后面两个概念在下一节课中会详细说明,这里我们先说一下什么是封装。我自己对封装的理解是:**隐藏一切可以隐藏的实现细节,只向外界暴露简单的调用接口**。我们在类中定义的对象方法其实就是一种封装,这种封装可以让我们在创建对象之后,只需要给对象发送一个消息就可以执行方法中的代码,也就是说我们在只知道方法的名字和参数(方法的外部视图),不知道方法内部实现细节(方法的内部视图)的情况下就完成了对方法的使用。
举一个例子,假如要控制一个机器人帮我倒杯水,如果不使用面向对象编程,不做任何的封装,那么就需要向这个机器人发出一系列的指令,如站起来、向左转、向前走5步、拿起面前的水杯、向后转、向前走10步、弯腰、放下水杯、按下出水按钮、等待10秒、松开出水按钮、拿起水杯、向右转、向前走5步、放下水杯等,才能完成这个简单的操作,想想都觉得麻烦。按照面向对象编程的思想,我们可以将倒水的操作封装到机器人的一个方法中,当需要机器人帮我们倒水的时候,只需要向机器人对象发出倒水的消息就可以了,这样做不是更好吗?
@@ -141,7 +141,7 @@ print(students) # [骆昊: 40, 王小锤: 16, 王大锤: 25]
### 经典案例
-#### 例子1:定义一个类描述数字时钟。
+#### 案例1:定义一个类描述数字时钟。
```Python
import time
@@ -189,7 +189,7 @@ while True:
clock.run()
```
-#### 例子2:定义一个类描述平面上的点,要求提供计算到另一个点距离的方法。
+#### 案例2:定义一个类描述平面上的点,要求提供计算到另一个点距离的方法。
```Python
class Point(object):
@@ -224,13 +224,7 @@ print(p1.distance_to(p2))
面向对象编程是一种非常流行的编程范式,除此之外还有**指令式编程**、**函数式编程**等编程范式。由于现实世界是由对象构成的,而对象是可以接收消息的实体,所以**面向对象编程更符合人类正常的思维习惯**。类是抽象的,对象是具体的,有了类就能创建对象,有了对象就可以接收消息,这就是面向对象编程的基础。定义类的过程是一个抽象的过程,找到对象公共的属性属于数据抽象,找到对象公共的方法属于行为抽象。抽象的过程是一个仁者见仁智者见智的过程,对同一类对象进行抽象可能会得到不同的结果,如下图所示。
-![](res/abstraction.png)
+
> **说明:** 本节课的插图来自于 Grady Booc 等撰写的《面向对象分析与设计》一书,该书是讲解面向对象编程的经典著作,有兴趣的读者可以购买和阅读这本书来了解更多的面向对象的相关知识。
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
diff --git a/第017课:面向对象编程进阶.md b/第18课:面向对象编程进阶.md
similarity index 91%
rename from 第017课:面向对象编程进阶.md
rename to 第18课:面向对象编程进阶.md
index c030da3..02da305 100644
--- a/第017课:面向对象编程进阶.md
+++ b/第18课:面向对象编程进阶.md
@@ -1,4 +1,4 @@
-## 第017课:面向对象编程进阶
+## 第18课:面向对象编程进阶
上一节课我们讲解了Python面向对象编程的基础知识,这一节课我们继续来讨论面向对象编程相关的内容。
@@ -24,7 +24,7 @@ print(stu.__name)
上面代码的最后一行会引发`AttributeError`(属性错误)异常,异常消息为:`'Student' object has no attribute '__name'`。由此可见,以`__`开头的属性`__name`是私有的,在类的外面无法直接访问,但是类里面的`study`方法中可以通过`self.__name`访问该属性。
-需要提醒大家的是,Python并没有从语法上严格保证私有属性的私密性,它只是给私有的属性和方法换了一个名字来阻挠对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,我们可以对上面的代码稍作修改就可以访问到私有的。
+需要提醒大家的是,Python并没有从语法上严格保证私有属性的私密性,它只是给私有的属性和方法换了一个名字来阻挠对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,我们可以对上面的代码稍作修改就可以访问到私有的属性。
```Python
class Student:
@@ -42,9 +42,9 @@ stu.study('Python程序设计')
print(stu._Student__name, stu._Student__age)
```
-Python中做出这样的设定是基于一句名言:“**We are all consenting adults here**”(大家都是成年人)。Python语言的设计者认为程序员要为自己的行为负责,而不是由Python语言本身来严格限制访问可见性,而大多数的程序员都认为**开放比封闭要好**,把对象的属性私有化并不是必须的东西。
+Python中有一句名言:“**We are all consenting adults here**”(大家都是成年人)。Python语言的设计者认为程序员要为自己的行为负责,而不是由Python语言本身来严格限制访问可见性,而大多数的程序员都认为**开放比封闭要好**,把对象的属性私有化并不是编程语言必须的东西,所以Python并没有从语法上做出最严格的限定。
-Python中可以通过`property`装饰器为“私有”属性提供读取和修改的方法,装饰器通常会放在类、函数或方法的声明之前,通过一个`@`符号表示将装饰器应用于类、函数或方法。装饰器的概念我们会在稍后的课程中以专题的形式为大家讲解,这里我们只需要了解`property`装饰器的用法就可以了。
+Python中可以通过`property`装饰器为“私有”属性提供读取和修改的方法,之前我们提到过,装饰器通常会放在类、函数或方法的声明之前,通过一个`@`符号表示将装饰器应用于类、函数或方法。
```Python
class Student:
@@ -212,9 +212,3 @@ stu1.study('Python程序设计')
### 简单的总结
Python是动态语言,Python中的对象可以动态的添加属性。在面向对象的世界中,**一切皆为对象**,我们定义的类也是对象,所以**类也可以接收消息**,对应的方法是类方法或静态方法。通过继承,我们**可以从已有的类创建新类**,实现对已有类代码的复用。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第018课:面向对象编程应用.md b/第19课:面向对象编程应用.md
similarity index 96%
rename from 第018课:面向对象编程应用.md
rename to 第19课:面向对象编程应用.md
index 573a3e7..e99062b 100644
--- a/第018课:面向对象编程应用.md
+++ b/第19课:面向对象编程应用.md
@@ -1,4 +1,4 @@
-## 第018课:面向对象编程应用
+## 第18课:面向对象编程应用
面向对象编程对初学者来说不难理解但很难应用,虽然我们为大家总结过面向对象的三步走方法(定义类、创建对象、给对象发消息),但是说起来容易做起来难。**大量的编程练习**和**阅读优质的代码**可能是这个阶段最能够帮助到大家的两件事情。接下来我们还是通过经典的案例来剖析面向对象编程的知识,同时也通过这些案例为大家讲解如何运用之前学过的Python知识。
@@ -231,11 +231,4 @@ for emp in emps:
### 简单的总结
-面向对象的编程思想非常的好,也符合人类的正常思维习惯,但是要想灵活运用面向对象编程中的抽象、封装、继承、多态需要长时间的积累和沉淀,这件事情并非一夕之功,也无法一蹴而就。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
+面向对象的编程思想非常的好,也符合人类的正常思维习惯,但是要想灵活运用面向对象编程中的抽象、封装、继承、多态需要长时间的积累和沉淀,这件事情无法一蹴而就,属于“路漫漫其修远兮,吾将上下而求索”的东西。
diff --git a/第022课:Python标准库初探.md b/第20课:Python标准库初探.md
similarity index 97%
rename from 第022课:Python标准库初探.md
rename to 第20课:Python标准库初探.md
index 5398781..f7f3f66 100644
--- a/第022课:Python标准库初探.md
+++ b/第20课:Python标准库初探.md
@@ -1,4 +1,4 @@
-## 第022课:Python标准库初探
+## 第20课:Python标准库初探
Python语言最可爱的地方在于它的标准库和三方库实在是太丰富了,日常开发工作中的很多任务都可以通过这些标准库或者三方库直接解决。下面我们先介绍Python标准库中的一些常用模块,后面的课程中再陆陆续续为大家介绍Python常用三方库的用途和用法。
@@ -199,10 +199,3 @@ print(next(it))
### 简单的总结
Python标准库中有大量的模块,日常开发中有很多常见的任务在Python标准库中都有封装好的函数或类可供使用,这也是Python这门语言最可爱的地方。
-
->**温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
->付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
->免费群:**151669801**,仅供入门新手提问,定期清理群成员。
-
diff --git a/第023课:文件读写和异常处理.md b/第21课:文件读写和异常处理.md
similarity index 94%
rename from 第023课:文件读写和异常处理.md
rename to 第21课:文件读写和异常处理.md
index 0be06c8..4f57164 100644
--- a/第023课:文件读写和异常处理.md
+++ b/第21课:文件读写和异常处理.md
@@ -1,4 +1,4 @@
-## 第023课:文件读写和异常处理
+## 第21课:文件读写和异常处理
实际开发中常常会遇到对数据进行持久化的场景,所谓持久化是指将数据从无法长久保存数据的存储介质(通常是内存)转移到可以长久保存数据的存储介质(通常是硬盘)中。实现数据持久化最直接简单的方式就是通过**文件系统**将数据保存到**文件**中。
@@ -20,15 +20,9 @@
下图展示了如何根据程序的需要来设置`open`函数的操作模式。
-![](./res/file-open-mode.png)
+
-在使用`open`函数时,如果打开的文件是字符文件(文本文件),可以通过`encoding`参数来指定读写文件使用的字符编码。如果对字符编码和字符集这些概念不了解,可以看看[《字符集和字符编码》](https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html)一文,此处不再进行赘述。如果没有指定该参数,则使用系统默认的编码作为读写文件的编码。当前系统默认的编码可以通过下面的代码获得。
-
-```Python
-import sys
-
-print(sys.getdefaultencoding())
-```
+在使用`open`函数时,如果打开的文件是字符文件(文本文件),可以通过`encoding`参数来指定读写文件使用的字符编码。如果对字符编码和字符集这些概念不了解,可以看看[《字符集和字符编码》](https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html)一文,此处不再进行赘述。
使用`open`函数打开文件成功后会返回一个文件对象,通过这个对象,我们就可以实现对文件的读写操作;如果打开文件失败,`open`函数会引发异常,稍后会对此加以说明。如果要关闭打开的文件,可以使用文件对象的`close`方法,这样可以在结束文件操作时释放掉这个文件。
@@ -188,8 +182,8 @@ class InputError(ValueError):
def fac(num):
"""求阶乘"""
- if type(num) != int or num < 0:
- raise InputError('只能计算非负整数的阶乘!!!')
+ if num < 0:
+ raise InputError('只能计算非负整数的阶乘')
if num in (0, 1):
return 1
return num * fac(num - 1)
@@ -260,10 +254,4 @@ print('程序执行结束.')
### 简单的总结
-通过读写文件的操作,我们可以实现数据持久化。在Python中可以通过`open`函数来获得文件对象,可以通过文件对象的`read`和`write`方法实现文件读写操作。程序在运行时可能遭遇无法预料的异常状况,可以使用Python的异常机制来处理这些状况。Python的异常机制主要包括`try`、`except`、`else`、`finally`和`raise`这五个核心关键字。`try`后面的`except`语句不是必须的,`finally`语句也不是必须的,但是二者必须要有一个;`except`语句可以有一个或多个,多个`except`会按照书写的顺序依次匹配指定的异常,如果异常已经处理就不会再进入后续的`except`语句;`except`语句中还可以通过元组同时指定多个异常类型进行捕获;`except`语句后面如果不指定异常类型,则默认捕获所有异常;捕获异常后可以使用`raise`要再次抛出,但是不建议捕获并抛出同一个异常;不建议在不清楚逻辑的情况下捕获所有异常,这可能会掩盖程序中严重的问题。最后强调一点,不要使用异常机制来处理正常业务逻辑或控制程序流程,简单的说就是不要滥用异常机制。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+通过读写文件的操作,我们可以实现数据持久化。在Python中可以通过`open`函数来获得文件对象,可以通过文件对象的`read`和`write`方法实现文件读写操作。程序在运行时可能遭遇无法预料的异常状况,可以使用Python的异常机制来处理这些状况。Python的异常机制主要包括`try`、`except`、`else`、`finally`和`raise`这五个核心关键字。`try`后面的`except`语句不是必须的,`finally`语句也不是必须的,但是二者必须要有一个;`except`语句可以有一个或多个,多个`except`会按照书写的顺序依次匹配指定的异常,如果异常已经处理就不会再进入后续的`except`语句;`except`语句中还可以通过元组同时指定多个异常类型进行捕获;`except`语句后面如果不指定异常类型,则默认捕获所有异常;捕获异常后可以使用`raise`要再次抛出,但是不建议捕获并抛出同一个异常;不建议在不清楚逻辑的情况下捕获所有异常,这可能会掩盖程序中严重的问题。最后强调一点,**不要使用异常机制来处理正常业务逻辑或控制程序流程**,简单的说就是不要滥用异常机制,这是初学者常犯的错误。
diff --git a/第024课:对象的序列化和反序列化.md b/第22课:对象的序列化和反序列化.md
similarity index 89%
rename from 第024课:对象的序列化和反序列化.md
rename to 第22课:对象的序列化和反序列化.md
index f83937b..5ae2901 100644
--- a/第024课:对象的序列化和反序列化.md
+++ b/第22课:对象的序列化和反序列化.md
@@ -1,10 +1,10 @@
-## 第024课:对象的序列化和反序列化
+## 第22课:对象的序列化和反序列化
###读写JSON格式的数据
-通过上面的讲解,我们已经知道如何将文本数据和二进制数据保存到文件中,那么这里还有一个问题,如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?在Python中,我们可以将程序中的数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨语言跨平台的数据交换。使用JSON的原因非常简单,因为它结构紧凑而且是纯文本,任何操作系统和编程语言都能处理纯文本,这就是实现跨语言跨平台数据交换的前提条件。目前JSON基本上已经取代了XML(可扩展标记语言)作为异构系统间交换数据的事实标准。可以在[JSON的官方网站](https://www.json.org/json-zh.html)找到更多关于JSON的知识,这个网站还提供了每种语言处理JSON数据格式可以使用的工具或三方库。
+通过上面的讲解,我们已经知道如何将文本数据和二进制数据保存到文件中,那么这里还有一个问题,如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?在Python中,我们可以将程序中的数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨语言跨平台的数据交换。使用JSON的原因非常简单,因为它结构紧凑而且是纯文本,任何操作系统和编程语言都能处理纯文本,这就是**实现跨语言跨平台数据交换**的前提条件。目前JSON基本上已经取代了XML(可扩展标记语言)作为**异构系统间交换数据的事实标准**。可以在[JSON的官方网站](https://www.json.org/json-zh.html)找到更多关于JSON的知识,这个网站还提供了每种语言处理JSON数据格式可以使用的工具或三方库。
-下面是JSON格式的一个简单例子,大家可能已经注意到了,它跟Python中的字典非常类似而且支持嵌套结构,就像Python字典中的值还可以是字典,如果我们把下面的代码输入到浏览器控制台中,它会创建出一个JavaScript中的对象。
+下面是JSON格式的一个简单例子,大家可能已经注意到了,它跟Python中的字典非常类似而且支持嵌套结构,就像Python字典中的值可以是字典。如果我们把下面的代码输入到浏览器控制台中,它会创建出一个JavaScript中的对象。
```JSON
{
@@ -19,7 +19,7 @@
}
```
-![json-in-console](res/json-in-console.png)
+![](https://gitee.com/jackfrued/mypic/raw/master/20210803201758.png)
JSON格式的数据类型和Python中的数据类型也是很容易找到对应关系的,正如下面的两张表所示。
@@ -196,11 +196,5 @@ if resp.status_code == 200:
### 简单的总结
-Python中实现序列化和反序列化除了使用`json`模块之外,还可以使用`pickle`和`shelve`模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别,关于这两个模块的相关知识可以自己看看网络上的资料。处理JSON格式的数据很显然是程序员必须掌握的一项技能,因为不管是访问网络API接口还是提供网络API接口给他人使用,都需要具备处理JSON格式数据的相关知识。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
+Python中实现序列化和反序列化除了使用`json`模块之外,还可以使用`pickle`和`shelve`模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别,关于这两个模块的相关知识,有兴趣的读者可以自己查找网络上的资料。处理JSON格式的数据很显然是程序员必须掌握的一项技能,因为不管是访问网络API接口还是提供网络API接口给他人使用,都需要具备处理JSON格式数据的相关知识。
diff --git a/第027课:用Python读写CSV文件.md b/第23课:用Python读写CSV文件.md
similarity index 93%
rename from 第027课:用Python读写CSV文件.md
rename to 第23课:用Python读写CSV文件.md
index dd19ef1..35f6e16 100644
--- a/第027课:用Python读写CSV文件.md
+++ b/第23课:用Python读写CSV文件.md
@@ -1,4 +1,4 @@
-## 第027课:用Python读写CSV文件
+## 第23课:用Python读写CSV文件
### CSV文件介绍
@@ -59,9 +59,3 @@ with open('scores.csv', 'r') as file:
### 简单的总结
将来如果大家使用Python做数据分析,很有可能会用到名为`pandas`的三方库,它是Python数据分析的神器之一。`pandas`中封装了名为`read_csv`和`to_csv`的函数用来读写CSV文件,其中`read_CSV`会将读取到的数据变成一个`DataFrame`对象,而这个对象就是`pandas`库中最重要的类,它封装了一系列的方法用于对数据进行处理(清洗、转换、聚合等);而`to_csv`会将`DataFrame`对象中的数据写入CSV文件,完成数据的持久化。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
diff --git a/第028课:用Python读写Excel文件.md b/第24课:用Python读写Excel文件.md
similarity index 94%
rename from 第028课:用Python读写Excel文件.md
rename to 第24课:用Python读写Excel文件.md
index e086a79..4f862cc 100644
--- a/第028课:用Python读写Excel文件.md
+++ b/第24课:用Python读写Excel文件.md
@@ -1,4 +1,4 @@
-## 第028课:用Python读写Excel文件
+## 第24课:用Python读写Excel文件
### Excel简介
@@ -191,10 +191,4 @@ wb_for_write.save('阿里巴巴2017年股票数据-2.xlsx')
### 简单的总结
-其他操作Excel文件的三方库(如`openpyxl`)大家有兴趣可以自行了解。掌握了Python程序操作Excel的方法,可以解决日常办公中很多繁琐的处理Excel电子表格工作,最常见就是将多个数据格式相同的Excel文件合并到一个文件以及从多个Excel文件或表单中提取指定的数据。当然,如果要对表格数据进行处理,使用Python数据分析神器之一的`pandas`库可能更为方便,因为`pandas`库封装的函数以及`DataFrame`类可以完成大多数数据处理的任务。本章例子中使用的Excel文件,大家可以从我的百度云盘链接中进行下载,地址:,提取码:e7b4。
-
-> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
->
-> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
->
-> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。
\ No newline at end of file
+其他操作Excel文件的三方库(如`openpyxl`)大家有兴趣可以自行了解。掌握了Python程序操作Excel的方法,可以解决日常办公中很多繁琐的处理Excel电子表格工作,最常见就是将多个数据格式相同的Excel文件合并到一个文件以及从多个Excel文件或表单中提取指定的数据。当然,如果要对表格数据进行处理,使用Python数据分析神器之一的`pandas`库可能更为方便,因为`pandas`库封装的函数以及`DataFrame`类可以完成大多数数据处理的任务。
diff --git a/第030课:用Python获取网络资源.md b/第30课:用Python获取网络资源.md
similarity index 100%
rename from 第030课:用Python获取网络资源.md
rename to 第30课:用Python获取网络资源.md
diff --git a/第031课:用Python解析HTML页面.md b/第31课:用Python解析HTML页面.md
similarity index 100%
rename from 第031课:用Python解析HTML页面.md
rename to 第31课:用Python解析HTML页面.md
diff --git a/第032课:Python中的并发编程(上).md b/第32课:Python中的并发编程(1).md
similarity index 73%
rename from 第032课:Python中的并发编程(上).md
rename to 第32课:Python中的并发编程(1).md
index b9c467d..dbab614 100644
--- a/第032课:Python中的并发编程(上).md
+++ b/第32课:Python中的并发编程(1).md
@@ -1,4 +1,4 @@
-## 第032课:Python中的并发编程(上)
+## 第032课:Python中的并发编程(1)
现如今,我们使用的计算机早已是多CPU或多核的计算机,为此我们使用的操作系统基本都是支持“多任务”的操作系统,这样的操作系统使得我们我们可以同时运行多个程序,也可以将一个程序分解为若干个相对独立的子任务,让多个子任务“齐头并进”的执行,从而缩短程序的执行时间,同时也让用户获得更好的体验。因此,当下不管用什么编程语言进行开发,实现让一个程序同时执行多个任务已经成为程序员的标配技能。为此,我们需要先了解两个重要的概念:多进程和多线程。
@@ -10,7 +10,7 @@
![](res/macos-monitor.png)
-这里,我们还需要跟大家说说另外两个概念:**并发**(concurrency)和**并行**(parallel)。并发是指同一时刻只能有一条指令执行,但是多个线程对应的指令被快速轮换地执行。比如一个处理器,它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。由于处理器执行指令的速度和切换的速度非常非常快,人完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作,这就使得宏观上看起来多个线程在同时运行,但微观上其实只有一个线程在执行。并行是指同一时刻,有多条指令在多个处理器上同时执行,并行必须要依赖于多个处理器。不论是从宏观上还是微观上,多个线程都是在同一时刻一起执行的。在我们的课程中,其实并不用严格区分并发和并行两个词,所以我们把Python中的多线程、多进程以及异步I/O都视为实现并发编程的手段,但实际上前面两者也可以实现并行编程。
+这里,我们还需要跟大家说说另外两个概念:**并发**(concurrency)和**并行**(parallel)。并发是指同一时刻只能有一条指令执行,但是多个线程对应的指令被快速轮换地执行。比如一个处理器,它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。由于处理器执行指令的速度和切换的速度非常非常快,人完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作,这就使得宏观上看起来多个线程在同时运行,但微观上其实只有一个线程在执行。并行是指同一时刻,有多条指令在多个处理器上同时执行,并行必须要依赖于多个处理器,不论是从宏观上还是微观上,多个线程都是在同一时刻一起执行的。在我们的课程中,其实并不用严格区分并发和并行两个词,所以我们把Python中的多线程、多进程以及异步I/O都视为实现并发编程的手段,但实际上前面两者也可以实现并行编程,当然这里还有一个全局解释器锁(GIL)的问题,我们稍后讨论。
### 多线程编程
@@ -19,17 +19,52 @@ Python标准库中`threading`模块的`Thread`类可以帮助我们非常轻松
不使用多线程的下载。
```Python
+import random
+import time
+
+def download(*, filename):
+ start = time.time()
+ print(f'开始下载{filename}.')
+ time.sleep(random.randint(3, 6))
+ print(f'{filename}下载完成.')
+ end = time.time()
+ print(f'下载耗时: {end - start:.3f}秒.')
+
+
+start = time.time()
+download(filename='Python从入门到住院.pdf')
+download(filename='MySQL从删库到跑路.avi')
+download(filename='Linux从精通到放弃.mp3')
+end = time.time()
+print(f'总耗时: {end - start:.3f}秒.')
```
> **说明**:上面的代码并没有真正实现联网下载的功能,而是通过`time.sleep()`休眠指定的时间模拟下载文件需要花费一段时间。
-使用多线程的下载。
+运行上面的代码,可以得到如下所示的运行结果。可以看出,当我们的程序只有一个工作线程时,每个下载任务都需要等待上一个下载任务执行结束才能开始,所以程序执行的总耗时是三个下载任务各自执行时间的总和。
+
+```
+开始下载Python从入门到住院.pdf.
+Python从入门到住院.pdf下载完成.
+下载耗时: 3.005秒.
+开始下载MySQL从删库到跑路.avi.
+MySQL从删库到跑路.avi下载完成.
+下载耗时: 5.006秒.
+开始下载Linux从精通到放弃.mp3.
+Linux从精通到放弃.mp3下载完成.
+下载耗时: 6.007秒.
+总耗时: 14.018秒.
+```
+
+事实上,上面的三个下载任务之间并没有逻辑上的因果关系,三者是可以并发的,没有必要等待上一个下载任务结束,为此,我们可以使用多线程编程来改写上面的代码。
```Python
```
+再次运行程序,我们可以发现,整个程序的执行时间几乎等于耗时最长的一个下载任务的执行时间,这也就意味着,三个下载任务是并发执行的,没有一个等一个,这样做很显然提高了程序的执行效率。
+
通过上面的代码可以看出,直接使用`Thread`类的构造器就可以创建线程对象,而线程对象的`start()`方法可以启动一个线程。线程启动后会执行`target`参数指定的函数,当然前提是获得CPU的调度;如果`target`指定的线程要执行的目标函数有参数,需要通过`args`参数为其进行指定。
除了上面的代码展示的创建线程的方式外,还可以通过继承`Thread`类并重写`run()`方法的方式来自定义线程,具体的代码如下所示。
@@ -40,22 +75,6 @@ Python标准库中`threading`模块的`Thread`类可以帮助我们非常轻松
通过上面的例子可以看出,如果程序中有非常耗时的执行单元,而这些耗时的执行单元之间又没有逻辑上的因果关系,即B单元的执行不依赖于A单元的执行结果,那么A和B两个单元就可以放到两个不同的线程中,让他们并发的执行。这样做的好处除了减少程序执行的等待时间,还可以带来更好的用户体验,因为一个单元的阻塞不会造成程序的“假死”,因为程序中还有其他的单元是可以运转的。
-#### 守护线程
-
-
-
-#### 资源竞争
-
-
-
-#### 线程池
-
-
-
-#### GIL问题
-
-
-
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
diff --git a/第33课:Python中的并发编程(2).md b/第33课:Python中的并发编程(2).md
new file mode 100644
index 0000000..34a0ed0
--- /dev/null
+++ b/第33课:Python中的并发编程(2).md
@@ -0,0 +1,17 @@
+## 第033课:Python中的并发编程(2)
+
+
+
+#### 守护线程
+
+
+
+#### 资源竞争
+
+
+
+#### 线程池
+
+
+
+#### GIL问题
\ No newline at end of file
diff --git a/第033课:Python中的并发编程(中).md b/第34课:Python中的并发编程(3).md
similarity index 97%
rename from 第033课:Python中的并发编程(中).md
rename to 第34课:Python中的并发编程(3).md
index 42d70bc..4c47636 100644
--- a/第033课:Python中的并发编程(中).md
+++ b/第34课:Python中的并发编程(3).md
@@ -1,4 +1,4 @@
-## 第033课:Python中的并发编程(中)
+## 第034课:Python中的并发编程(中)
###创建进程
diff --git a/第034课:Python中的并发编程(下).md b/第35课:Python中的并发编程(4).md
similarity index 81%
rename from 第034课:Python中的并发编程(下).md
rename to 第35课:Python中的并发编程(4).md
index bec3f07..61d954e 100644
--- a/第034课:Python中的并发编程(下).md
+++ b/第35课:Python中的并发编程(4).md
@@ -1,4 +1,4 @@
-## 第034课:Python中的并发编程(下)
+## 第035课:Python中的并发编程(4)
### 异步编程
diff --git a/第036课:用Python程序操作Redis.md b/第36课:Python中的并发编程(5).md
similarity index 76%
rename from 第036课:用Python程序操作Redis.md
rename to 第36课:Python中的并发编程(5).md
index 76664f4..a446e07 100644
--- a/第036课:用Python程序操作Redis.md
+++ b/第36课:Python中的并发编程(5).md
@@ -1,4 +1,6 @@
-## 第036课:用Python程序操作Redis
+## 第036课:Python中的并发编程(5)
+
+### 异步编程
diff --git a/第035课:用Python程序操作MySQL.md b/第37课:用Python程序操作MySQL.md
similarity index 100%
rename from 第035课:用Python程序操作MySQL.md
rename to 第37课:用Python程序操作MySQL.md