目录
Hello,你好呀,我是灰小猿,一个超会写bug的程序猿!
最近发现很多开始学习编程的小伙伴苦于编程入门比较困难,而且有很多想学习编程却苦于没有资源的小伙伴,所以今天在这里为大家爆肝Python基础入门的相关技术,适合刚开始接触Python或苦于编程入门的小伙伴们,建议收藏认真阅读!相信会对大家的Python学习助一臂之力的!
本文持续更新,欢迎小伙伴收藏关注!
话不多说直接开肝!
在正式开始学习这个小节之前你要明白,现在我们是在学习写程序。那么在写程序之前你要知道程序的作用是什么?
程序的主要作用是处理数据。数据的种类有很多,我们在手机和电脑上看到的那些文字、数字、图片、视频、页面样式等等都是数据。这些数据都是由程序来处理并显示到屏幕上的。
虽然数据的种类形形色色,并且有些看起来比较复杂,但是在编程时它们实际上都是由一些非常基本的数据形式(或经过组合)来表示。这些基本数据形式有哪些呢?比如有常用到的数字和字符,以及其它的诸如数组、字节序列等形式。
以数字和字符为例,为大家介绍下在代码中它们是怎么表示的。
对于数字,数字在代码中的表示形式和平时的电脑输入一样,直接书写即可:
123
3.14159
对于字符,和平时的书写稍有不同,Python 代码中表示字符时一定要给字符括上单引号或双引号:
'How are you?'
'嗨!'
这些不同的数据表示(书写)形式,对应着不同的数据种类,而不同的数据种类又具有不同的功能或者作用。
我们将代码中的数据种类称为数据类型,也就是数据的类型。
代码中的所有数据都是有类型的。
数字所对应的数据类型有整数型以及浮点型。整数型表示整数数字,比如:0
,-59
,100
。浮点型表示小数数字,如 -3.5
,0.25
,0.0
。
字符所对应的数据类型叫字符串,所谓字符串就是一串字符。它里面可以是任意语言的字符,比如 '哼哼哈嘿'
,'Good Good Study'
。当然字符串里也可以只有一个字符,比如 'a'
。
有一种表示「是」或「否」的类型,叫做布尔型。它的值叫布尔值,只有 True
和 False
两种取值。这就好比考试时的判断题,结果只能二选一,要么「是」要么「否」。
另外还有一种很特别的类型:None 型,表示什么都没有,它就一个取值 None
。
说明:为了不增加大家的记忆负担,这里只介绍这五种基本数据类型,后续的我们慢慢掌握。
考大家一个问题,在代码中 1000
和 '1000'
是相同的东西吗?答案是不同,一个是数字,一个是字符串,数据类型不同。
对于整数型和浮点型,因为它们都被用来表示数值,理所应当这二者可以做数值运算,也就是加减乘除等操作。
我们进入 Python 解释器交互模式中,输入代码试验一下这些数值运算:
可以看到,数值的加(+
)、减(-
)、乘(*
)、除(/
)、除余(%
)都可以被计算。这些操作也是多种程序语言所通用的,除此之外 Python 还内置了次方运算(**
)和整除(//
):
这恐怕是 Python 的最简单的用法了——当作计算器!
说明:通常我们为了美观,会在上面的运算符号的左右各加上一个空格,如
12 - 24
,2 ** 3
。 之后的代码示例中我们会添加空格。
整数型和浮点型除了数值运算外,还可以做比较运算,也就是比较两个数值的大小。比较的结果是布尔值。如:
2 > 3
>>> 2 > 3 False
2 <= 3
>>> 2 <= 3 True
2 == 3
>>> 2 == 3 False
比较运算的运算符可以是大于(>
),小于(<
),大于等于(>=
),小于等于(<=
),等于(==
),不等于(!=
)。其写法与数学中的比较运算很相似,但不同的是「等于」和「不等于」,尤其注意「等于」是用两个等号 ==
表示。
刚才我们学习了数值运算,那我们现在来算算一周有多少秒,一年有多少秒。
首先我们不难得出一天有 60 * 60 * 24
秒。我们可以暂时把这个结果用某种方式记录下来,以便后续使用。用什么方式记录呢?我们可以使用变量。
变量其实就是编程者给代码中的某个数据所取的名字,之后的编程过程中使用这个名字就相当于使用它背后的数据。简单地来理解的话,我们可以把变量看作是代码中用于保存数据的临时容器。
创建变量的动作我们称之为定义变量。如下是定义变量的方法:
seconds_per_day = 60 * 60 * 24
在这里我们起了个名字 seconds_per_day
,并且通过符号 =
把 60 * 60 * 24
的计算结果给了它。seconds_per_day
这个名字就是我们所定义的变量,它的值(也就是其背后的数据)是 60 * 60 * 24
的实际运算结果。也就是说我们将一天的秒数 60 * 60 * 24
保存在了变量 seconds_per_day
中。
等号(=
) 在代码中是赋值的意思,表示将 =
右边的值赋予 =
左边的变量。注意赋值用等号 =
表示,而「等于」用 ==
(连续两个等号)表示。
执行刚才的代码后,紧接着输入 seconds_per_day
可以看到这个变量的值:
>>> seconds_per_day 86400
回到「一周有多少秒」的问题上去。我们有了表示一天的秒数的 seconds_per_day
变量,那我们的程序就可以这样写下去:
seconds_per_day * 7
>>> seconds_per_day * 7 604800
一天的秒数乘以七(天),最终结果是 604800
,没有任何问题。
刚才的完整连贯代码是: seconds_per_day = 60 * 60 * 24 seconds_per_day * 7
你可能会说「一周的秒数,直接计算 60 * 60 * 24 * 7
不就好了,也用不着使用变量」?是的,有时确实可以不使用变量。但使用变量有一个好处,那就是可以暂存一个中间结果,方便之后去重复利用它。
比如我们现在还想要再算一下「一年有多少秒」,因为前面已经算好了一天的秒数 seconds_per_day
,所以可以直接拿来利用:
seconds_per_day * 365
>>> seconds_per_day * 365 31536000
除此之外变量的好处还有,你可以通过妥当的变量名字来改善程序的可读性(阅读的容易程度)。比如我们在代码里写下 60 * 60 * 24
,别人(包括未来的你自己)在阅读时很难一下子理解这串运算表示什么。但是如果这样写呢: seconds_per_day = 60 * 60 * 24
。噢,原来是指一天的秒数。
前面内容中的变量是在定义的时候被赋值的,其实变量被定义后也可以反复给这个变量赋予新的值,这样变量中的数据就被更新了。如:
>>> day = 1 >>> day 1 >>> day = 2 >>> day 2 >>> day = 3 >>> day 3
变量用来保存数据,而数据类型用来指明数据的种类。
刚才我们使用了 seconds_per_day = 60 * 60 * 24
语句来定义变量 seconds_per_day
,并将它赋值为 60 * 60 * 24
。因为变量 seconds_per_day
中保存的是个整数型的值,所以我们说 seconds_per_day
是个整数型(的)变量。
这个章节中我们提到的 Python 基础数据类型有:
类型 | 表示 | 取值示例 |
---|---|---|
整数型 | 整数 | -59,100 |
浮点型 | 小数 | -3.5,0.01 |
字符串 | 文本 | '哼哼哈嘿','Good Good Study' |
布尔型 | 是与非 | True,False |
None 型 | 什么都没有 | None |
Python 中的数据类型不止这些,之后会渐渐涉及,表格中的这些类型也会在之后被应用到。
数值运算的符号有:
符号 | 含义 | 示例 |
---|---|---|
+ | 加法 | 1 + 1 |
- | 减法 | 2 - 3 |
* | 乘法 | 4 * 5 |
/ | 除法 | 6 / 7 |
% | 取余 | 8 % 9 |
** | 次方 | 2 ** 3(2 的 3 次方) |
// | 整除 | 5 // 4 |
数值比较的符号有:
符号 | 含义 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
上面的内容看起来罗列了很多,但其实不会带来记忆负担。数值运算和数值比较与数学上的概念和符号大致相同,略有区别而已。
我们通过以下形式来定义变量和赋值:
变量名 = 数据值
多语言比较: 「多语言比较」这部分内容,是为让大家了解本章节所介绍的语言基本特性在其它语言中是如何表达的。大家可以了解体会它们之间的相识之处。 不同于动态类型的 Python,在静态类型的语言中数据类型还有长度一说,也就是类型所能容纳的数据大小。并且变量在定义时还需先声明它的类型。以整数型为例。Java 中的整数型根据长度的不同分为:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节),浮点型分为 float(4 字节)、double(8 字节)。其它语言也有一些类似。C/C++ 中的整数型有「有无符号」之分(如
unsigned int
表示无符号的int
型,也就是说这只能表示 0 和正数,不能表示负数)。 Java 定义变量并初始化: int yearDays = 365 C/C++ 定义变量并初始化: int yearDays = 365 把 C 和 C++ 合并称为 C/C++,是因为 C++ 基本上是 C 的强大很多的超集,虽然 C++ 严格来说不是 100% 兼容 C,但几乎是兼容的。 Go 语言定义变量并初始化: var yearDays int = 365 Go 语言中的变量定义需要加上关键字 var,且数据类型(这里是int
)放在变量名后面。或者采用另一种写法: yearDays := 365 这种写法不但可以省略关键字var
还可以省略数据类型,数据类型可直接由编译器推导出来。 以上语言在变量定义后,都可通过下述语句再次赋值: yearDays = 366
上一节中讲了数据类型,有一个问题,之前所介绍的数据类型大多是用来表示单个数据的。比如整数型,一个整数型的变量只能保存一个整数。又如布尔型,一个布尔型的变量只能保存一个布尔值。浮点型和 None 型也是如此。要是此刻有一系列的数据,那该怎么在程序里保存和使用呢?
举个栗子:当我的只有一个电话号码的时候,我可以使用整数型来表示,并保存在变量里:
tel = 13011110000
但如果有十个电话号码,该怎么来表示和使用它们呢?
13011110000 18022221111 13433332222 13344443333 17855554444 13866665555 15177776666 13388887777 18799998888 17800009999
你可能会说,「那就用十个变量」,像这样:
tel01 = 13011110000
tel02 = 18022221111
...
tel10 = 17800009999
或者「把它们用逗号拼在一起然后放到字符串里」:
tels = '13011110000,18022221111,13433332222,13344443333,17855554444,13866665555,15177776666,13388887777,18799998888,17800009999'
是的,看起来这似乎能解决问题。但是这两种办法的弊端也很明显。第一种使用多个变量的方式,在数据量很大的情况下使用起来会十分繁琐;第二种使用字符串的方式,如果我们需要对其中的某些数据做处理,那这种方式就很不方便灵活了。
这时我们可以选择使用列表。
列表是一种用于保存批量数据的数据类型。它和整数型、布尔型等数据类型一样都被内置在 Python 中。
列表的写法
列表的写法为 [ 数据项1, 数据项2, ..., 数据项N ]
,方括号就代表列表,每个数据项放在方括号中并用逗号分隔。
如之前的那一串电话号码可以这样来保存:
tels = [13011110000, 18022221111, 13433332222, 13344443333, 17855554444, 13866665555, 15177776666, 13388887777, 18799998888, 17800009999]
扩展:为了方便阅读,我们也可以把把这个列表写成多行的形式: tels = [ 13011110000, 18022221111, 13433332222, 13344443333, 17855554444, 13866665555, 15177776666, 13388887777, 18799998888, 17800009999 ] 每个数据项一行,这样是不是更好看了! 在解释器的交互模式中输入这样的多行代码时,我们会发现第一行的提示符是
>>>
,之后每行的提示符会变成...
,直到完成了多行输入则又变回>>>
。如: >>> tels = [ … 13011110000, … 18022221111, … 13433332222 … ] >>>
列表中的数据可以是任意类型的。比如整数型、字符串类型和布尔类型等:
[100, 'about', True]
列表索引
列表中的每个数据项都是有先后次序的,最前面的数据项的位置编号为 0,之后依次是 1 ,2 …… N,这个位置编号在编程中的术语叫做索引(Index)。注意 Python 中索引是从 0 开始计数的,0 即代表第一个位置。
可以通过符号 []
来获取某个索引所对应的数据项。比如:
>>> fruits = [‘apple’, ‘banana’, ‘cherry’, ‘durian’] >>> fruits[0] ’apple’ >>> fruits[2] ’cherry’
上面的 fruits
有 4 项数据,所以最大的索引是 3
。如果我们强行要用更大的索引值去取数据会怎样呢,来试一下:
>>> fruits[4] Traceback (most recent call last): File “”, line 1, in IndexError: list index out of range
可以看到代码直接就报错了,具体信息为「list index out of range」,列表索引超出范围。
扩展:这是 Python 的典型报错形式,这里有三行内容(也可能会有很多行),前两行是错误定位,描述出错的位置(如某文件的某行),后面是错误描述,指出这是个
IndexError
错误,具体信息为「list index out of range」。 若大家在写代码时遇到错误,可以按照这种方法尝试自己分析错误信息。
除了通过索引去获取值,也可以通过索引去改变列表中某项数据的值。通过赋值的方式来实现:
fruits[0] = 'pear'
>>> fruits[0] ‘apple’ >>> fruits[0] = 'pear’ >>> fruits[0] ‘pear’
列表的长度
列表中数据项的个数,叫做列表(的)长度。
想要获得列表的长度可以使用 len()
这个东西。像这样:
len(fruits)
>>> len(fruits) 4
>>> len([1, 2, 3, 4, 5, 6, 7]) 7
说明:
len()
是 Python 中的内置函数。函数的概念会在之后的章节中介绍。
向列表添加数据
之前使用时,列表中的数据在一开始就已经被确定下来了,并一直保持着这个长度。但在很多时候,我们需要随时向列表中添加数据。
向列表的末尾添加数据可以用 .append()
这个东西,它的写法是:
列表.append(新数据)
看一个示例。这里首先创建了一个空的列表,将其变量命名为 fruits
,然后通过 .append()
向其中添加内容。
>>> fruits = [] >>> fruits [] >>> fruits.append(‘pear’) >>> fruits [‘pear’] >>> fruits.append(‘lemon’) >>> fruits [‘pear’, ‘lemon’]
扩展:append() 是列表的方法。「方法」具体是什么我们在之后的面向对象章节中介绍。这里暂且把方法理解为某个数据类型自带的功能,如 append() 是列表自带的功能。
字符串也可以保存批量数据,只不过其中的数据项只能是字符。
我们在前一个章节中介绍过字符串,字符串是用来表示文本的数据类型。字符串以单引号或双引号以及包裹在其中的若干字符组成,如:
'good good study'
'100'
'江畔何人初见月,江月何年初照人'
字符串索引
从形式上我们不难看出,字符串中的字符也是有先后次序的。字符串是字符的有序序列,所以也具有索引。也可以根据索引取出其中某一个字符。其索引使用方式和列表相同:
'good good study'[3]
>>> ‘good good study’[3] ‘d’
也可以先把字符串保存在变量里,然后在变量上使用索引。结果是一样的:
words = 'good good study'
words[3]
>>> words = ‘good good study’ >>> words[3] ‘d’
有一点需要注意,字符串不能像列表那样通过索引去改变数据项的值。因为字符串类型的值是不可变的(Immutable),我们不能在原地修改它其中的某个字符。
>>> words = ‘good good study’ >>> words[3] = 'b’ Traceback (most recent call last): File “”, line 1, in TypeError: ‘str’ object does not support item assignment
上面报出一个 TypeError 错误,具体信息为「‘str’ object does not support item assignment」,其中「‘str’ object」指的就是字符串,它不支持直接为其中某一个项(字符)赋值。
字符串长度
字符串中字符的个数也就是字符串的长度(包括空格在内的所有空白符号)。
获取字符串长度的方式和列表一样,也是使用 len()
:
len('good good study')
>>> len(‘good good study’) 15
如果我们想要保存和表示批量数据,可以使用 Python 中的列表(List)类型。列表是有序序列,能保存任意类型的数据项,可以通过索引(Index)来获取和修改其中某一个数据项,可以通过 len()
函数来获取列表的长度,也可以通过 .append()
在列表末尾追加数据项。
如果数据是文本,那么可以用字符串类型(String)来表示。字符串类型是字符的有序序列,可以通过索引获取某个位置的字符,也可以通过 len()
函数来获取长度。
Python 中的列表和字符串还有很多功能,之后讲「数据结构」时为大家一一介绍。
多语言比较: 数组是保存和表示批量数据的最基本的结构,它也是构造字符串、集合和容器的基石。 Python 中没有数组概念,取而代之的是列表这种更高级的数据结构,列表涵盖了数组的功能并提供了更多且更强大的功能。 Java 中,用
类型[]
的写法来表示数组: // 定义数组 int numbers[]; // 定义数组并用指定值初始化: int numbers[] = {1, 2, 3}; C/C++ 定义数组: // 定义数组 int numbers[3]; // 定义数组并用指定值初始化: int numbers[] = {1, 2, 3}; Go 语言定义数组: // 定义数组 var numbers [3] int // 定义数组并用指定值初始化: var numbers = [3]int {1, 2, 3}
前面的章节中我们学习了数据类型、变量、赋值、数值运算,并且用这些知识编写程序来做一些简单的运算,比如「计算一年有多少秒」。像这样的程序,执行流程是完全固定的,每个步骤事先确定好,运行时一步一步地线性地向下执行。
但是很多时候程序的功能会比较复杂,单一的执行流程并无法满足要求,程序在运行时可能需要对一些条件作出判断,然后选择执行不同的流程。这时就需要分支和循环语句了。
在开始学习分支和循环前,为了可以让程序与我们交互,先来学习三个函数。至于什么是函数,我们暂且把它看作是程序中具有某种功能的组件,下一小节中将会详细介绍函数的概念。
input() 函数
如果想要通过命令行与程序交互,可以使用 input()
函数。
input()
函数可以在代码执行到此处时输出显示一段提示文本,然后等待我们的输入。在输入内容并按下回车键后,程序将读取输入内容并继续向下执行。读取到的输入内容可赋值给变量,供后续使用。写法如下:
读取到的输入 = input('提示文本')
>>> age = input(‘请输入你的年龄:’) 请输入你的年龄:30 >>> age ’30’
这行代码会在命令行中显示「请输入你的年龄:」,然后等待输入,读取到输入内容后赋值给 age
变量。
input()
返回的结果是字符串类型,如 '30'
。如果我们需要整数型,可以使用 int()
函数进行转换。
int() 函数
int()
函数可以将字符串、浮点型转换整数型。写法为:
int(字符串或浮点数)
将字符串类型 ‘1000’ 转换为整数型 1000:
>>> int(‘1000’) 1000
将浮点数 3.14 转化为整数:
>>> int(3.14) 3
print() 函数
print()
函数可以将指定的内容输出到命令行中。写法如下:
print('要输出的内容')
>>> print(‘Hello World!’) Hello World! >>> print(‘你的年龄是’, 20) 你的年龄是 20
要输出的内容放在括号中,多项内容时用逗号分隔,显示时每项以空格分隔。
input()、print() 示例
我们可以把 input()
和 print()
结合起来。如下面这两行代码将在命令行中提示「请输入你的年龄:」,然后等待输入,手动输入年龄后按下回车键,将显示「你的年龄是 x」。
age = input('请输入你的年龄:')
print('你的年龄是', age)
我们把代码保存到文件中,文件命名为 age.py
, 然后执行下:
➜ ~ python3 age.py 请输入你的年龄:18 你的年龄是 18
上一个例子很简单,接收一个输入内容然后把该内容显示出来。现在难度升级。在刚才代码的基础上,如果所输入的年龄小于 18 岁,那么在最后再显示一句勉励语——「好好学习,天天向上」。如何来实现?
if 语句
如果想要表达「如果……」或者「当……」这种情形,需要用到 if
语句。它的写法是:
if 条件:
代码块
它的执行规则是,若「条件」满足,则执行 if
下的「代码块」,若「条件」不满足则不执行。
条件满足指的是,条件的结果为布尔值 True
,或非零数字,或非空字符串,或非空列表。
代码块就是一段代码(可以是一行或多行),这段代码作为一个整体以缩进的形式嵌套在 if
下面。按照通常的规范,缩进以 4 个空格表示。
回到我们之前的需求上,「当年龄小于 18 岁」就可以通过 if
语句来实现。完整代码如下:
age = int(input('请输入你的年龄:')) # 注意此处用 `int()` 将 `input()` 的结果由字符串转换为整数型
print('你的年龄是', age)
if age < 18:
print('好好学习,天天向上')
保存在文件中,执行一下看看:
➜ ~ python3 age.py 请输入你的年龄:17 你的年龄是 17 好好学习,天天向上
➜ ~ python3 age.py 请输入你的年龄:30 你的年龄是 30
可以看到,当所输入的年龄小于 18 时,程序在最后输出了「好好学习,天天向上」,而输入年龄大于 18 时则没有。
else 语句
又在上面的基础上,如果输入的年龄大于等于 18 岁,输出「革命尚未成功,同志任需努力」。该如何实现?
我们可以在 if
语句之后紧接着使用 else
语句,当 if
的条件不满足时,将直接执行 else
的代码块。写法如下:
if 条件:
代码块 1
else:
代码块 2
若条件满足,则执行代码块 1,若不满足则执行代码块 2。所以之前的需求我们可以这样实现:
age = int(input('请输入你的年龄:'))
print('你的年龄是', age)
if age < 18:
print('好好学习,天天向上')
else:
print('革命尚未成功,同志仍需努力')
执行下看看:
➜ ~ python3 age.py 请输入你的年龄:18 你的年龄是 18 革命尚未成功,同志任需努力
➜ ~ python3 age.py 请输入你的年龄:17 你的年龄是 17 好好学习,天天向上
elif 语句
我们可以看到,if
和 else
表达的是「如果……否则……」这样的二元对立的条件,非此即彼。但有时我们还需要表达「如果……或者……或者……否则……」这样多个条件间的选择。
举个例子,下表是年龄和其对应的人生阶段。
年龄 | 人生阶段 |
---|---|
0-6 岁 | 童年 |
7-17 岁 | 少年 |
18-40 岁 | 青年 |
41-65 岁 | 中年 |
65 岁之后 | 老年 |
当我们在程序中输入一个年龄时,输出对应的人生阶段。该如何实现?我们先用汉语来描述一下代码逻辑(这种自然语言描述的代码逻辑,也叫作伪代码):
如果年龄小于等于 6:
输出童年
如果年龄介于 7 到 17:
输出少年
如果年龄介于 18 到 40:
输出青年
如果年龄介于 41 到 65:
输出中年
否则:
输出老年
可以看到,我们需要依次进行多个条件判断。要实现它就要用到 elif
语句了,字面上它是 else if 的简写。
elif
置于 if
和 else
之间,可以有任意个:
if 条件 1:
代码块 1
elif 条件 2:
代码块 2
else
代码块 3
之前根据年龄输出人生阶段的需求,可以这样实现:
age = int(input('请输入年龄:'))
if age <= 6:
print('童年')
elif 7 <= age <=17:
print('少年')
elif 18 <= age <= 40:
print('青年')
elif 41 <= age <= 65:
print('中年')
else:
print('老年')
➜ ~ python3 age.py 请输入年龄:3 童年 ➜ ~ python3 age.py 请输入年龄:17 少年 ➜ ~ python3 age.py 请输入年龄:30 青年 ➜ ~ python3 age.py 请输入年龄:65 中年 ➜ ~ python3 age.py 请输入年龄:100 老年
分支语句小结
如上所述,if
可以配合 elif
和 else
一起使用。代码执行时,将会从第一个条件开始依次验证判断,若其中某个条件满足,则执行对应的代码块,此时后续条件将直接跳过不再验证。
一个 if
-elif
-else
组合中,elif
可出现任意次数,else
可出现 0 或 1 次。
之前介绍的 if
语句,是根据条件来选择执行还是不执行代码块。我们还有一种很重要的场景——根据条件来判断代码块该不该被重复执行,也就是循环。
在 Python 中可以使用 while
语句来执行循环操作,写法如下:
while 条件:
代码块
它的执行流程是,从 while 条件
这句出发,判断条件是否满足,若满足则执行代码块,然后再次回到 while 条件
,判断条件是否满足……循环往复,直到条件不满足。
可以看到,如果这里的条件一直满足且固定不变,那么循环将无穷无尽地执行下去,这称之为死循环。一般情况下我们很少会刻意使用死循环,更多的是让条件处于变化中,在循环的某一时刻条件不被满足然后退出循环。
循环示例
举个例子,如何输出 100 次「你很棒」?
显然我们可以利用循环来节省代码,对循环条件做一个设计,让它刚好执行 100 次后结束。
count = 0
while count < 100:
print('你很棒')
count = count + 1
利用一个计数器 count
让它保存循环的次数,当 count
小于 100 就执行循环,代码块每执行一次就给 count
加 1
。我们在大脑中试着来模拟这个流程,用大脑来调试(Debug)。
将代码写入文件 loop.py,执行下看看:
➜ ~ python3 loop.py 你很棒 你很棒 你很棒 …
程序将如预期输出 100 行「你很棒」。
扩展:
count = count + 1
可以简写为count += 1
if
语句和 while
语句中的条件可以由多个语句组合表达。
and 关键字
要表达多个条件同时满足的情况,可以使用 and
关键字。使用 and
关键字时,在所有并列的条件均满足的情况下结果为 True
。至少一个条件不满足时结果为 False
。如:
>>> 2 > 1 and ‘abc’ == ‘abc’ and True True >>> 1 > 0 and 0 != 0 False
在 if
语句中可以这样使用 and
关键字 :
if 条件1 and 条件2 and 条件N:
代码块
上述 if
语句只有在所有的条件均满足的情况下,代码块才会被执行。
例如我们假设把年龄大于 30 并且为男性的人称为大叔,「年龄大于 30 」和「男性」是两个判断条件,并且需要同时满足,这种情况就可以用 and
关键字来表达。如:
if age > 30 and sex == 'male':
print('大叔')
or 关键字
要表达多个条件中至少一个满足即可的情况,可以使用 or
关键字。使用 or
关键字时,并列的条件中至少有一个满足时,结果为 True
。全部不满足时结果为 False
。
在 if
语句中可以这样使用 or
关键字 :
if 条件1 or 条件2 or 条件N:
代码块
上述 if
语句中只要有任意一个(或多个)条件满足,代码块就会被执行。
not 关键字
not 关键字可以将一个布尔值取反。如:
>>> not True False >>> >>> not 1 > 0 False
用在 if
语句和 while
语句的条件上时,条件的结果被反转。
在 if
语句中可以这样使用 not
关键字 :
if not 条件:
代码块
上述 if
语句在条件不满足时执行代码块,条件满足时反而不执行,因为 not
关键字对结果取了反。
前面介绍了 while
循环,在 Python 中还有一种循环方式——for
循环。
for
循环更多的是用于从头到尾地去扫描列表、字符串这类数据结构中的每一个项,这种方式叫做遍历或迭代。
for
循环写法为:
for 项 in 序列:
代码块
其执行过程是,反复执行 for 项 in 序列
语句和其代码块,项
的值依次用序列的各个数据项替换,直到序列的所有项被遍历一遍。
比如,有个列表为 ['apple', 'banana', 'cherry', 'durian']
,我们想依次输出它的每个列表项,就可以用 for
循环。
fruit_list = ['apple', 'banana', 'cherry', 'durian']
for fruit in fruit_list:
print(fruit)
将代码写入 for.py
,执行下:
➜ ~ python3 for.py apple banana cherry durian
可以看到,
fruit
的值为 apple
fruit
的值为 banana
fruit
的值为 cherry
fruit
的值为 durian
每次循环时 fruit
都自动被赋予新的值,直到 fruit_list
的所有列表项遍历完,循环退出。
input()
函数可以在程序运行到此处时输出一段提示文本,然后停留在此等待我们的输入,输入内容后按下回车键,程序将读取输入内容并向下执行。写法为:
age = input('请输入你的年龄:')
print()
函数可以将内容输出到命令行中,内容放到括号中,多项内容时可用逗号分隔。写法为:
print('你的年龄是', 20)
int()
函数可以将字符串、浮点型转换整数型。写法为:
int(字符串或浮点数)
if
,elif
,else
组合使用,根据条件来选择对应的执行路径。写法为:
if 条件 1:
代码块 1
elif 条件 2:
代码块 2
else:
代码块 3
while
语句来用执行循环操作,根据条件来判断代码块该不该被重复执行。写法为:
while 条件:
代码块
for
循环通常用于执行遍历操作。写法为:
for 项 in 序列:
代码块
多语言比较: Java 中的分支语句: if (条件1) { 代码块1 } else if (条件2) { 代码块2 } else { 代码块3 } C/C++ 中的分支语句: if (条件1) { 代码块1 } else if (条件2) { 代码块2 } else { 代码块3 } Go 中的分支语句: if 条件1 { 代码块1 } else if 条件2 { 代码块2 } else { 代码块3 } Java 中的循环: for (int i=0; i < 100; i++) { 代码块 } // 或 for each 形式 for (int number: numbers) { 代码块 } C/C++ 中的循环: for (int i=0; i < 100; i++) { 代码块 } Go 中的循环: for i := 0; a < 100; i++ { 代码块 } // 相当于 while for 条件 { 代码块 } // for each 形式 for index, item := range numbers { 代码块 }
我们之前介绍过一些函数,如 print()
、int()
、input()
等。直接使用它们就可以获得一些功能,如向命令行输出内容、转换数字、获取命令行输入,那么它们到底是什么呢?
大家应该都非常熟悉数学上的函数,简单来说数学上的函数就是一个映射关系,给定一个 x,经映射后将得到 y 值,至于这其中的映射关系我们可以直接把它抽象为 y=f(x)。
程序中的函数与数学上的函数有一丝类似,我们也可以把它抽象地看作一个映射关系,给定输入参数后,经函数映射,返回输出结果。如之前我们使用过的 int()
和 len()
:
数字 = int(字符串)
长度 = len(列表)
给定输入值,经函数处理,返回输出值,这是函数最单纯的模式。
Python 中函数的定义方式如下:
def 函数名(参数1, 参数2, ...):
代码块
函数的输入值叫做函数参数,如上面的「参数1」、「参数2」。函数参数的个数可以是任意个,如 0 个、1 个或多个。需要注意参数是有顺序的,使用时要按对应位置传递参数。
函数内部的代码块就是函数的实现。所有的函数功能都实现于此。
函数的输出结果叫函数的返回值。函数可以没有返回值,也可以有一个或多个返回值。返回值通过 return
语句传递到函数外部,如:
def add(x, y):
return x + y
函数定义示例
我们来编写一个函数试试,这个函数的需要的参数是年龄,返回值是年龄对应的人生阶段。
年龄 | 人生阶段 |
---|---|
0-6 岁 | 童年 |
7-17 岁 | 少年 |
18-40 岁 | 青年 |
41-65 岁 | 中年 |
65 岁之后 | 老年 |
这个功能好像有点眼熟,没错,上一章节中我们完成过这个功能,现在把这个功能改写成函数。
可以这样来定义这个函数:
def stage_of_life(age):
if age <= 6:
return '童年'
elif 7 <= age <=17:
return '少年'
elif 18 <= age <= 40:
return '青年'
elif 41 <= age <= 65:
return '中年'
else:
return '老年'
我们给函数起名为 stage_of_life
,需要一个参数 age
,最终通过 return
语句返回对应的人生阶段,这个人生阶段就是函数的返回值。
这里虽然有多个 return
语句,但是实际上每次函数使用时,只会有一个 return
语句被执行。
副作用
上面这个示例中,给定一个参数 age
,便返回对应的人生阶段。函数内部只是做了一个映射,并没有对程序和系统的状态作出影响,这样的函数是纯函数。
纯函数是函数的一个特例,更普遍的情况是,函数包含一些会引起程序或系统状态变化的操作,如修改全局变量、命令行输入输出、读写文件等,这样的变化叫做函数的副作用。
副作用并不是不好的作用,它只是函数在输入值和输出值间映射之外,所附带的作用。副作用在有些时候是不可避免的。
因为有了副作用,函数就不必完全遵从 输入 -> 映射 -> 输出
这种模式,函数可以在没有参数或返回值的情况下,拥有其功能。如果你看到一个函数没有参数或返回值,要自然的想到,那是副作用在发挥作用。
没有参数没有返回值:
def say_hello():
print('hello')
有参数没有返回值:
def say_words(words):
print(words)
没有参数有返回值:
def pi():
return 3.14159
函数定义完成后,就可以在后续的代码中使用它了,对函数的使用叫做函数调用。
以前我们调用过 int()
、len()
,它们是内置在 Python 语言中的函数,也就是内置函数。现在我们自己定义了 stage_of_life
,调用的方式是一样的:
def stage_of_life(age):
if age <= 6:
return '童年'
elif 7 <= age <=17:
return '少年'
elif 18 <= age <= 40:
return '青年'
elif 41 <= age <= 65:
return '中年'
else:
return '老年'
stage = stage_of_life(18)
print(stage)
用参数的形式将数据传递给函数,用赋值语句来接收返回值。
需要说明的是
从形式上来看,函数将一段代码包裹起来,调用函数就像当于执行那段代码。代码所需要的数据我们可以通过函数参数的形式传递进去,代码的执行结果通过返回值出传递出来。那么函数到底有什么用呢?
抽象
函数的价值主要体现在调用时,而不是定义时。调用时函数就像个盒子,使用者不需要了解其中有什么代码,是什么样的逻辑,只要知道怎么使用它的功能就足够了。以 len()
函数为例,我们不知道这个函数的原理,但是能用它达到我们获取列表长度的目的,这就是它的重要价值。
简单来说函数的主要作用是抽象,屏蔽繁杂的内部细节,让使用者在更高的层次上简单明了地使用其功能。我们之前说过「计算机的世界里最重要的原理之一就是抽象」,函数就是其一个体现。
代码复用
因为具有抽象的好处,函数也延伸出另一个作用——复用,或者叫代码复用。也就是便于重复使用,节省代码。举个例子,假如我们想用程序计算并输出 -33,456,-0.03 的绝对值,不用函数时我们这样写:
number = -33
if number > 0:
print(number)
else:
print(-number)
number = 456
if number > 0:
print(number)
else:
print(-number)
number = -0.03
if number > 0:
print(number)
else:
print(-number)
显然有大量重复的代码,这些重复代码是可以避免的。用函数修改后如下:
def print_absolute(number):
if number > 0:
print(number)
else:
print(-number)
print_absolute(-33)
print_absolute(456)
print_absolute(-0.03)
代码量减少很多,也能应对未来的相同需求,这就是复用的好处。
看了上面函数的好处之后,想必你已经知道在什么时候用函数了吧?
函数的主要作用是抽象和代码复用。
Python 中函数的定义方法:
def 函数名(参数1, 参数2, ...):
代码块
返回值通过 return
语句传递到函数外部。
多语言比较 Java 中所有的函数都需要定义在类中,类中的函数也叫做方法。 Java 中定义函数: int add(int x, int y) { return x + y } C/C++ 中定义函数: int add(int x, int y) { return x + y } Go 中定义函数: func max(x, y int) int { return x + y }
我们之前写的代码能够正常运行是建立在一个前提之下的,那就是假设所有的命令行输入或者函数参数都是正确无误的,并且执行过程中每个环节都是可靠和符合预期的。
当然,在程序的实际开发和使用过程中,这个前提是不能成立的,所有的假设都无法完全保证。比如:
这些错误发生在程序运行阶段,无法在编码阶段预知到它们是否会发生,但我们可以未雨绸缪,在代码中对潜在错误做出处理,以避免对程序运行造成破坏性影响。
说明:开发程序过程中还有一种常见的错误,就是开发者编写代码时的语法错误、编译错误以及运行时的 Bug。这些错误可以在开发时通过测试、调试、日志诊断等手段予以发现和解决,并不属于本章节所讲的错误处理机制的范畴。且不能用错误处理机制来规避 Bug。
首先错误发生时,需要先捕获到该错误,然后根据具体的错误内容或类型,选择后续处理的方式。
在 Python 中大多数情况下,错误是以抛出异常的形式报告出来。如列表的索引越界异常:
>>> fruit = ['apple', 'banana'][2]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
上面提示发生了「IndexError」错误,这个 IndexError
就是异常的一种。在这里它直接被解释器捕捉到,然后将错误信息输出到了命令行中。
我们也可以自己来捕获异常,然后自定义处理方式。
try-except 语句捕获异常
异常的捕获使用 try-except
语句:
try:
代码块1
except:
代码块2
执行流程是,从 try
下的 代码块1
开始执行,若其中有异常抛出,那么异常将会被捕获,直接跳转并执行 except
下的 代码块2
。若 代码块1
一切正常,并没有异常抛出,那么 代码块2
将不会被执行。
也就是说 代码块1
是我们想要正常运行的代码,而 代码块2
是当错误发生时用于处理错误的代码。
来看一个使用 try-except
时发生异常的例子:
>>> try: … fruit = [‘apple’, ‘banana’][2] … print(fruit) … except: … print(‘列表索引越界啦’) … 列表索引越界啦
这里的执行流程是,执行 try
下的 ['apple', 'banana'][2]
,此时由于索引越界而产生异常,代码 print(fruit)
将被跳过,转而执行 except
下的 print('列表索引越界啦')
。
再来看一个无异常的例子:
>>> try: … fruit = [‘apple’, ‘banana’, ‘cherry’][2] … print(fruit) … except: … print(‘列表索引越界啦’) … cherry
可以看到无异常抛出时,try
下的代码被全部执行,except
下的代码不会被执行。
捕获指定的异常
之前我们没有直接指定要捕获的异常类型,所以所有类型的异常都会被捕获。
我们也可以显式地指定要捕获的异常种类。方法是:
try:
代码块1
except 异常X as e:
代码块2
和之前的区别在于,多出了 异常X as e
这一部分。异常X
是指定的要捕获的异常名,如 IndexError
、NameError
。as e
语句是将异常对象赋予变量 e
,这样 e
就可以在 代码块2
中使用了,如获取错误信息。
如下是捕获指定异常的例子:
>>> try: … fruit = [‘apple’, ‘banana’][2] … except IndexError as e: … print(‘出现索引越界错误:’, e) … 出现索引越界错误: list index out of range
这里我们显式地指定要捕获 IndexError
异常,并且将异常中的错误信息输出出来。
显式指定异常时,只有被指定的异常会被捕获,其余异常将会被忽略。
捕获指定的多个异常
上面是指定并捕获一个异常,当然也可以在一个 try
语句下指定并捕获多个异常。有两种方式:
try:
代码块1
except (异常X, 异常Y, 异常Z) as e:
代码块2
try:
代码块1
except 异常X as e:
代码块2
except 异常Y as e:
代码块3
except 异常Z as e:
代码块4
如上,第一种方式是将多个异常放在一个 except
下处理,第二种方式将多个异常分别放在不同的 except
下处理。无论用哪种方式,异常抛出时,Python 会根据异常类型去匹配对应的 except
语句,然后执行其中代码块,若异常类型未能匹配到,则异常会继续抛出。那么这两种方式有什么区别呢?
try-except-finally 语句
在之前介绍的 try-except
语句之后,还可以紧跟 finall
语句,如下:
try:
代码块1
except 异常X as e:
代码块2
finally:
代码块3
它的执行流程是,
代码块1
代码块2
,否则跳过 代码块2
代码块3
也就是说在 try-except
执行流程的基础上,紧接着执行 finally
下的代码块,且 finally
下的代码必定会被执行。
finally
有什么用?举个例子,我们有时会在 try
下使用一些资源(比如文件、网络连接),而无论过程中是否有异常产生,我们在最后都应该释放(归还)掉这些资源,这时就可以将释放资源的代码放在 finally
语句下。
下表中是 Python 常见的内置异常:
异常名 | 含义 |
---|---|
Exception | 大多数异常的基类 |
SyntaxError | 无效语法 |
NameError | 名字(变量、函数、类等)不存在 |
ValueError | 不合适的值 |
IndexError | 索引超过范围 |
ImportError | 模块不存在 |
IOError | I/O 相关错误 |
TypeError | 不合适的类型 |
AttributeError | 属性不存在 |
KeyError | 字典的键值不存在 |
ZeroDivisionError | 除法中被除数为 0 |
除此之外内置异常还有很多,待日后慢慢积累掌握。
之前的示例中,异常是在程序遇到错误无法继续执行时,由解释器所抛出,我们也可以选择自己主动抛出异常。
主动抛出异常的方法是使用 raise
语句:
raise ValueError()
也可以同时指明错误原因:
raise ValueError("输入值不符合要求")
我们用示例来学习为什么要主动抛出异常,以及如何主动抛出异常。
之前我们在学习函数的时候写过这样一个函数:
def stage_of_life(age):
if age <= 6:
return '童年'
elif 7 <= age <=17:
return '少年'
elif 18 <= age <= 40:
return '青年'
elif 41 <= age <= 65:
return '中年'
else:
return '老年'
显然这个函数没有应对可能出错的情况。比如函数的 age
参数不能任意取值,要符合人类的年龄范围才行,如果取值超出范围就需要向函数调用方报告错误,这时就可以采取主动抛出异常的方式。
我们在函数内检验输入值的有效性,若输入有误则向外抛出异常,新增第 2 和第 3 行代码:
def stage_of_life(age):
if age < 0 or age > 150:
raise ValueError("年龄的取值不符合实际,需要在 0 到 150 之间")
if age <= 6:
return '童年'
elif 7 <= age <=17:
return '少年'
elif 18 <= age <= 40:
return '青年'
elif 41 <= age <= 65:
return '中年'
else:
return '老年'
这里检查 age
的范围是否在 0~150 之间,若不是则使用 raise
抛出 ValueError
异常,表示取值错误。
用不为 0——150 的数字执行下函数看看:
>>> stage_of_life(-11) Traceback (most recent call last): File “”, line 1, in File “”, line 3, in stage_of_life ValueError: 年龄的取值不符合实际,需要在 0 到 150 之间 >>> stage_of_life(160) Traceback (most recent call last): File “”, line 1, in File “”, line 3, in stage_of_life ValueError: 年龄的取值不符合实际,需要在 0 到 150 之间
在 Python 中大多数情况下,错误是以抛出异常的方式报告出来,可以针对潜在的异常来编写处理代码。
可使用 try-except
语句捕获异常
异常的捕获使用 try-except
语句:
try:
代码块1
except 异常X as e:
代码块2
捕获多个异常:
try:
代码块1
except (异常X, 异常Y, 异常Z) as e:
代码块2
try:
代码块1
except 异常X as e:
代码块2
except 异常Y as e:
代码块3
except 异常Z as e:
代码块4
finally
语句紧接着 try-except
的流程执行:
try:
代码块1
except 异常X as e:
代码块2
finally:
代码块3
使用 raise
语句可主动抛出异常:
raise ValueError()
Python 中内置有这么一个函数,通过它可以查看变量或值的数据类型,它就是 type()
。像这样来使用:
type(变量或值)
执行几个例子看看:
>>> type(100) <class ‘int’>
>>> type(3.14) <class ‘float’>
>>> type(‘words’) <class ‘str’>
>>> type(True) <class ‘bool’>
>>> type(None) <class ‘NoneType’>
>>> type([1, 2, 3]) <class ‘list’>
执行的结果是 <class '类型'>
形式,其中类型的含义是:
类型 | 含义 |
---|---|
int | 整数型 |
float | 浮点型 |
str | 字符串类型 |
bool | 布尔型 |
NoneType | None 类型 |
list | 列表类型 |
上表中的这些数据类型,都内置在 Python 中。
那 <class '类型'>
中的 class
是指什么呢?
class
是指面向对象编程范式中的一个概念——类。Python 中的数据类型就是类,一个类对应一种数据类型。类的具体对象中可以保存若干数据,以及用于操作这些数据的若干函数。
我们来看一个例子:
我们常用的字符串类型,就是名为 str
的类。一个 str
中可以保存若干字符,并且针对这些字符提供了一系列的操作函数。
如 'hello'
就是一个 str
对象,我们可以把这个对象赋值给变量:
>>> words = ‘hello’ >>> words ’hello’
str
对象自带的 find()
函数,可用于获取字符的索引:
>>> words.find(‘e’) 1
str
对象自带的 upper()
函数,可用于获取英文字符的大写形式:
>>> words.upper() ‘HELLO’
除此 str
之外,前面列表中的那些数据类型也都是类。
像 str
、int
、list
这样的类,是被预先定义好并且内置在 Python 中的。
当然,我们也可以自己来定义类。
类的定义方法是:
class 类名:
代码块
如:
class A:
pass
这里定义了一个非常简单的类,名为 A
。pass
是占位符,表示什么都不做或什么都没有。
我们把类看作是自定义的数据类型,既然是类型,那么它只能用来表示数据的种类,不能直接用于保存数据。想要保存数据,就需要先创建一个属于这种类型的类似于容器的东西,这种容器就叫做对象(或称实例)。通过类产生对象的过程叫实例化。
打个比方,类就相当于图纸,对象就相当于按照图纸所生产出来的产品。图纸能决定产品的内部构造以及所具有的功能,但图纸不能替代产品被直接使用。类能决定对象能保存什么样的数据,以及能拥有什么样的函数,但类不直接用来保存数据。
定义好类以后,可以像这样实例化对象:
变量 = 类名()
通过 类名()
这样类似函数调用的方式生成出对象,并将对象赋值给 变量
。
如实例化之前的类 A
并将对象赋值为 a
:
>>> class A: … pass … >>> a = A()
查看变量 a
的类型:
>>> type(a) <class ‘__main__.A’>
可以看到类型是 __main__.A
,表示模块 __main__
下的 A
类。模块的概念后续章节中介绍,现在只需关注类即可。
可以看看 a
是什么:
>>> a <__main__.A object at 0x103d8e940>
a
是 A
的对象,位于内存的 0x103d8e940
地址。
之前定义的 A
类是一个空的类,像一个空壳子,它的对象 a
并没有保存任何数据。
想要在对象中保存数据该怎么做呢?
可以像这样来定义类,实例化的时候就可以用参数的形式将数据传入,并保存在对象中:
class 类名:
def __init__(self, 数据1, 数据2, ...):
self.数据1 = 数据1
self.数据2 = 数据2
...
和之前相比类的内部多了一个函数 __init__()
,__init__()
函数一方面可以接收要保存在对象中的数据,另一方面也可以在实例化类的时候做一些初始化工作。
我们通过实际例子来学习。之前介绍的类(数据类型)要么保存一个数据,要么保存多个数据,假如现在想要一个不多不少只保存两个数据的类,这就需要我们自己来定义了。如下:
class Pair:
def __init__(self, first, second):
self.first = first
self.second = second
我们将这个类命名为 Pair
,即表示数据对。
它的 __init__()
函数有三个参数:
self
,它是类中函数默认的第一个参数,表示对象自身。我们可以将数据赋值给 self.数据名
,这样数据就保存在对象中了first
是数据对的第一个值second
是数据对的第二个值实例化的时候像这样传入数据:
pair = Pair(10, 20)
这个过程中会自动调用 __init__()
函数,并将 10
传给了 first
参数,将 20
传给了 second
参数,而 __init__()
的第一个参数 self
是不需要传值的,Python 会自动填充这个参数。
实例化之后我们可以通过 pair
对象来获取数据对中的数据,像这样:
pair.first
pair.second
>>> pair = Pair(10, 20) >>> pair.first 10 >>> pair.second 20
通过 pair = Pair(10, 20)
来实例化 Pair
类,得到对象的变量 pair
,使用 pair.first
、pair.second
就可以获得对象中保存的数据了。
first
和 second
叫做 Pair
类的对象属性,一般也可以直接叫作属性。
我们不仅可以通过对象获取对象属性的值,也能修改对象属性值。如:
>>> pair = Pair(10, 20) >>> pair.first = 1000 1000 >>> pair.first 1000
刚才在类中定义了对象属性,也可以在类中定义一些函数。这样的函数可直接由对象调用,例如我们之前学过的 list.append()
。
定义在类中,供对象调用的函数称为对象方法,一般也可以直接叫作方法。定义方式如下:
class 类名:
def 函数1(self, 参数1, 参数2):
...
定义对象方法时第一个参数默认使用 self
,定义时必须有这个参数,但是调用时不必传递。之前介绍过的 __init__()
就是一个对象方法,不过是个特殊的对象方法。
我们在之前 Pair
类的基础上定义一个方法,功能是交换对象的 first
和 second
属性的值。来实现一下:
class Pair:
def __init__(self, first, second):
self.first = first
self.second = second
def swap(self):
self.first, self.second = self.second, self.first
这个方法被命名为 swap
,无需传递参数,内部通过
self.first, self.second = self.second, self.first
实现了 self.first
和 self.second
两个值的交换。
执行下看看:
>>> pair = Pair(10, 20) >>> pair.first 10 >>> pair.second 20 >>> pair.swap() >>> pair.first 20 >>> pair.second 10
定义类的方式是:
class 类名:
代码块
在类中定义方法:
class 类名:
def 方法(self, 参数1, ...):
self.数据1 = 数据1
...
可以在 __init__
方法中定义对象属性,之后在实例化类的时候传入数据。如:
class Pair:
def __init__(self, first, second):
self.first = first
self.second = second
pair = Pair(10, 20)
之前介绍过两种运行 Python 代码的方式,一种是解释器的交互模式,另一种是直接运行 Python 代码文件。
在 Python 中,每一个 Python 代码文件就是一个模块。写程序时,我们可以将代码分散在不同的模块(文件)中,然后在一个模块里引用另一个模块的内容。
在一个模块中引用(导入)另一个模块,可以使用 import
语句:
import 模块名
这里的模块名是除去 .py
后缀的文件名称。如,想要导入模块 abc.py
,只需 import abc
。
import
模块之后,就可以使用被导入模块中的名字(变量、函数类)。方式如下:
模块名.变量
模块名.函数
模块名.类
导入及使用模块示例
我们用个例子来试验下模块的导入和使用,在这个例子中,农民种下果树,然后等待果树结果收获。
在同一个目录下创建两个模块:
tree_farmer # 目录名
|___tree.py # 文件名
|___farmer.py # 文件名
第一个模块名为 tree.py
,内容如下:
import random
fruit_name = ''
def harvest():
return [fruit_name] * random.randint(1, 9)
代码中各个变量和函数的功能如下:
fruit_name
用来保存水果名称。将在函数 harvest()
中使用;random.randint(1, 9)
,随机生成 1~9 中的一个数;[fruit_name] * 数字
,该形式是将列表项重复若干遍。比如执行 ['X'] * 3
将得到 ['X', 'X', 'X']
;harvest()
函数返回一个包含 1~9 个列表项的列表,其中每个项都是 fruit_name
的值。第二个模块名为 farmer.py
,内容如下:
import tree
print('种下一棵果树。')
tree.fruit_name = 'apple'
print('等啊等,树长大了,可以收获了!')
fruits = tree.harvest()
print(fruits)
代码中,
import tree
将 tree.py
模块导入进来(使用 import
导入时不需要写 .py
后缀);tree
模块后,就可以使用其中的变量和函数了。将 tree.fruit_name
设置为 apple,调用 tree.harvest()
来收获 apple。执行下模块 farmer.py
看看:
➜ ~ python3 farmer.py 种下一棵果树。 等啊等,树长大了,可以收获了! [‘apple’, ‘apple’, ‘apple’, ‘apple’]
说明:apple 随机出现 1~9 个,所以你的结果可能和这里不一样。
可以看到,执行 farmer.py
时,由于 tree.py
模块被导入,所以可以在 farmer.py
中使用 tree.py
的内容。
标准库模块的导入
上面的例子中,我们自己定义了模块,然后在其它模块中使用它。其中有个地方不知道你有没有注意到,tree.py
的第一行代码是 import random
,random
并不是我们所定义的模块,那它是从哪里来的呢?
random
是标准库中的一个模块。标准库是由 Python 官方开发的代码库,和解释器一起打包分发,其中包含非常多实用的模块,我们在使用时直接 import
进来即可。
刚才我们用这种方式来执行模块:
python3 模块文件名
其实我们还可以进一步将参数传递到模块中去,像这样:
python3 模块文件名 参数1 ...参数n
参数传递到模块中以后,我们可以通过 sys
模块来取出这些参数,参数放在 sys.argv
列表中:
import sys
模块文件名 = sys.argv[0]
参数1 = sys.argv[1]
参数N = sys.argv[N]
首先需要导入 sys
模块,这是个标准库中的模块。sys.argv
是个列表,执行模块时被传递进来的参数保存在其中,它的列表项分别为:
sys.argv[0]
保存当前被执行模块的文件名sys.argv[1]
保存第 1 个参数sys.argv[2]
保存第 2 个参数之前种果树那个例子中,farmer.py
固定种苹果树,我们可以改进一下,具体种什么树由传递的模块参数来决定。
修改 farmer.py
的代码,内容如下:
import sys # 新增
import tree
print('种下一棵果树。')
tree.fruit_name = sys.argv[1] # 将 'apple' 改为 参数 sys.argv[1]
print('等啊等,树长大了,可以收获了!')
fruits = tree.harvest()
print(fruits)
以「banana」为例执行下看看:
➜ ~ python3 farmer.py banana 种下一棵果树。 等啊等,树长大了,可以收获了! [‘banana’, ‘banana’, ‘banana’]
在这个例子中 sys.argv
的值是:
sys.argv[0]
: farmer.pysys.argv[1]
: banana之前我们将定义的两个模块放在同一目录下,然后通过 import
语句来相互引用,这是一种扁平的模块组织结构,当模块数量很大的时候就很不灵活了,也难以维护。
Python 中可以用文件树这样的树形结构来组织模块,这种组织形式下的模块集合称为包(Package)。
比如包的结构可以是这样的:
包/
├── __init__.py
├── 模块1.py
├── 模块2.py
├── 子包1/
├── __init__.py
├── 模块3.py
└── 模块4.py
└── 子包2/
├── __init__.py
├── 模块5.py
└── 孙子包1/
├── __init__.py
└── 模块6.py
这是个很明显的层级结构——包里面包含子包、子包包含孙子包…… 单独将子包或孙子包拿出来,它们也是包。
包的存在形式是目录,模块的存在形式是目录下的文件。所以我们可以很容易地构造出这样一个包,只要在文件系统中创建相应的目录和文件即可。
需要注意的是,每个层级的包下都需要有一个 __init__.py
模块。这是因为只有当目录中存在 __init__.py
时,Python 才会把这个目录当作包。
导入包中模块的方法是:
import 包.子包.模块
从最顶层的包开始依次向下引用子包,直至目标模块。
如,从上面示例的包结构中,
导入 模块1.py
,使用:
import 包.模块1
导入 模块3.py
,使用:
import 包.子包1.模块3
导入 模块6.py
,使用:
import 包.子包2.孙子包1.模块6
模块的存在是为了更好的组织代码。将不同功能的代码分散在不同模块中,清晰地划分出各个模块的职责,有利于使用和维护代码,同时也可避免模块中的内容过长。
包的存在是为了更好的组织模块。与模块同理,包在更高的抽象层次上组织着代码。
模块可以更好的组织代码,它的存在形式是文件。包的可以更好的组织模块,它的存在形式是目录。
导入模块使用 import
语句:
import 模块名
导入包下的模块:
import 包名.模块名
模块导入后,可以使用该模块中所定义的名字(变量、函数类)。方式如下:
模块名.变量
模块名.函数
模块名.类
学习编程一定要多加练习,只靠单纯地阅读是无法真正掌握编程方法的,只有反复练习才能真正领悟编程思想。我们已经学习了一些 Python 知识了,说多不多说少也不少,是时候来运用一下了。
开始之前我们先来聊聊账号的话题。当今互联网十分普及,大家一定注册了很多 APP 和网站吧,大大小小的账号少则十几个多则可能数十个。大家的密码是都怎么设置的呢,所有账号用的是同一个密码吗?
所有账号用同一个密码是件很危险的事,一个平台上的账号泄漏了,有可能殃及其它平台。安全的做法是每个平台使用单独的密码,并且密码间的关联性尽可能的小,这样就算一个密码泄漏了也不会将影响扩大。
每个平台都使用一个单独的密码,并且密码间的关联性尽要可能的小,那十几个甚至几十个平台的密码要怎么来取呢?我们可以用密码自动生成器呀,现在就来动手做一个!
我们对密码生成器的要求是:
要求有了,怎么来实现呢?
实现方法非常多,不同的人有不同的思路。在这里我们一起来分析吧。
N - 4
个字符就依次放任意的字符。random.randint()
函数,它可以随机生成一个数字,我们就将这个随机数字当作索引去字符集合中取值(字符集合可以是 str
或 list
形式),这样就达到了随机从字符集合中取字符的目的。input()
即可。经过刚才的分析,我们可以将这个程序划分为三个主要部分:
这三个部分各司其职,共同构成我们的密码生成器。
我们完全可以只用我们之前学习过的那些知识,来实现这三个部分,并完整地构建整个程序。
命令行交互部分的实现
程序被执行后,首先给出提示信息要求用户指定密码长度,然后接收用户所输入的值,并判断值是否符合要求。
实现如下:
password_length = input('请输入密码长度(8~20):')
password_length = int(password_length)
if password_length < 8 or password_length > 20:
raise ValueError('密码长度不符')
获取到密码长度以后,就该使用「密码逻辑部分」来进一步完成工作,在这里我们把「密码逻辑部分」封装成一个函数,只需调用它就可以获取到想要的密码。也就是下面代码中的 generate_password()
函数。
password = generate_password(password_length)
print(password)
对于「命令行交互部分」而言,它不需要知道「密码逻辑部分」中的实现细节,只要调用「密码逻辑部分」能获取到密码就足够了。
密码逻辑部分的实现
「密码逻辑部分」是一个函数,它以密码长度作为参数,返回我们所要的随机密码。
它生成密码的策略是,先随机生成一个大写字母,以此作为起始密码;再生成一小写字母,追加到密码末尾;再生成一个数字,追加到密码末尾;再生成一个特殊字符,追加到密码末尾。这样就拥有 4 位密码了,且满足包含大写字母、小写字母、数字、特殊字符的要求。密码剩余的几位,依次随机取任意字符并追加到密码末尾。
上述生成随机字符的功能将由「随机字符生成部分」提供,我们将「随机字符生成部分」封装成 RandomChar
类,并单独放置在 randomchar
模块中。使用 RandomChar
类对象的方法即可获取随机字符:
random_char.uppercase()
random_char.lowercase()
random_char.digit()
random_char.special()
random_char.anyone()
无需在这个层面上关心 RandomChar
类对象是怎么做到获取随机字符的,对当前这个部分来讲这并不重要,重要的是如何运用其它部分的能力来达到当前部分的目的。
我们来实现整个「密码逻辑部分」:
def generate_password(length):
if length < 4:
raise ValueError('密码至少为 4 位')
random_char = randomchar.RandomChar()
password = random_char.uppercase() # 用一个随机的大写字符作为起始密码
password += random_char.lowercase() # 将一个随机的小写字符拼接在密码末尾
password += random_char.digit() # 将一个随机的数字拼接在密码末尾
password += random_char.special() # 将一个随机的特殊字符拼接在密码末尾
count = 5 # 此时的密码长度为 4,再向后拼接要从第 5 位开始,所以 count 为 5。
while count <= length: # 如果 count 大于密码长度则退出循环
password += random_char.anyone() # 随机取出一个字符拼接在密码末尾
count += 1
return password
上面代码中以 #
号开头的代码,称为注释,如 # 用一个随机的大写字符作为起始密码
。注释用于对代码作注解,只是写给代码阅读者看的,并不会被解释器执行。注释的范围是 #
及其之后的该行的所有字符。
随机字符生成部分的实现
「随机字符生成部分」被封装成 RandomChar
类,并单独放置在 randomchar
模块中,使用它的对象方法即可获取随机字符。
我们需要先准备好各类字符的完整集合,这里采用字符串的形式存放:
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz'
'0123456789'
'~!@#$%^&*'
可以把这些字符串分别保存在对象属性中:
class RandomChar:
def __init__(self):
self.all_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
self.all_lowercase = 'abcdefghijklmnopqrstuvwxyz'
self.all_digits = '0123456789'
self.all_specials = '~!@#$%^&*'
再来准备一个方法 pick_random_item()
,这个方法接受一个字符串作为参数,随机返回这个字符串中的一个字符。其内部可以使用 random.randint()
随机生成一个数字,然后把这个随机数字当作索引去字符串中取值,以此生成随机字符。
pick_random_item()
方法实现如下:
def pick_random_item(self, sequence):
random_int = random.randint(0, len(sequence) - 1) # 调用 random.randint() 生成一个随机数字作为索引去字符串中取值,因为随机生成的数字不可超过字符串长度,所以取值范围为 0, len(sequence) - 1。
return sequence[random_int]
有了上面这个从任意字符串中随机取值的功能,我们就可以把它应用到大写字母、小写字母、数字、特殊字符的集合(字符串形式)中去,这样就可以随机获取这四种字符了。
分别对应四个方法:
def uppercase(self):
return self.pick_random_item(self.all_uppercase) # 调用 pick_random_item 随机从 all_uppercase 字符串中取出一个大写字母
def lowercase(self):
return self.pick_random_item(self.all_lowercase) # 调用 pick_random_item 随机从 all_lowercase 字符串中取出一个小写字母
def digit(self):
return self.pick_random_item(self.all_digits) # 调用 pick_random_item 随机从 all_digits 字符串中取出一个数字
def special(self):
return self.pick_random_item(self.all_specials) # 调用 pick_random_item 随机从 all_specials 字符串中取出一个特殊字符
最后还有需要一个不区分上述字符种类,随机取任意字符的对象方法。
我们可以把大写字母、小写字母、数字、特殊字符的集合拼接在一起,形成一个更大的集合,然后随机从中取值。
可随机取任意字符的 anyone()
方法如下:
def anyone(self):
# 将四种字符拼接在一起,形成一个大字符串 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*',然后调用 pick_random_item 方法从中随机取出一个字符。
return self.pick_random_item(self.all_uppercase + self.all_lowercase + self.all_digits + self.all_specials)
至此就全部实现完了,大功告成!
整个程序的调用链是:「命令行交互部分」->「密码逻辑部分」->「随机字符生成部分」。每一个部分各司其职,共同完成这个程序。
我们的代码位于两个模块中。
「命令行交互部分」和「密码逻辑部分」位于 password_generator.py
模块,完整代码如下:
password_generator.py
import randomchar
def generate_password(length):
if length < 4:
raise ValueError('密码至少为 4 位')
random_char = randomchar.RandomChar()
password = random_char.uppercase()
password += random_char.lowercase()
password += random_char.digit()
password += random_char.special()
count = 5
while count <= length:
password += random_char.anyone()
count += 1
return password
password_length = input('请输入密码长度(8~20):')
password_length = int(password_length)
if password_length < 8 or password_length > 20:
raise ValueError('密码长度不符')
password = generate_password(password_length)
print(password)
「随机字符生成部分」位于 randomchar.py
模块,完整代码如下:
randomchar.py
import random
class RandomChar:
def __init__(self):
self.all_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
self.all_lowercase = 'abcdefghijklmnopqrstuvwxyz'
self.all_digits = '0123456789'
self.all_specials = '~!@#$%^&*'
def pick_random_item(self, sequence):
random_int = random.randint(0, len(sequence) - 1)
return sequence[random_int]
def uppercase(self):
return self.pick_random_item(self.all_uppercase)
def lowercase(self):
return self.pick_random_item(self.all_lowercase)
def digit(self):
return self.pick_random_item(self.all_digits)
def special(self):
return self.pick_random_item(self.all_specials)
def anyone(self):
return self.pick_random_item(self.all_uppercase + self.all_lowercase + self.all_digits + self.all_specials)
来执行一下程序看看:
➜ ~ python3 password_generator.py 请输入密码长度(8~20):16 Aw6~8a3$AeAo4kSN
为了可以仅利用之前学过的知识来实现这个程序,这里放弃了一些更简洁或更恰当的 Python 用法。比如
while
循环,可以使用 for _ in range(x)
的方式替代random.choice()
函数替代RandomChar
中的对象属性和对象方法,可直接定义成类属性和类方法string
模块即可获取,如 string.ascii_uppercase
大家若有兴趣可以自己改进这个程序。
高级的用法和概念将会在之后章节中介绍,不过值得一提的是,朴素的方法也是有价值的!