1.1 无输入
1.1.1 Python 变量和常量以及它们的命名规则
变量
在了解 Python 的变量之前,我们首先需要明白什么是变量。假设现在我们有以下 Python 代码:
variable = 1
其中=
表示的是赋值运算符,每个变量需要先赋值再使用,变量在被赋值之后才会被创建。=
前面表示的就是变量,variable 表示的就是变量名称,1 表示的是变量的值。在程序运行过程中,变量的值一般都会发生改变,内存中会专门开辟一段空间,用来存放变量的值,而变量名将指向这个值所在的内存空间,如图1所示。
图1
Python 中大小写是敏感的,所以 variable 和 VARIABLE 是两个不同的变量。
命名规范
变量是标识符的一种,而变量的命名规范也就是标识符的命名规范。 在选择标识符时,我们需要注意以下几点:
标识符只能是字母、数字或下划线的任意组合;
标识符的第一个字符不能是数字; 以下都是合法变量名:
variable_a _variable a_variable_1
以下都是非法变量名:
1variable var?iable vari\able
以下关键字不能声明为变量名:
关键字是Python语言已经定义好、具有特定含义的标识符,不允许开发者自己定义和关键字名字相同的标识符。
'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'
常量
与变量相对的就是常量,在程序运行过程中,常量的值不会发生改变。但是在 Python 中没有专门定义常量的语法。
整型常量示例:1
,5
,-10
浮点型常量示例:1.5
,2.3014
,-10.55
1.1.2 代码注释
代码注释分为单行注释和多行注释,单行注释通过 #
实现,而多行注释通过 '''...'''
或者"""..."""
实现。在代码运行过程中,会跳过注释部分。具体实现如下列代码所示:
# 这是单行注释 """这是多行注释这是多行注释"""
1.1.3 整型和浮点型数据
在 Python 中定义变量的时候,我们通常需要给变量赋值,而这个值也分为不同的类型,下方我们介绍了其中的整形和浮点型:
整型(int)
int_num = 1 # 将整型常量 1 赋值给变量 int_num
浮点型(float)
float_num = 0.5 # 将浮点型常量 0.5 赋值给变量 float_num
1.1.4 算术表达式
在代码中我们经常需要用到一些基本的算术运算,下面我们通过一个表格来给大家介绍 Python 中的常用运算符。
下表中 a = 10,b = 21,x = 5.2,y = 2.5
基本算术运算符表
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 - 两个对象相加 | a + b 输出结果 31 x + y 输出结果 7.7 |
- | 减 - 得到负数或是一个数减去另一个数 | a - b 输出结果 -11 x - y 输出结果 2.7 |
* | 乘 - 两个数相乘 | a * b 输出结果 210 x * y 输出结果 13.0 |
/ | 除 - x 除以 y | b / a 输出结果 2.1 x / y 输出结果 2.08 |
% | 取余 - 返回除法的余数 | b % a 输出结果 10 y % x 输出结果 2.5 |
** | 幂 - 返回 x 的 y 次幂 | ab 为 10 的 21 次方 xy 为 5.2 的 2.5 次方 |
// | 整除 - 向下取接近商的整数 | 9 // 2 的结果为 4 5.2 // 2.5 的结果为 2.0 |
1.1.5 打印输出
在 Python 中,如果我们想将结果输出到控制台(控制台是一个用来提供字符输入或者输出的接口),我们可以使用 print 函数来实现,使用方法如下列代码所示:
a = 1 # 将整型常量 1 赋值给变量 a b = 2.5 # 将浮点型常量 2.5 赋值给变量 b print(a) # 输出变量 a 的值 print(b) # 输出变量 b 的值
输出:
1 2.5
1.2 有输入格式
1.2.1 字符串类型的基本使用
在上一个学习类型的实训中,我们介绍了 Python 的整型和浮点型,而本实训介绍的是 Python 中最常用的数据类型 —— 字符串类型。
我们可以使用引号( ‘ 或 “ )来创建字符串,如下列代码所示。
string = "hello world" # "hello world"是一个字符串常量,将其赋给字符串变量str
字符串也可以进行特定的加法和特定的乘法运算:
str1 = "abc" # 定义一个字符串变量str1,值为"abc" str2 = "def" # 定义一个字符串变量str2,值为"def" # 加法运算:只能进行字符串与字符串的加法 add_result = str1 + str2 # 将str2的字符串拼接到str1后面,将得到的新字符串赋值给add_result print(add_result) # 这一步的输出为 "abcdef" # 乘法运算:只能进行字符串与整型数据的乘法 mul_result = str1 * 3 # 将str1的字符串复制两份,将复制的两份拼接在str1后面 print(mul_result) # 这一步的输出为 "abcabcabc"
小贴士:将 str1 字符串乘以 2 等价于
str1 + str1
。
1.2.2 数据类型转换
Python 中不同的数据类型可以进行转换,我们之前介绍过整型、浮点型和字符串三种数据类型,下面我们通过代码来了解如何进行数据类型转换。
整型转换为浮点型和字符串
# 定义一个整型变量 int_num,值为1 int_num = 1 # 使用 float 函数将 int_num 转换为浮点型,并赋值给 float_num float_num = float(int_num) print(float_num) # 输出的值为 1.0# 使用 str 函数将 int_num 转换为字符串,并赋值给 str_num str_num = str(int_num) print(str_num) # 输出的值为 "1"
浮点型转换为整型和字符串
# 定义一个浮点型变量 float_num,值为2.8 float_num = 2.8 # 使用 int 函数将 float_num 转换为整型,并赋值给 int_num int_num = int(float_num) print(int_num) # 输出的值为 2 # 使用 str 函数将 float_num 转换为字符串,并赋值给 str_num str_num = str(int_num) print(str_num) # 输出的值为 "2.8"
字符串转换为整型和浮点型
字符串转换为整型和浮点型时,只有字符串值为数字时才能转换,否则代码运行时将报错。
# 定义一个字符串变量 str_num,值为"2.1" str_num = "2.1" # 如果 str_num = "abc",则下列代码会报错# 使用 int 函数将 str_num 转换为整型,并赋值给 int_num int_num = int(str_num)print(int_num) #输出的值为 2# 使用 float 函数将 str_num 转换为浮点型,并赋值给 float_num float_num = float(str_num) print(float_num) # 输出的值为 2.1
Python 中自带了一个可以查看数据类型的函数 type,它的使用方法如下。
int_num = 1 # 定义一个整型变量int_num,值为1 # 使用type函数查看int_num的数据类型,并将函数的返回结果赋值给type_value type_value = type(int_num) print(type_value)
输出:
<class 'int'>
1.2.3 input 的使用
当我们想要从键盘获取某个值时,我们可以使用 input 函数来实现,input 函数得到的值默认是字符串类型,如下图 1 的 gif 所示。
如果我们需要使用 input 函数获取非字符串类型的数据,例如整型、浮点型数据,则需要对获取的字符串类型的数据进行数据类型转换。
图 1
1.2.4 print 的格式化输出
在之前的学习类型实训中,我们简单介绍了 print 函数的基本使用,假设我们想要输出更清晰的信息,例如“正方形的面积为100平方米。”,就可以使用 print 函数的格式化输出方式。
我们通过几个个简单的例子来介绍格式化输出的方式。
area = 10 # 定义一个变量area, 值为 10 print("正方形的面积为%d平方米。"% area)
上面的 print 语句中引号内的内容为一个待打印的字符串,其中 %d 是一个格式化符号,表示在此处打印一个整型数据,该数据的具体取值由引号右侧的百分号后面的数据决定,此语句中对应的是变量area的值。 输出:
正方形的面积为10平方米。
整型数据、浮点型数据和字符串对应的格式化符号分别是:%d
、%f
、%s
shape = "长方形" # 定义一个字符串变量shape area = 10 # 定义一个变量area, 值为 10 print("%s的面积为%d平方米。" % (shape,area)) # 其中shape对应的是%s,area对应的是%d
输出:
长方形的面积为10平方米。
如果在字符串中给的是 %d 和 %f,而传入 print 函数的值为字符串时,代码运行时则会报错。
shape = "长方形" # 定义一个字符串变量shape area = 2/3 # 定义一个变量area,表示长方形的面积 print("%s的面积为%f平方米。" % (shape,area)) # 其中shape对应的是%s,area对应的是%f
输出:
长方形的面积为0.666667平方米。
%f 会将结果四舍五入后保存到小数点后六位。
那假如我们只想要保留两位小数又该如何做呢?%.nf 可以设置小数点后保留的位数(n 表示保留的位数):
shape = "长方形" # 定义一个字符串变量shape area = 2/3 # 定义一个变量area,表示长方形的面积 print("%s的面积为%.2f平方米。" % (shape,area)) # 其中shape对应的是%s,area对应的是%.2f,表示四舍五入后保留小数点后两位数字
输出:
长方形的面积为0.67平方米。
1.3 内置函数与数学函数
1.3.1 数学函数
模块的导入方法
导入模块是为了调用定义在其他文件中的变量、函数或者类(后面的实训会具体说明什么是类和函数)。Python 中内置了一个数学相关的模块,模块名为 math,本实训将通过该模块来介绍三种模块的导入方法。
import 语句
在之前的练习实训中,我们求平方根是通过x**0.5
实现的,其实我们还可以通过调用 math 模块中的 sqrt 函数实现。
import math # 导入Python的内置库math,并导入了该模块内的所有内容 num = 4 value = math.sqrt(num) # 调用math模块中的sqrt函数对num的值求平方根 print(value) # 结果为2
>小贴士:不管你执行多少次 import,一个模块只会被导入一次。
from … import * 语句
from ... import *
语句也会将模块中的所有内容都导入到当前的空间,我们可以在当前代码文件中调用该模块的所有变量、函数和类,不用再写出模块名。
from math import * # 导入math模块中的所有变量、函数和类 num = 4 num1 = pi # pi是math模块中的数学常量,表示圆周率 value = sqrt(num) # 调用math模块中的sqrt函数对num的值求平方根,不需要再写出模块名 print(value) # 输出结果为2.0 print(num1) # 输出结果为3.141592653589793
from … import 语句
上面个两种方法会导入模块中的所有内容,会非常占用内存,而from ... import
这种方法,我们可以只导入模块中的一个或多个函数。我们同样以 math 模块为例子。
from math import sqrt # 导入math中的sqrt函数,这样就只能调用sqrt方法 num = 4 value = sqrt(num) # 调用math模块中的sqrt函数对num的值求平方根,不需要再写出模块名 print(value) # 结果为2.0
常用数学函数
表 1 中介绍了 math 模块中常用的几个数学函数。
表 1 常用数学函数表(部分)
函数 | 使用说明 |
---|---|
exp(x) | 返回 ex |
expm1(x) | 返回 ex-1 |
log(x[,base]) | 返回 x 的以 base 为底的对数,base 默认为 e |
log2(x) | 返回 x 基数为 2 的对数。通常比log(x,2)更准确 |
log10(x) | 返回 x 基数为 10 的对数。通常比 log(x,10) 更准确 |
pow(x,y) | 返回 x 的 y 次方,即 x**y |
sqrt(x) | 返回 x 的平方根 |
1.3.2 内置函数
bin 函数
bin 函数是 Python 的内置函数(内置函数是 Python 自带的函数,不需要从其他模块导入)之一,它的作用是将整型数据转换为二进制数据。
num = 4 print(bin(num)) # 打印整型数字 4 的二进制形式型
输出:
0b100 # 0b表示二进制,100表示4的二进制形式
> 小贴士:bin 函数返回的是一个字符串,不能直接对返回值进行计算。
参数不仅可以接受十进制整数,八进制、十六进制也是可以的,只要是 int 型数据就合法。
num1 = bin(0b10010) # 0b表示二进制,这一步是二进制转二进制,显然结果是不会变的 num2 = bin(0o10) # 0o表示八进制 num3 = bin(0x2d) # 0x表示十六进制 print(num1) print(num2) print(num3)
输出:
0b10010 0b1000 0b101101
oct 函数
oct 函数是将一个整型数据转换成八进制,它和 bin 函数的用法一致。
num = 10 print(oct(num)) # 打印整型数字 10 的八进制形式型
输出:
0o12 # 0o表示八进制,12表示10的八进制形式
参数也可以接受二进制整数、十六进制数,只要是 int 型数据就合法。
num1 = oct(0b10010) # 0b表示二进制 num2 = oct(4) # 4是十进制数 num3 = oct(0x2d9) # 0x表示十六进制 print(num1) print(num2) print(num3)
输出:
0o22 0o4 0o1331
hex 函数
hex 函数是将一个整型数据转换成十六进制,它和 bin 函数的用法一致。
num = 10 print(hex(num)) # 打印整型数字 10 的十六进制形式型
输出:
0xa # 0x表示十六进制,a表示10的十六进制形式
参数也可以接受二进制整数、八进制数,只要是 int 型数据就合法。
num1 = hex(0b10010) # 0b表示二进制 num2 = hex(0o4) # 0o表示八进制 num3 = hex(18) # 18表示十进制数 print(num1) print(num2) print(num3)
输出:
0x12 0x4 0x12
int 函数
在之前的实训中,我们了解到 int 函数可以将部分非整型的数据转换为整型,除此之外,它还可以在传一个参数来表示进制数。
num1 = int("0b1101", 2) # “0b1101”表示字符串,当传入参数2时,字符串的内容就会转换成二进制,如果无法转换成二进制,则代码运行时报错 num2 = int("0o11", 8) # 转换成八进制 num3 = int("0x2b", 16) # 转换成十六进制 print(num1) print(num2) print(num3)
代码运行结果:
13 9 43
返回的结果都是十进制数。 在了解了 int 函数的第二个参数后,我们就可以结合 input 函数来获取不同进制数了。如图 1 所示。
图1
2.1 单路分支
2.1.1 布尔类型
布尔类型是 Python 的基本数据类型之一,布尔类型的数据常量只有 True 和 False,分别表示真和假。Python 中定义了一个 bool 函数来将其他数据类型的数据转换成布尔型。
num1 = 0 num2 = 2 print(bool(num1)) # 打印num1的布尔值 print(bool(num2)) # 打印num2的布尔值
代码运行结果:
False True
在整型和浮点型数据中,只有数据值为 0 时,转换成布尔类型才会是 False,其它值的转换结果都是 True 。
当数据类型为字符串时:
str1 = "" # 定义了一个空字符串 str2 = "hello world" print(bool(str1)) print(bool(str2))
代码运行结果:
False True
只有字符串为空时,布尔值才会为 False,否则都为 True。
2.1.2 关系运算符
关系运算符,顾名思义是用来比较两个值的关系的运算符。
我们以表格的形式来详细介绍关系运算符,假设表格中的a = 10 ,b = 20
,x 和 y 表示任意两个数值。
表 1 关系运算符表
运算符 | 描述 | 实例 |
---|---|---|
== | 等于 - 比较对象是否相等 | (a == b) 返回 False。 |
!= | 不等于 - 比较两个对象是否不相等 | (a != b) 返回 True。 |
> | 大于 - 返回 x 是否大于 y | (a > b) 返回 False。 |
< | 小于 - 返回 x 是否小于 y。 | (a < b) 返回 True。 |
>= | 大于等于 - 返回 x 是否大于等于 y。 | (a >= b) 返回 False。 |
<= | 小于等于 - 返回 x 是否小于等于 y。 | (a <= b) 返回 True。 |
>小贴士:所有比较运算符返回 1 表示真,返回 0 表示假。这分别与特殊的变量 True 和 False 等价。注意这些变量名首字母大写。
之前的学习中,我们了解了赋值运算符、算术运算符和关系运算符,当一个执行语句中,同时有这三个运算符时,它们的优先级为算术运算符>关系运算符>赋值运算符。
2.1.3 单路分支结构 if
假设我们现在要让计算机判断一个整型数值是正数还是负数,该怎么编写代码实现呢?这时我们就可以使用 Python 中的单路分支结构 if 语句。语句格式如下:
if 表达式: 执行语句
if 语句的运行流程图如下图 1。
图1
已知 i 为 -1,如果 i 小于 0,则输出 “i为负数”,否则直接结束。
代码实现如下:
i = -1 # i<0是通过关系运算符得到的一个布尔类型的值,if语句后要以英文冒号结尾 if i < 0: # 判断语句为True时才会运行的代码 print("i 为负数") # if 语句内的代码需要进行缩进操作,也就是代码最前方要有4个空格 # i<0返回的结果是False,所以这个if语句内的代码不运行 if i < 0: print("i 为正数")
代码运行结果:
i 为负数
如果我们想在周末天气好的时候出去玩,那么我们应该怎么来通过代码判断呢?这时就可以使用 if 语句进行多重嵌套,如以下代码所示。
if today == "周末": if weather == "晴天": # 每一个if之后都要进行一次缩进操作 print("出去玩") ...
>小贴士:if 后的表达式可直接为整型、浮点型和字符串类型的数据。
2.2 双路分支
2.2.1 双路分支结构if-else
在上一个学习类型的实训中,我们介绍了单路分支结构的 if 语句。但是单路分支结构的 if 语句在判断某个数是否为正数时,要用到两个 if 语句,非常的不方便。双路分支结构 if-else 就能完美的解决这个问题。if-else 的语句格式如下:
if 表达式: 执行语句1 else: 执行语句2
双路分支结构 if-else 的流程图如图 1 所示。
图 1
已知 i 为 -1,如果 i 小于 0,则输出 “i为负数”,如果 i 不小于 0,则输出“i不是负数”。 代码实现:
i = -1 if i > 0: print("i为负数") else: print("i不是负数")
执行结果:
i不是负数
这段代码通俗易懂的解释为:如果 i 大于 0,则输出”i为负数”,否则输出”i不是负数”。
2.2.1 逻辑运算符
Python 中的逻辑运算符有三个,如表 1 所示(其中的a=10,b=20
)。
表 1 逻辑运算符
运算符 | 逻辑表达式 | 描述 | 实例 |
---|---|---|---|
and | x and y | 布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。 | (a and b) 返回 20。 |
or | x or y | 布尔"或" - 如果 x 是 True,它返回 x 的值,否则它返回 y 的计算值。 | (a or b) 返回 10。 |
not | not x | 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 | not(a and b) 返回 False |
三种逻辑运算符的真值表如表 2 所示:
表 2 真值表(1 为 True,0 为 False)
x | y | x and y | x or y | not x | not y |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 0 | 1 |
0 | 1 | 0 | 1 | 1 | 0 |
2.3 多路分支
2.3.1 多路分支结构 if-elif-else
当我们想判断一个数是正数或负数还是 0 时,普通的单路分支要写两次 if-else ,非常麻烦,这时我们就可以使用多路分支结构,与双路分支结构 if-else 相比,多路分支结构多出了 elif,接下来我们通过代码来理解多路分支结构 if-elif-else 的作用。 if-elif-else 的用法:
if 表达式A: print("A成立") elif 表达式B: # 如果条件A不成立,则运行elif内的代码 print("B成立") else: # 如果条件A和B都不成立,则运行else内的代码 print("C成立")
这段代码的解释为:如果条件 A 成立,那么输出“A成立”;如果条件 B 成立,那么输出“B成立”,否则输出“C成立”。
实际应用:
num = 1 if num > 0: print("num为正数") elif num < 0: print("num为负数") else: print("num为0")
代码运行结果:
num为正数
多路分支的优点在于它能使用多个 elif,如下列代码所示。
if 条件A: ··· elif 条件B: ··· elif 条件C: ··· ··· else: ···
3.1 while循环
3.1.1 while 循环
在之前的实训中,我们学习过 input 函数,我们可以使用它从键盘获取一次字符串值,假如我们要获取 100 次值时,难道要写 100 次 input 语句吗?这时我们就可以使用 while 循环。while 循环就是只要条件满足,就不断循环,条件不满足时退出循环。它的语句格式如下:
while 表达式: # 这个表达式结果必须是布尔类型或者能够转换为布尔类型 执行语句1 执行语句2 ···
while 循环的流程图如下图 1:
图1
当我们要获取 100 次键盘输入的值时,我们可以使用以下代码:
i = 0 while i < 100: # 如果i的值小于100则执行循环内的语句 input() i += 1
假设我们要计算 100 以内所有奇数之和,具体代码实现如下:
sum = 0 n = 99 while n > 0: # 当n>0时,则执行while循环内的语句 sum = sum + n n = n - 2 print(sum)
代码运行结果:
2500
while 循环注意事项:while 循环在给定表达式后,执行语句中必须给出能改变表达式结果的语句,否则 while 循环将进入死循环。我们通过代码来了解什么是无限循环。
i = 1 while i < 10: print("i=%s" % i)
上面代码的表达式为i<10
,但是执行语句中,并没有改变表达式结果的语句。所以这个 while 语句进入了死循环。想要避免死循环,在 while 循环中必须改变 i 的值。
i = 1 while i < 10: print("i=%s" % i) i += 1 # 等价于i = i+1
执行结果:
i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9
在 Python 中,循环结构也是可以嵌套的。但是我们使用 while 嵌套循环时,需要注意避免无限循环的情况出现。
while 表达式: 执行语句 while 表达式: 执行语句 ···
代码实现:
i = 1 while i < 4: n = 1 while n < 3: print("第二层循环") n += 1 print("第一层循环") i += 1
执行结果:
第二层循环 第二层循环 第一层循环 第二层循环 第二层循环 第一层循环 第二层循环 第二层循环 第一层循环
3.2 for循环
3.2.1 列表的基本概念
列表是最常用的 Python 数据类型之一。列表的数据项不需要具有相同的类型,创建一个列表,只要把逗号分隔的不同的数据项,使用方括号[]
括起来即可。 简单来说,列表是由一系列元素,按特定顺序排列组成。你可以创建包含字母表中所有字母、数字或一些字符串的列表;还可以将其他数据类型放入列表中,甚至可以将另一个列表放在列表中。
list1 = [1, 2, 3, 4] # 创建了一个列表,列表里的元素全部都为整型 list2 = [1, 2.5, "a", "hello", [1, "b"], True] # 列表里的元素可以为任意类型 print(type(list1)) # 打印list1的类型 print(list1) print(list2)
执行结果:
<class 'list'> [1, 2, 3, 4] [1, 2.5, 'a', 'hello', [1, 'b'], True]
访问列表内元素的方法和访问字符串的方式一样,都是通过下标来确定访问元素的位置。
list2 = [1, 2.5, "a", "hello", [1, "b"], True] value1 = list2[0] value2 = list2[4] print(value1) print(value2)
执行结果:
1 [1, 'b']
在之前学习的 len 函数也可以将列表作为参数,len 函数返回的是列表内的元素个数。
list2 = [1, 2.5, "a", "hello", [1, "b"], True] print(len(list2))
执行结果:
6
Python 中有一个类型转换函数 list,它可以将部分其他类型的数据转换为列表,例如字符串以及后面将学习到的元组,集合。
string = "hello" result = list(string) print(result)
执行结果:
['h', 'e', 'l', 'l', 'o']
3.2.2 range 函数的使用
range 函数可创建一个有序的整数序列。range 函数有三个参数:range([start,] stop[, step])
,方括号内[]
的内容表示可以省略,其中 start 表示有序序列开始的数,它是可以省略的,当不传 start 值时,它默认为 0,stop 表示结束的数(结束的值取不到,等价于区间的左闭右开),step 表示步长,step 参数的默认值为 1。
reslut = range(5) # range(5)的取值结果为一个0-4的有序序列 print(result)
执行结果:
<class 'range'>
可以看到 range 函数返回的并不是一个列表,当我们想查看 range 返回的有序序列时,不能直接使用赋值语句将它的返回结果赋给一个变量。如果我们想要查看 range 的结果,则需要用到类型转换函数 list 将其转换为列表。
# 传入一个参数 result1 = list(range(5)) # 取0-4的有序序列,等价于range(0, 5) # 传入两个参数 result2 = list(range(1,5)) # 取1-4的有序序列# 传入三个参数 result3 = list(range(1, 5, 2)) # 取1-4的有序序列,步长为2 print(result1) print(result2) print(result3)
执行结果:
[0, 1, 2, 3, 4] [1, 2, 3, 4] [1, 3]
3.2.3 for 循环语句
Python 的 for 语句格式如下所示,它将依次调取序列中的元素并将其分配给变量,然后执行一遍循环体中的所有执行语句,直到整个序列中的元素取完为止。
for 变量 in 序列: 执行语句 ···
for 循环的流程图如图 1 所示。
图 1
for 循环会依次遍历序列中的成员,执行循环语句。例如:
list1 = ['python','java','c','c++'] for book in list: print("当前书籍为:%s" % book)
执行结果:
当前书籍为:python 当前书籍为:java 当前书籍为:c 当前书籍为:c++
Python 中的 for 循环通常与 range 函数连用。
for num in range(5): print(num)
执行结果:
0 1 2 3 4
for 循环还有一些常用的小技巧。举个例子:
list1 = ['python','java','c','c++'] list2 = ['mysql','php','R','golang'] # len(list1)返回的是列表的长度,等价于range(4) for book in range(len(list1)): print(list2[book])
执行结果:
mysql php R golang
3.3 break与continue
3.3.1 break 语句
在之前学习 while 循环时,如果我们在执行语句中不改变表达式的结果,那么代码将进入无限循环;若是我们无法在执行语句中改变表达式结果呢?这时我们就可以使用 break 语句。break 语句是用于结束当前循环。
假设老师想求一个班上 5 名同学的平均成绩,分数都是通过 input 函数获取,分数的取值范围为 0-100,假设老师输入时不小心输入了一个超出范围的值,这时,我们就可以通过 break 语句来结束循环。
num = 0 sum_score = 0 while num < 5: score = int(input()) # 每循环一次就输入一个值 if score < 0 or score > 100: # 如果输入的分数小于0或大于100就退出循环 print("输入的数据不合法,请重新输入") break sum_score += score if num == 4: # num=4的时候表示分数都输入完成,可以计算平均分 mean_value = sum_score/5 print("平均成绩为",mean_value) num += 1
输入数据1:
50 66 906 100 88
执行结果1:
输入的数据不合法,请重新输入
输入数据2:
50 66 90 100 88
执行结果2:
平均成绩为 78.8
当发生循环嵌套时,break 语句只能结束当前循环。
num = 1 while num < 4: print("第一次循环") # 正常情况下,表达式为True,第二层循环会无限循环,但是break语句让第二层循环只执行了一次 while True: print("第二层循环") break num += 1
执行结果:
第一次循环 第二层循环 第一次循环 第二层循环 第一次循环 第二层循环
通过结果可以得知,break 每次都是结束当前的第二次循环,并没有结束它的上一级循环。
for 循环中也可以使用 break 语句,它也只能结束当前的 for 循环。
for x in range(5): if x > 2: break print(x)
执行结果:
0 1 2
3.3.2 continue 语句
while 循环和 for 循环都可以使用 continue 语句,这个语句的作用是结束本次循环,重新开始下一次循环。 以下场景便模拟了 for 循环结构跳出当前循环的现实场景:
全班同学的试卷为一个序列,老师在批阅一个班同学的试卷时,需要从第一个同学开始一个一个批阅,然后根据每个同学的具体答卷情况给出最后得分。如果评阅到某张试卷时发现这位同学缺考,为空白卷,那么就不评阅这张试卷,直接评阅下一张。当全部同学的试卷都评阅完毕时,结束评阅,跳出循环。跳过评阅空白卷的过程就相当于使用了 continue 语句。
continue 语句与 break 语句使用方法一致,但是作用不同,continue 的基本形式为:
for 变量 in 序列: 执行语句 if 表达式: continue
我们通过代码来实现上述场景,假设班上有 5 个同学,我们用 0 表示白卷:
student = [10, 50, 0, 62, 90] for stu in student: if stu == 0: # 如果stu等于0,跳出本次循环 print("白卷") continue print("分数为%d"% stu)
执行结果:
分数为10 分数为50 白卷 分数为62 分数为90
continue 语句同样无法结束循环,只能跳过本次循环。
num = 0 while num < 10: if num == 5: # 如果num等于5,跳出本次循环 num += 1 continue print(num) num += 1
执行结果:
0 1 2 3 4 6 7 8 9
当发生循环嵌套时,continue 只会跳过当前循环,而不会影响其他层的循环。
for x in range(2): for y in range(3): if y == 2: continue print("第二次循环第%d次" % y) print("第一次循环第%d次" % x)
执行结果:
第二次循环第0次 第二次循环第1次 第一次循环第0次 第二次循环第0次 第二次循环第1次 第一次循环第1次
从执行结果可以得知,第二层循环本来要运行 3 次,但是当 y 等于 2 时跳出了本次循环,但是并没有影响第一层循环。
注意:在 Python 中,break、continue不能直接使用,应和分支语句配合使用。
3.4 for...else
for-else 语句是 for 循环的一种,它和 break 语句一起使用时才能显示出 else 的作用。下面给出了两个使用 for-else 的例子及其执行结果。
for i in range(10): if i == 5: print( 'found it! i = %s' % i) else: print('not found it')
执行结果:
found it! i = 5 not found it
# 这段代码中,在i=5时使用了break结束了循环 for i in range(10): if i == 5: print( 'found it! i = %s' % i) break else: print('not found it')
执行结果:
found it! i = 5
从上述两段代码的运行结果可知,第一段代码并没有使用 break 语句,循环是完整的进行完了的,所以 else 语句中的代码运行了,而在第二段代码中,在 i=5 时使用了 break 结束了循环,所以 else 语句中的代码并没有执行。可以发现,如果循环在运行过程中被中断,则 else 语句不会执行。
4.1 列表
4.1.1 更新列表元素
列表内的元素都是通过下标来取值的,如果我们想要更新列表的元素,首先需要定位到这元素的位置才能更改它的值。
list1 = [1,2,3,4,5] list1[2] = 0 # 将list1的第3个值改为0 print(list1)
执行结果:
[1, 2, 0, 4, 5]
4.1.2 添加列表元素
添加列表元素主要有两个方法:1. append;2. insert
append
append(obj)方法用于在列表末尾添加新的元素,obj 表示要添加的元素。
list1 = [1,2,3,4,5] list1.append(6) # 在末尾添加一个int类型的元素6 list1.append("A") # 在末尾添加一个str类型的元素A print(list1)
执行结果:
[1, 2, 3, 4, 5, 6, 'A']
insert
insert(index, obj) 函数用于将指定元素插入列表的指定位置。index 表示下标,obj 表示要插入的元素。
list1 = [1,2,3,4,5] list1.insert(1,0) # 在下标为1的位置插入一个0 print(list1)
执行结果:
[1, 0, 2, 3, 4, 5]
4.1.3 删除列表元素
Python 中有两个方法来实现列表元素的删除,del 方法和 pop(index) 方法都可以删除列表中指定下标位置的元素,但是两者的调用方法不同,我们通过代码来查看两者的区别。
list1 = [1,2,3,4,5] del list1[0] # 删除list1中第0个元素,这一步没有返回值 print(list1)
执行结果:
[2, 3, 4, 5]
list1 = [1,2,3,4,5] value = list1.pop(0) # 删除list1中第0个元素,返回值为删除的元素 print(value) print(list1)
执行结果:
1 [2, 3, 4, 5]
4.1.4 其他列表函数
前面的实训中我们介绍列表函数中的 len 函数和 list 函数,下面介绍其他的函数功能:
max():返回列表元素最大值。元素类型必须全部为字符串或者全部为整型和浮点型。
list1 = [1,2,3,4,5] print(max(list1))
执行结果:
5
min():返回列表元素最小值。元素类型必须全部为字符串或者全部为整型和浮点型。
list1 = [1,2,3,4,5] print(min(list1))
执行结果:
1
sum():返回列表元素的和。元素类型必须全部为整型和浮点型。
list1 = [1,2,3,4,5] print(sum(list1))
执行结果:
15
4.1.5 其他常用列表方法
除了可以对列表进行增删改查操作,Python 列表还有一些常用的方法来对元素进行操作,下面介绍部分常用的列表方法:
reverse():将列表中的元素反向排列。
list1 = [1,2,3,4,5] list1.reverse() print(list1)
执行结果:
[5, 4, 3, 2, 1]
remove():用于移除列表中某个值的第一个匹配项。
list1 = [1,2,3,4,5] list1.remove(1) print(list1)
执行结果:
[2, 3, 4, 5]
index():用于从列表中找出某个值第一个匹配项的下标值。
list1 = ["a","b","c","d","e"] value = list1.index("b") print(value)
执行结果:
1
sort():对列表进行排序。元素类型必须全部为字符串或者全部为整型和浮点型。
list1 = ["d","b","e","a","c"] list2 = [5,3,2,1,4] list1.sort() list2.sort() print(list1) print(list2)
执行结果:
['a', 'b', 'c', 'd', 'e'] [1, 2, 3, 4, 5]
count():用于统计某个元素在列表中出现的次数。
list1 = ["x","b","b","z","c"] value = list1.count("b") print(value)
执行结果:
2
4.1.6 列表推导式
Python 中列表推导式用于使用其他可迭代对象创建一个新列表。可迭代对象表示可以使用 for 循环遍历的数据,例如列表、字符串,元组,字典,集合(元组、字典、集合会在后续实训中介绍)等。列表推导式的基本形式为[表达式 for 变量 in 可迭代对象]
,下面我举几个例子。
# 想得到1-10的平方组成的列表 list_1_10 = [x**2 for x in range(1,11)] print(list_1_10)
执行结果:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
这个列表推导式等价于下列代码:
list_1_10 = [] for x in range(1,11): y = x ** 2 list_1_10.append(y)
列表推导式也可以使用 if 语句:
# 想得到1-10中为偶数的平方组成的list example = [i**2 for i in range(1,11) if i%2 == 0 ] print(example)
执行结果:
[4, 16, 36, 64, 100]
这个列表推导式等价于下列代码:
example = [] for i in range(1,11): if i % 2 == 0: y = i ** 2lie example.append(y)
列表推导式还可以使用 for 循环嵌套:
# 想得到多重嵌套中的数是 2 的倍数的元素的平方组成的列表 example2 = [[1,2,3],[4,5,6],[7,8,9],[10]] example3 = [j**2 for i in example2 for j in i if j%2 == 0] print(example3)# 想得到多重嵌套的列表中,一重嵌套中列表长度大于 1 且列表中的元素为 2 的倍数的元素的平方组成的列表 example4 = [[1,2,3],[4,5,6],[7,8,9],[10]] exmaple5 = [j**2 for i in example2 if len(i)>1 for j in i if j%2 == 0] print(exmaple5)
执行结果:
[4, 16, 36, 64, 100] [4, 16, 36, 64]
从上述几个例子可以看出,如果你想通过 for 循环来创建列表时,完全可以使用列表推导式来替代。但是列表推导式中不能使用 while 循环。
4.1.7 列表的复制
在之前的实训中,我们做过列表复制相关的练习,在题目中,我们的要求是使用 for 循环实现列表复制,可能有同学就会发现,为什么不直接新建一个变量,将原列表赋值给这个变量呢?如下面代码所示:
list1 = [1,2,3,4] list2 = list1 print(list1) print(list2)
执行结果:
[1,2,3,4] [1,2,3,4]
从执行结果来看,确实是实现了列表的复制功能,但是实际上并没有实现列表的复制,如果对 list2 进行增删改操作时,list1也会发生相应的变化。
list1 = [1,2,3,4] list2 = list1 list2.append(5) print(list1) print(list2)
执行结果:
[1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
其实 list1 和 list2 指向的还是同一个内存地址,并没有真正的实现复制功能。如图 1 所示。
图1
4.1.8 浅拷贝
想要了解什么是浅拷贝,我们首先就需要列表嵌套的相关知识。在前面的实训中,我们可以了解到列表是可以嵌套的,列表内的元素还可以是列表。
list1 = [1,[2,3],4,5] # list1列表中的第二个元素也是一个列表,这就是一个嵌套列表
浅拷贝是复制列表的最外层,内存的嵌套的列表并不会复制。浅拷贝需要使用 copy 模块中的 copy 函数。
import copy list1 = [1,[2,3],4,5] list2 = copy.copy(list1) # 使用浅拷贝复制一个列表 list2.append(6) # list2末尾添加一个元素6 list2[1].append(6) # list2第二个元素的列表的末尾添加一个6 print(list1) print(list2)
执行结果:
[1, [2, 3, 6], 4, 5] [1, [2, 3, 6], 4, 5, 6]
从执行结果可以得知,两个列表的元素不一样了,但是列表中的嵌套列表还是一致的,浅拷贝只能拷贝表层,嵌套在列表内的列表并没有拷贝。
4.1.9 深拷贝
如果要彻底地产生一个完全独立的复制品,就需要使用深拷贝。深拷贝需要使用到 copy 模块中的 deepcopy 函数。
import copy list1 = [1,[2,3],4,5] list2 = copy.deepcopy(list1) # 使用深拷贝复制一个列表 list2.append(6) # list2末尾添加一个元素6 list2[1].append(6) # list2第二个元素的列表的末尾添加一个6 print(list1) print(list2)
执行结果:
[1, [2, 3], 4, 5] [1, [2, 3, 6], 4, 5, 6]
从执行结果可以看出,使用深拷贝复制出来的列表是两个完全互不影响的列表。
4.2 元组
在之前学习类型实训中,我们学习过列表以及它的性质,元组和列表相似,只不过定义的时候使用的不是[]
,而是()
,元组中的元素和列表一样,可以是任意数据类型的数据。
tup = (1,1.5,"a",[1,2,3,4],(1,2,3,4)) # 创建一个包含整型、浮点型、字符型、列表和元组类型数据的元组 value = tup[1] # 取值方式和列表一致,取元组中的第二个元素 print(type(tup)) print(value)
执行结果:
<class 'tuple'> 1.5
需要注意的是,元组中的任何元素都不可更改。
tup = (1,1.5,"a",[1,2,3,4],(1,2,3,4)) tup[1] = 2.4 # 想将元组中的第二个元素修改为2.4
执行结果会报错,并提示元组类型的数据不支持修改。但是元组内的可变类型的数据是可以更改的,我们可以发现元组 tup 中有一个列表类型的元素,这个列表类型的元素是可以更改的。
tup = (1,1.5,"a",[1,2,3,4],(1,2,3,4))# tup[3]得到的是元组中的第4个列表元素,tup[3][2]得到的是列表中的第三个元素,这一步是将元组中的第四个列表中的第三个元素修改该为0 tup[3][2] = 0 print(tup)
执行结果:
(1, 1.5, 'a', [1, 2, 0, 4], (1, 2, 3, 4))
还有一点需要注意,如果我们想要创建只有单个元素的数组时,需要在元素末尾加一个逗号。
tup = (1,) # 创建的是一个元组 num = (1) # 创建的是一个 int 类型的数据 print(type(tup)) print(type(num))
执行结果:
<class 'tuple'> <class 'int'>
其它类型的数据想要转换成元组时,可以使用 Python 中的 tuple 函数来进行转换。
list1 = (1,2,3,4) print(tuple(list1))
执行结果:
(1,2,3,4)
由于元组中的元素不可更改,所以能应用在元组上的方法相比列表就少了很多,主要有以下三个:
len():返回元组的长度
max():返回元组中最大的元素
min():返回元组中最小的元素
元组在 Python 中还有一个非常重要的应用,两个变量的值想交换时,可以使用以下代码:
a = 1 b = 2 a,b = b,a # 交换值,等价于 a,b = (b,a) print(a,b)
执行结果:
2 1
我们通过代码可以发现,元组不一定需要()
来表示,如果多个对象用逗号分隔,这样也能表示一个元组。
4.3 字典
字典是 Python 中非常重要的一种数据类型,相比列表和元组,它有许多不同之处。字典是使用{}
来表示的,字典类型的数据是一种典型的键值对结构,就好像一把钥匙只能开一把锁一样,是一一对应的,格式为{key1:value1,key2:value2}
。
dict1 = {'city':["Beijing","Shanghai","Guangzhou"],'info':'a beautiful country',1:2,(1,2):(1,2)} # 冒号前面的是字典的键,后面是字典的值 print(dict1["info"]) # 获取字典dict1中键为info的值
执行结果:
a beautiful country
从上面的例子可以得知,字典的键可以是字符串、整型、元组,这 3 种类型都是不可变的数据类型,而字典的值可以是任意数据类型,所以字典也是可以嵌套的,字典的值可以是字典。
dict1 = {"country":{"city":"Beijing"}}
字典类型还有以下几点注意事项:
字典的键值是唯一的,且键值是不可修改的;
如果同一个键被赋值两次,后一个值会被记住。
dict1 = {"city":"Beijing","city":"Shanghai"} print(dict1)
执行结果:
{'city': 'Shanghai'}
字典是可变类型的数据,它可以进行增删改操作。
dict1 = {"city":"Beijing"}# 增加 dict1["area"] = 10000 # []内的为键,10000为该键的值
执行结果:
{'city': 'Beijing', 'area': 10000}
删除操作:
dict1 = {"city":"Beijing","area":10000}# 删除 del dict1["area"] # 删除键为“area”的键值对 print(dict1) dict1.clear() # 清空字典 print(dict1) del dict1 # 删除字典,对应的变量也会清除
执行结果:
{'city': 'Beijing'} {}
修改操作:
dict1 = {"city":"Beijing","area":10000}# 修改dict1 dict1["city"] = "Shanghai" # 将dict1中键为city的值改为“Shanghai” print(dict1)
执行结果:
{'city': 'Shanghai', 'area': 10000}
字典还有部分常用的内置函数:
keys():获取字典的所有键,返回一个可迭代对象,可以使用 list() 方法将返回结果转换成列表。
dict1 = {"city":"Beijing","area":10000} print(dict1.keys())
执行结果:
dict_keys(['city', 'area'])
values():获取字典的所有值,返回一个可迭代对象,可以使用 list() 方法将返回结果转换成列表。
dict1 = {"city":"Beijing","area":10000} print(dict1.values())
执行结果:
dict_values(['Beijing', 10000])
items():获取字典的键和值,以列表嵌套元组的形式返回,该函数常用于 for 循环遍历字典。
dict1 = {"city":"Beijing","area":10000} print(dict1.items()) for x,y in dict1.items(): print(x) print(y)
执行结果:
dict_items([('city', 'Beijing'), ('area', 10000)]) city Beijing area 10000
其它类型的数据想要转换成字典类型相对而言比较复杂,需要使用到的是 dict 函数,下面给大家举两个常用的转换例子:
dict1 = dict(a="a",b=10,c=[1,2,3]) print(dict1) dict2 = dict([("a","a"),("b",10),("c",[1,2,3])]) print(dict2)
执行结果:
{'a': 'a', 'b': 10, 'c': [1, 2, 3]} {'a': 'a', 'b': 10, 'c': [1, 2, 3]}
4.4 集合
Python 中的集合是用于表示一个无序且不重复的序列。集合和字典相同,是使用{}
来表示的,但是集合并不是键值对。
# 集合内的元素只能是不可变类型的数据,列表和字典不能成为集合内的元素 agg = {1,"a",(1,2,3)} # 创建了一个集合 print(type(agg))
执行结果:
<class 'set'>
集合并不是不可变类型的数据,所以它可以进行增加和删除元素的操作:
增加元素:add() 和 update() 两种方法。
agg = {1,"a",(1,2,3)}# add()可以在集合中任意位置添加单个元素,如果添加的元素在集合中已存在,则集合不发生改变 agg.add("b") print(agg)
执行结果:
{1, 'a', 'b', (1, 2, 3)}
agg = {1,"a",(1,2,3)}# update()可以在集合中添加单个或多个元素 agg.update("b") print(agg) agg.update({4,5,6}) # 添加多个元素时,需要用集合的形式来传递参数 print(agg)
执行结果:
{1, 'a', 'b', (1, 2, 3)} {1, 'a', 4, 5, 6, 'b', (1, 2, 3)}
删除元素:remove() 和 discard() 两个方法。
agg = {1,"a",(1,2,3)}# remove()传入的参数为想要删除的元素,如果该元素在集合中不存在,代码将报错 agg.remove("a") print(agg)
执行结果:
{1, (1, 2, 3)}
使用 discard() 删除元素的方法与 remove() 一致,两者的区别在于,discard() 删除元素时,如果元素在集合中不存在,代码也不会报错。
已知字典和集合使用的是同样的符号表示,那么我们使用x = {}
创建的是一个空集合还是一个空字典呢?这样创建的是一个空字典,如果我们想要创建一个空集合时,我们需要使用到 set() 来创建一个空集合。
agg = set() print(agg) print(type(agg))
执行结果:
set() <class 'set'>
set() 还可以将其它可迭代类型的数据转换成集合。
list1 = [1,2,3,4] agg = set(list1) print(agg)
执行结果:
{1, 2, 3, 4}
集合的所有内置方法如下表所示。
方法 | 描述 |
---|---|
add() | 为集合添加元素 |
clear() | 移除集合中的所有元素 |
copy() | 拷贝一个集合 |
difference() | 返回多个集合的差集 |
difference_update() | 移除两个集合都包含的元素 |
discard() | 删除集合中指定的元素 |
intersection() | 返回集合的交集 |
intersection_update() | 返回集合的交集,intersection() 方法是返回一个新的集合,而 intersection_update() 方法是在原始的集合上移除不重叠的元素。 |
isdisjoint() | 判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False;调用方法:set1.issubset(set2),set1 和 set2 分别表示 2 个不同的集合 |
issubset() | 判断指定集合是否为该方法参数集合的子集;调用方法:set1.issubset(set2),set1 和 set2 分别表示 2 个不同的集合 |
issuperset() | 判断该方法的参数集合是否为指定集合的子集;调用方法:set1.issuperset(set2),set1 和 set2 分别表示 2 个不同的集合 |
pop() | 随机移除元素 |
remove() | 移除指定元素 |
symmetric_difference() | 返回两个集合中不重复的元素集合 |
symmetric_difference_update() | 移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中 |
union() | 返回两个集合的并集 |
update() | 给集合添加元素 |
5.1 函数的定义与调用
5.1.1 函数定义、参数、返回值的基本语法
首先,让我们来看一下自定义函数的语法,如下所示:
def 函数名(参数列表): 函数体 return 表达式
结合语法,我们来了解一下自定义函数的规则:
自定义函数以 def 关键字开头,后接函数名和圆括号 (),圆括号后面接冒号;
任何传入的参数必须放在圆括号中间,有多个参数时,参数之间用逗号分隔,参数也可以为空;
函数体的内容需要缩进;
return [表达式] ,用于结束函数,可以选择性地返回一个值给调用方;不带表达式的 return 或者没有 return,相当于返回 None。
下面,我们来看一个案例。
# 定义函数hello def hello(): print('Hello,Educoder!') # 打印Hello,Educoder!
上述自定义函数 hello
实现的是,打印“Hello,Educoder!”的功能,没有参数,也没有使用 return
,返回的值是 None
。可以看到,print
前面有缩进。
我们可以再来看一个简单的自定义函数。
# 定义函数plus def plus(a,b): c = a + b #计算a加b的值,并将结果赋给c return c #返回结果值
上述自定义函数 plus
实现的是加法功能,其中 a
和 b
是传入的参数,c
是返回的值。
函数调用过程
函数已经定义好了,下面来介绍一下如何调用。函数的调用十分简单,只需要使用函数名加参数,就可以实现函数的调用。 首先,我们来看一下函数hello
的调用,如图 1 所示。
图1
在交互环境下,我们自定义了函数hello
,然后通过hello()
实现了函数的调用。因为在定义时,没有参数,所以不需要传参数。我还可以来看一下它的返回值,如图 2 所示。
图2
我们将函数hello
的返回值赋值给变量temp
,然后再打印变量temp
,可以看到得到的返回值确实是None
。
然后,我们再来看一下函数 plus
的调用,如图 3 所示。
图3
可以看出,函数 plus
实现了两个数的加法功能,并且能够返回结果值。
5.2 函数参数
位置参数
我们首先来回顾一下,前一个实训自定义的函数 plus。
# 定义函数plus def plus(a,b): c = a + b # 计算a加b的值,并将结果赋给c return c # 返回结果值
这里的两个参数a
和b
都是位置参数。调用函数时,传入的两个值按照位置顺序依次赋给参数a
和b
。在传参时,有多少个位置参数,就必须传入多少个参数,否则会报错。
默认参数
对于位置参数,有多少个位置参数,就必须传入多少个参数,否则会报错。而如果函数中,有默认参数的话,对于默认参数,可以不用传参,也不会报错,参数会使用默认值。举个例子,我们写个小学生注册的函数,需要打印学生姓名和年级,如果现在注册的都是一年级学生,那么我们就可以将年级默认为一年级,代码如下:
# 定义函数enroll def enroll(name, grade = 'grade1'): # 定义位置参数name和默认参数grade print('name:', name) # 打印姓名 print('grade:', grade) # 打印年级 enroll('XiaoMing') # 调用函数enroll,传入位置参数'XiaoMing'
执行结果:
name: XiaoMing grade: grade1
可见,虽然没有传入默认参数,但是也不会报错,参数grade
输出的值为默认值,默认参数降低了函数调用的难度。如果想对默认值进行修改,可以在传参时,对应默认参数的位置传入新的值。代码如下:
enroll('XiaoHong','grade2') # 修改默认值
执行结果:
name: XiaoHong grade: grade2
可见,默认值已经被修改为“grade2”。这里是在对应默认参数的位置传入默认参数值,对于存在多个默认参数的函数,也可以不按顺序提供默认参数值。当不按顺序提供部分默认参数时,需要把参数名写上。代码如下:
# 使用默认参数,定义函数enroll def enroll(name, age = 7, grade = 'grade1'): # 定义位置参数name和默认参数grade print('name:', name) # 打印姓名 print('grade:', grade) # 打印年级 print('age:', age) # 打印年龄 enroll('XiaoMa', grade = 'grade2') # 调用函数enroll,传入位置参数'XiaoMing'和默认参数grade
执行结果:
name: XiaoMa grade: grade2 age: 7
可见,虽然没有在对应的位置传入默认参数grade
,但是也能正常输出。
可变参数
在 Python 函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是 1 个、2 个到任意个,还可以是 0 个。代码示例如下:
# 定义函数calc,实现参数求和运算 def calc(*numbers): # 参数numbers为可变参数 sum = 0 # 定义变量sum,并赋初值0 # 循环遍历参数,实现参数求和运算 for n in numbers: sum = sum + n return sum # 返回结果值 calc(1, 2, 3, 4) # 调用函数calc,并传入可变参数 calc() # 调用函数calc,不传参数
执行结果:
100
可见,可变参数与位置参数的区别是,前面多了一个*
号,虽然只定义了一个参数numbers
,但是在传参时,可以传入多个参数,甚至不传参数。 如果已经有一个列表 list 或者元组 tuple,要调用一个可变参数怎么办?可以这样做:
nums = [1, 2, 3] # 定义列表nums calc(*nums) # 通过列表调用函数calc
执行结果:
6
*nums
表示把 nums
这个 list
的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键字参数
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时,相当于组装成了一个元组 tuple。与此类似,关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部会自动组装成一个字典 dict。代码如下:
# 使用关键字参数,定义函数enroll def enroll(name, age, **kw): # 定义位置参数name、age和关键字参数kw print('name:', name, 'age:', age, 'other:', kw) # 打印对应参数值 enroll('XiaoMing', 6) # 不传关键字参数 enroll('XiaoHong', 7, sex = 'girl') # 传入一个关键字参数sex enroll('XiaoMa', 7, sex = 'boy', grade = 'grade2') # 传入两个关键字参数sex和grade
执行结果:
name: XiaoMing age: 6 other: {} name: XiaoHong age: 7 other: {'sex': 'girl'} name: XiaoMa age: 7 other: {'sex': 'boy', 'grade': 'grade2'}
可见,与位置参数相比,关键字参数前面多了两个*
,虽然在定义的时候,只定义了一个关键字参数kw
,但是在传参时,可以传入多个参数,甚至不传参数。 和可变参数类似,也可以先组装出一个字典 dict,然后,把该字典 dict 转换为关键字参数传进去:
extra = {'sex': 'girl', 'grade': 'grade1'} # 定义字典extra enroll('XiaoHua', 6, **extra) # 通过字典调用函数enroll
执行结果:
name: XiaoHua age: 6 other: {'sex': 'girl', 'grade': 'grade1'}
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数,参数名可以随便命名。如果要限制关键字参数的名字,就可以用命名关键字参数。代码如下:
# 使用命名关键字参数,定义函数enroll def enroll(name, age, *, sex, grade): # 定义位置参数name、age和命名关键字参数sex、grade print('name:', name, 'age:', age) # 打印name和age print('sex:', sex, 'grade:', grade) # 打印sex和grade enroll('XiaoHong', 7, sex = 'girl', grade = 'grade2')
执行结果:
name: XiaoHong age: 7 sex: girl grade: grade2
可见,要定义命名关键字参数,需要在命名关键字参数前面加一个*
号,并且用逗号将*
号与命名关键字参数分开。 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了。代码如下:
# 使用可变参数和命名关键字参数,定义函数enroll def enroll(name, age, *kw, sex, grade): # 定义位置参数name、age和命名关键字参数sex、grade print('name:', name, 'age:', age, 'kw:', kw) # 打印name、age和kw print('sex:', sex, 'grade:', grade) # 打印sex和grade
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错,如图 1 所示。
图1
由于调用时缺少参数名 sex 和 grade,Python 解释器把这 4 个参数均视为位置参数,但 enroll 函数仅接受 2 个位置参数。 命名关键字参数可以有缺省值,在定义命名关键字参数时,可以传入默认值,调用时,就可以不传这个参数,使用方法和默认参数一样。
5.3 函数返回值
之前的学习类型实训中,我们简单介绍过函数返回值使用的是 return,return 是用于退出函数的,return 后可以选择性地返回一个值给调用方;不带表达式的 return 或者没有 return,相当于返回 None。
def func(): 执行语句 return 表达式
我们可以将return 表达式
近似的理解为print(表达式)
,print 中的表达式可以使用的,return 中的表达式也可以使用。我们通过代码来区分两者在函数中的区别。
def func1(): b = 10**3 print(b) # 调用函数 result = func1() print(result)
执行结果:
1000 None
可以看到输出了两行结果,第一行时运行函数时打印的值,第二行是 func1 运行后的返回值,由于 func1 没有返回值,函数默认返回了 None 值。
def func1(): b = 10**3 return b # 调用函数 result = func1() print(result)
执行结果:
1000
return 在调用时并不会打印,return 会将func1()
转换为一个值。return 通常返回的是单个值,如果我们想要返回多个值时,我们该怎么实现呢?这里我们就需要使用到元组。
def func1(): a = 10**2 b = 10**3 c = 10**4 return a,b,c result = func1() # 如果只有一个变量接收结果,得到的将是一个元组 print(result) result1,result2,result3 = func1() # 用3个变量接收结果时,得到的是3个独立的值 print(result1,result2,result3)
执行结果:
(100, 1000, 10000) 100 1000 10000
5.4 函数嵌套调用与定义
5.4.1 函数的嵌套调用
什么是函数的嵌套调用呢?大家可能会理解为一个函数中再定义一个函数,这是函数的嵌套定义,在后面的实训中,我们会详细介绍函数的嵌套定义。在一个函数中调用另一个函数叫做函数的嵌套调用。我们来举个例子:
# 定义一个求和函数sum def sum(args): value = 0 for x in args: value += x return value# 定义一个求平均值函数mean def mean(*args): sum_value = sum(args) # 在该函数中调用求和函数sum return sum_value/len(args) print(mean(5,6,7,8,9))
执行结果:
7.0
在案例中,我们在定义求平均值函数 mean 时,首先定义了求和函数 sum,通过调用求和函数 sum 得到求和的结果 sum_value,再除以参与求和的数量,从而得到平均值,这样的调用方法可以降低代码的耦合度,提升代码的阅读性,也可以提高函数的复用性。
5.4.2 函数的嵌套定义
函数的嵌套定义就像嵌套列表一样,列表内还有列表叫嵌套列表,而函数内再定义一个函数叫做函数的嵌套定义。
def func1(): # 定义了一个函数func1 print(1) def func2(): # 在函数func1内定义了一个函数func2 print(2) print(3) func2() # 调用函数func2 # 调用函数func1 func1()
执行结果:
1 3 2
我们只能在 func1 函数内调用 func2,在 func1 外不能直接调用 func2 函数。
5.5 变量的作用域
作用域
可能有细心的同学会发现,在函数中定义的变量,在函数外并不能直接使用,这就涉及到了变量的作用域。通常情况下,我们定义的变量是可以在代码中的任意地方使用的,但是,学习了函数的知识之后,我们知道变量分为全局变量和局部变量了。
全局变量:在整个代码文件任意地方都可以使用的变量,作用域为全局范围。
局部变量:在某个函数中声明的,只能在该函数中调用它,如果试图在超出范围的地方调用,代码运行时可能会报错。
var1 = 1 # 定义了一个全局变量 def func(): var2 = 2 # 定义了一个局部变量 print(var1) print(var2) # 调用函数 func() print(var1) # print(var2) # 当我们想打印var2时,代码会报错,提示var2未定义。
执行结果:
1 2 1
如果我们定义了一个全局变量 var1,在函数中也定义了一个局部变量 var1,如果在函数中 var1 的值发生了变化,并不会影响全局变量 var1 的值。
var1 = 1 # 定义了一个全局变量 def func(): var1 = 2 # 定义了一个局部变量 print(var1) # 调用函数 func() print(var1)
执行结果:
2 1
在 Python 中还有两个改变作用域的关键字:
global:将局部变量声明为全局变量。
var1 = 1 # 定义了一个全局变量 def func(): global var1 # 将变量var1声明为全局变量 var1 = 2 print(var1) # 调用函数 func() print(var1)
执行结果:
2 2
nonlocal:将局部变量声明为非局部变量,该关键字能将局部变量的作用域增加一个级别,并不是声明为全局变量,且该关键字
只能对局部变量使用。
var1 = 1 # 定义了一个全局变量var1 def func1(): var2 = 1 # 定义了一个局部变量var2 def func2(): nonlocal var2 # 将var2声明为非全局变量 var2 = 2 # 将局部变量var2的值改为2 func2() # 调用函数func2 print(var2) # 调用函数 func1() print(var1) # print(var2) # 当我们想打印var2时,代码会报错,提示var2未定义。
执行结果:
2 1
5.6 函数递归调用
递归
在 Python 中,函数内可以调用其它函数,如果调用的是这个函数本身,那么这个函数就是递归函数,通俗的来说,就是自己调用自己,这就叫做递归。在之前的实训中,我们做过求阶乘的相关实例,下面我们通过递归来实现求阶乘。
def func(n): # 定义一个求阶乘的函数 if n==1: # 这一步是给出可以退出递归的判断条件 return 1 value = n * func(n-1) # 使用递归 return value print(func(5)) print(func(10))
执行结果:
120 3628800
递归虽然能使代码更加简洁,但是,如果我们在函数中没有给出判断条件来退出递归,那么函数在调用时就会发生栈溢出的情况。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。这种状态和 while 循环中的无限循环类似,不过无限循环并不会报错,栈溢出时,会报错。
def func(n): if n==1: return 1 value = n * func(n-1) return value print(func(1000))
执行结果:
...RecursionError: maximum recursion depth exceeded in comparison
从执行结果可以看出,func() 函数的参数为 1000 时发生了栈溢出,代码报错。
5.7 lambda表达式
lambda 表达式
我们定义函数的时候,通常会给函数定义一个名字,但是对于一个简单函数的定义时,我们完全没有必要定义函数名,这时我们就可以使用 lambda 表达式来定义匿名函数。
# 定义了一个匿名函数,匿名函数没有定义函数名,所以无法直接调用 lambda x:x**2
上述匿名函数等价于下列代码中的函数:
def func(x): return x**2
从上面的例子可以得知,lambda 表达式中冒号前面的表示函数的参数,冒号后面的表示函数的返回值。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
func = lambda x:x**2 print(func(2))
执行结果:
4
匿名函数在设计参数时,也可以像普通函数一样,没有参数、传入多个参数或者使用可变参数等。
func = lambda *x:max(x) # 传入一个可变参数,求传入参数中的最大值 print(func(2,343,63,245,734,52,0))
执行结果:
734
func = lambda x,y:x+y # 传入多个参数时,需要用逗号隔开 print(func(5,10))
执行结果:
15
5.8 生成器函数
在学习什么是生成器函数之前,我们需要了解什么是生成器,生成器是 Python 中的一个对象,对这个对象进行操作,可以依次生产出按生成器内部运算产生的数据;我们称这样的方式为惰性求值,延或者迟求值,但是要注意,如果不对生成器进行操作,是不会产生数据的。下面我们通过代码来解释什么是生成器。
# 定义了一个生成器,和列表推导式类似,不过生成器使用的是() m = (i for i in range(5)) print(list(m)) # 因为生成器是一个对象,所以我们无法直接查看结果,需要用list转换 print(type(m)) print(next(m)) # 查看生成器的值时,需要使用next函数,每次只显示一次循环的结果 print(next(m)) # 打印第二次循环的结果 print(next(m)) # 打印第三次循环的结果
执行结果:
[0, 1, 2, 3, 4] <class 'generator'> 0 1 2
生成器函数就是指定义函数时,函数内部使用了 yield 关键字的就是生成器函数。
# 定义一个简单的生成器函数 def func(): for x in range(5): yield x*2 f = func() # 用变量记住func函数,这样在此进行使用时就可以延续上一次未完成的,如果直接使用next(func()),每次的结果都将一致 print(next(f)) print(next(f))
执行结果:
0 2
生成器函数中也可以有多个 yield。
def gen(): print('line 1') yield 1 print('line 2') yield 2 print('line 3') return 3 # return之后,后面的yield语句将不会执行 yield 4 g = gen() print(next(g)) print(next(g)) # print(next(g)) # 这一步会报错,提示StopIteration,表示迭代完成,不能继续迭代# next()函数可以多传入一个参数来防止迭代完成时发生报错的情况,如果迭代完成,则返回"end",如果迭代没有完成,则返回yield后的表达式 print(next(g,'end'))
执行结果:
line 1 1 line 2 2 line 3 end
通过上述代码我们可以得出以下几点结论:
在生成器函数中,可以多次 yield,每执行一次 yield 后会暂定执行,把 yield 表达式的值返回;
每次循环执行到 yield 时,语句就会暂停执行,将yield 表达式的值返回,知道循环结束;
return 语句依然可以终止函数运行,但 return 语句的返回值不能被获取到;
当迭代完成时,如果继续使用 next 函数,将会抛出 Stoplteration 异常。
5.9 装饰器
要想了解装饰器,首先要了解什么是闭包,在函数中再嵌套一个函数,并且引用外部函数的变量,这就是一个闭包了。我们来举一个简单的例子:
def outer(x): def inner(y): return x + y return inner # 这就是一个闭包函数 print(outer(6)(5)) # 第一个参数是传给outer函数的,第二个传给inner函数
执行结果:
11
如代码所示,在 outer 函数内,又定义了一个 inner 函数,并且 inner 函数又引用了外部函数 outer 的变量 x,这就是一个闭包了。
装饰器是闭包的一种应用,从字面上理解,就是装饰对象的器件,其实装饰器本质上就是一个函数,功能是为其他函数添加功能,可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。
当我们不使用装饰器时:
def A(func): def B(): print("我是附加功能1!") # 附加功能 func() print("我是附加功能2!") # 附加功能 return B def C(): print("我是函数原来的功能!") res=A(C) res()
执行结果:
我是附加功能1! 我是函数原来的功能! 我是附加功能2!
当我们使用装饰器时:
def A(func): # func 表示一个函数 def B(): print("我是附加功能1!") # 附加功能 func() # 调用传入的函数 print("我是附加功能2!") # 附加功能 return B @A # @函数名 表示使用装饰器 def C(): print("我是函数原来的功能!") C()
执行结果:
我是附加功能1! 我是函数原来的功能! 我是附加功能2!
使用装饰器后,@A
等价于不使用装饰器代码中的res=A(C)
。当被装饰的函数需要传入参数时,装饰器的嵌套函数就需要添加一个参数。
def A(func): def B(x): # 添加一个参数 print("我是附加功能1!") #附加功能 func(x) print("我是附加功能2!") #附加功能 return B @A def C(x): print("我是函数原来的功能!%s"%x) C('hello action!')
执行结果:
我是附加功能1! 我是函数原来的功能!hello action! 我是附加功能2!
除了被装饰的函数可以添加参数之外,装饰器也是可以添加参数的。
def A(x): def B(func): def C(y): func(y) print(x) return C return B @A('hello') def D(x): print(x) D('你好!')
执行结果:
你好! hello
6.1 文本编码格式以及格式化
6.1.1 常用编码格式
各位同学在使用计算机的过程中,大部分人应该都遇到过乱码,而出现乱码的原因就是编码格式出了问题,想要解决乱码的问题,我们首先要了解编码格式。下面将介绍几种常用的编码格式:
ASCII 全称为 American Standard Code for Information Interchange,美国信息交换标准代码,这是世界上最早最通用的单字节编码系统,主要用来显示现代英语及其他西欧语言。ASCII 码用 7 位表示,只能表示 128 个字符。只定义了 27=128 个字符,用 7bit 即可完全编码,而一字节 8bit 的容量是 256,所以一字节 ASCII 的编码最高位总是 0。
ISO8859-1 ISO-8859-1 又称 Latin-1,是一个 8 位单字节字符集,它把 ASCII 的最高位也利用起来,并兼容了 ASCII,新增的空间是 128,但它并没有完全用完。在 ASCII 编码之上又增加了西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号,它是向下兼容 ASCII 编码。
GB2312 GB2312 全称为信息交换用汉字编码字符集,是中国于 1980 年发布,主要用于计算机系统中的汉字处理。GB2312 主要收录了 6763 个汉字、682 个符号 GB2312 覆盖了汉字的大部分使用率,但不能处理像古汉语等特殊的罕用字,所以后来出现了像 GBK、GB18030 这种编码。GB2312 完全兼容 ISO8859-1。
GBK 全称为 Chinese Internal Code Specification,即汉字内码扩展规范,于 1995 年制定。它主要是扩展了 GB2312,在它的基础上又加了更多的汉字,它一共收录了 21003 个汉字。
Unicode Unicode 编码设计成了固定两个字节,所有的字符都用 16 位(2^16=65536)表示,包括之前只占 8 位的英文字符等,所以会造成空间的浪费,UNICODE 在很长的一段时间内都没有得到推广应用。Unicode 完全重新设计,不兼容 iso8859-1,也不兼容任何其他编码。
UTF-8 对于英文字母,unicode 也需要两个字节来表示。所以 unicode 不便于传输和存储。因此而产生了 UTF 编码,UTF-8 全称是(8-bit UnicodeTransformation Format)。UTF 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符,不过,UTF 编码是不定长编码,每一个字符的长度从 1-4 个字节不等。其中,英文字母都是用一个字节表示,而汉字使用三个字节。一般项目都会使用 UTF-8。
6.1.2 encode 函数
Python 的encode(encoding, errors)
方法以 encoding 指定的编码格式编码字符串。errors 参数可以指定不同的错误处理方案,默认为”strict”,意为编码错误引起一个 UnicodeError。
str1 = "我爱祖国" result = str1.encode(encoding="UTF-8") # 以UTF-8格式编码字符串 print(result)
执行结果:
b'\xe6\x88\x91\xe7\x88\xb1\xe7\xa5\x96\xe5\x9b\xbd'
6.1.3 decode 函数
Python 的decode(encoding, errors)
方法以 encoding 指定的编码格式解码字符串。默认编码为字符串编码。errors 参数可以指定不同的错误处理方案,默认为”strict”,意为编码错误引起一个 UnicodeError。decode 只能用于编码之后的字符串。
str1 = "我爱祖国" str1_utf8 = str1.encode(encoding="UTF-8") result = str1_utf8.decode(encoding="UTF-8") # 一般情况下,用什么格式编码的,就需要用什么格式来解码,否则会报错或者出现字符乱码的情况 print(result)
执行结果:
我爱祖国
6.1.4 sys 模块
sys 模块是 Python 语言的一个系统内置模块,安装 Python 后已包含 sys 模块,不需要单独再安装。它是用来处理 Python 运行时的配置以及资源,从而可以与当前程序之外的系统环境交互。下面将列出几个 sys 模块中的常用方法。
sys.argv:获取命令行参数,返回值是一个列表,第一个元素是程序本身。
import sys # 使用之前需要先导入模块 print(sys.argv) # 不同py文件的执行结果不同
sys.path:返回模块的搜索路径。
sys.exit(n):退出解释器,n=0 为正常退出。一般情况下执行到主程序末尾,解释器自动退出。但是如果需要中途退出程序,可以调用该函数。0 是正常退出,其他为异常退出。
sys.version:获取 Python 解释程序的版本信息。
sys.platform:返回操作系统平台名称。
sys.stdin/sys.stdout:标准输入/标准输出。类似 print 和 input,stdout 的末尾没有换行符,stdin 在输入时,会将末尾的换行符算入字符串内。
import sys for i in range(10): sys.stdout.write("#") # 打印#号,类似于print,但是默认不换行
执行结果:
##########
6.1.5 chardet 模块
虽然我们了解了编码格式、编码以及解码等相关知识,但是我们不能一眼就看出字符串的编码格式,chardet 模块就是用来帮助我们检测编码的。chardet 模块并不是 Python 安装时自带的,需要我们自己安装,我们可以在命令行输入下列语句来安装第三方模块:
pip install chardet
在安装好之后,我们就可以在代码中调用该模块来识别字符串的编码格式了。
import chardet str1 = "我爱祖国".encode("UTF-8") # 对字符串进行编码 result = chardet.detect(str1) # 只能对编码之后的使用该方法 print(result)
执行结果:
{'encoding': 'utf-8', 'confidence': 0.938125, 'language': ''}
执行结果的字典中的 encoding 表示的是编码格式,confidence 表示检测正确的概率。可见,用 chardet 检测编码,使用简单。获取到编码后,再转换为 str,就可以方便后续处理。
6.1.6 格式化
% 的使用
在之前的实训中,我们都使用过 % 来做格式化输出,并介绍了几个常用的格式化字符;除此之外,% 还有很多实用的功能。
%f 默认情况是保留 6 位有效数字,%.3f 可以只保留小数点后 3 位;
%10s:右对齐,占位符 10 位;
%10d:右对齐,占位符 10 位;
%-10s:左对齐,占位符 10 位;
%.2s:截取 2 位字符串;
%10.2s:10 位占位符,截取两位字符串。
print('%.3f' % 3.14159265) # 保留小数点后3位 print('%20s' % 'hello world') # 右对齐,取20位,不够则补位 print('%20d' % 10) # 右对齐,取20位,不够则补位 print('%-20s' % 'hello world') # 左对齐,取20位,不够则补位 print('%.2s' % 'hello world') # 取2位 he print('%10.2s' % 'hello world') # 右对齐,取2位 print('%-10.2s' % 'hello world') # 左对齐,取2位
执行结果:
3.142 hello world 10 hello world he he he
format 的使用
相对基本格式化输出采用 % 的方法,format() 功能更强大,该函数把字符串当成一个模板,通过传入的参数进行格式化,并且使用大括号{}
作为特殊字符代替 %。使用 format() 函数,也可以很方便的对字符串进行格式化输出:
使用参数位置格式
str1 = "hello world" # 定义一个变量 str, 值为 "hello world" int_num = 1 # 定义一个变量 int_num, 值为 1 print("值为:{0},{0},{1}".format(int_num, str1)) # 0 表示位置,也就是 format 方法中的第一个值,1 表示中的第二个值
输出:
值为:1,1,hello world
使用参数名
str1 = "hello world" # 定义一个变量 str, 值为 "hello world" int_num = 1 # 定义一个变量 int_num, 值为 1 print("值为:{x},{y}".format(x = str1, y = int_num))
输出:
值为:hello world,1
不使用任何参数
str1 = "hello world" # 定义一个变量 str, 值为 "hello world" int_num = 1 # 定义一个变量 int_num, 值为 1 print("值为:{},{},{}".format(int_num, int_num, str1)) # 第一个{}对应第一个 int_num 的值,第二个{}对应第二个 int_num,第三个{}对应str
输出:
值为:1,1,hello world
Template
Template 是 Python 中 string 模块的一部分。通过 Template 可以为 Python 定制字符串的替换标准。下面我们来举几个例子。
from string import Template s = Template('$who likes $what') # 指定一个模板,$表示占位符,$后的表示变量名 print(s.substitute(who='tim', what='kung pao')) # 调用substitute 函数来给模板传值
执行结果:
tim likes kung pao
在模板中,我们给了两个占位符,并且在 substitute 函数中传入了两个对应的值,如果我们少传一个参数或者多传一个参数时,代码就会抛出一个异常,如果我们想要避免这个异常的发生,我们可以使用 safe_substitute() 函数。
from string import Template s = Template('$who likes $what') print(s.safe_substitute(who='tim'))
执行结果:
tim likes $what
6.2 字符串常用操作
6.2.1 字符串函数
其中 string 表示一个字符串。
string.find(str, beg=0, end=len(str)):检测字符串中是否包含子字符串 str,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,如果包含子字符串,返回子字符串的索引值,否则返回 -1。
str1 = "hello world" # 空格也算一个字符 print(str1.find("w)) print(str1.find("wo")) print(str1.find("l", 4, 10)) print(str1.find("e", 3))
执行结果: ```
6
6
9
1 ```
string.rfind(str, beg=0, end=len(string)):返回字符串 str 最后一次出现的位置(从右向左查询),如果没有匹配项则返回 -1。
str1 = "hello world" print(str1.rfind("l")) print(str1.rfind("l", 4, 10)) print(str1.rfind("e", 3))
执行结果:
9
9
1
string.index(str, beg=0, end=len(string)):跟 find() 方法一样,只不过如果 str 不在 string 中会抛出一个异常。
string.rindex(str, beg=0, end=len(string)):跟 rfind() 方法一样,只不过如果 str 不在 string 中会抛出一个异常。
string.count(str, start= 0, end=len(string)):统计字符串里某个字符出现的次数。
str1 = "hello world" print(str1.count("l")) print(str1.count("l", 2, 9))
执行结果:
3 2
string.split(str=” “, num=string.count(str)):str 表示分割字符串的符号,num 表示分割次数,返回一个列表。
str1 = "hello world" print(str1.split()) # 默认是以一个空白符为分割符号 print(str1.split("l")) print(str1.split("l", 1))
执行结果:
['hello', 'world'] ['he', '', 'o wor', 'd'] ['he', 'lo world']
string.rsplit(str=” “, num=string.count(str)):类似于 split() 函数,不过是从右边开始查找,返回一个列表。
str1 = "a b c" print(str1.rsplit()) print(str1.rsplit(" ", 1)) # 只切分一次
执行结果:
['a', 'b', 'c'] ['a b', 'c']
string.partition(str):根据指定的分隔符将字符串进行分割。如果字符串包含指定的分隔符,则返回一个三元的元组,第一个为分隔符左边的子串,第二个为分隔符本身,第三个为分隔符右边的子串。如果没有该分隔符,则抛出一个异常。
str1 = "www.educoder.net" print(str1.partition("."))
执行结果:
('www', '.', 'educoder.net')
string.rpartition(str):类似于 partition() 函数,不过是从右边开始查找。
str1 = "www.educoder.net" print(str1.rpartition("."))
执行结果:
('www.educoder', '.', 'net')
string.join(sequence):将序列中的元素以指定的字符连接生成一个新的字符串。sequence 表示要连接的元素序列。
list1 = ["www","educoder","net"] print(".".join(list1))
执行结果:
www.educoder.net
string.lower():将字符串中所有大写字母转换成小写。
string.upper():将字符串中所有小写字母转换成大写。
string.capitalize():将字符串的第一个字符转换为大写。如果已经是大写,则不变。
str1 = "hello" str2 = "Hello" print(str1.capitalize()) print(str2.capitalize())
执行结果:
Hello Hello
string.title():返回”标题化”的字符串,就是说所有单词的首个字母转化为大写,其余字母均为小写。
str1 = "How are you?" str2 = "www.baidu.com" print(str1.title()) print(str2.title())
执行结果:
How Are You? Www.Baidu.Com
string.swapcase():字符串的大小写字母进行转换。
str1 = "How Are You?" print(str1.swapcase())
执行结果:
hOW aRE yOU?
string.replace(old, new[, max]):把字符串中的 old (旧字符串) 替换成 new (新字符串),如果指定第三个参数 max,则替换不超过 max 次。
str1 = "www//educoder//net" print(str1.replace("//",".")) print(str1.replace("//",".",1))
执行结果:
www.educoder.netwww.educoder//net
sting.maketrans(intab, outtab):用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。两个字符串的长度必须相同,为一一对应的关系。通常与 translate 函数连用。
x = 'abcdefs' y = '1234567' str1='just do it' trantab = str1.maketrans(x,y) print(trantab)
执行结果(都是 ASCII 码值):
{97: 49, 98: 50, 99: 51, 100: 52, 101: 53, 102: 54, 115: 55}
string.translate(table):根据参数 table 给出的表(包含 256 个字符)转换字符串的字符,通常情况下,table 通过 maketrans 函数得到。
x = 'abcdefs' y = '1234567' # 就是将字符串中的a改成1,b改成2,以此类推 str1='just do it' trantab = str1.maketrans(x,y) print(str1.translate(trantab))
执行结果:
ju7t 4o it
string.strip([chars]):移除字符串首尾指定的字符(默认为空格)或字符序列,chars 表示指定的字符或字符序列。
str1 = " hello world " str2 = "## hello world###" print(str1.strip()) # 只去除首位的,中间的空格不会移除 print(str1.strip("#")) # 只去除首位的#号
执行结果:
hello world hello world
string.rstrip([chars]):与 strip 类似,不过 rstrip 函数只移除字符串末尾(最右侧)指定的字符(默认为空格)或字符序列。
string.lstrip([chars]):与 strip 类似,不过 lstrip 函数只移除字符串开头(最左侧)指定的字符(默认为空格)或字符序列。
string.startswith(substr, beg=0,end=len(string)):检查字符串是否是以指定子字符串 substr 开头,如果是则返回 True,否则返回 False。如果参数 beg 和 end 指定值,则在指定范围内检查。
str1 = "this is string example....wow!!!" print (str1.startswith('this')) # 字符串是否以this开头 print (str1.startswith('string', 8)) # 从第八个字符开始的字符串是否以 string 开头 print (str1.startswith('this', 2, 4)) # 从第2个字符开始到第四个字符结束的字符串是否以this开头
执行结果:
True True False
string.endswith(substr, beg=0,end=len(string)):检查字符串是否是以指定子字符串 substr 结束,如果是则返回 True,否则返回 False。如果参数 beg 和 end 指定值,则在指定范围内检查。
str1 = "this is string example" print (str1.endswith('example')) print (str1.endswith('str', 0, 11)) print (str1.endswith('this', 2, 4))
执行结果:
True True False
string.center(width[, fillchar]):返回一个指定的宽度 width 居中的字符串,fillchar 为填充的字符,默认为空格。如果指定的长度小于原字符串的长度则返回原字符串。
str1 = "www.educoder.net" print(str1.center(40, "#")) # 用#号来填充
执行结果:
############www.educoder.net############
string.ljust(width[, fillchar]):返回一个原字符串左对齐,并使用空格填充至指定长度 width 的新字符串。如果指定的长度小于原字符串的长度则返回原字符串。
string.rjust(width[, fillchar]):返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串。如果指定的长度小于字符串的长度则返回原字符串。
str1 = "www.educoder.net" print(str1.ljust(40, "#")) print(str1.rjust(40, "#")) print(str1.rjust(5))
执行结果:
www.educoder.net######################## ########################www.educoder.net www.educoder.net
string.zfill(width):返回指定长度的字符串,原字符串右对齐,前面填充 0。
str1 = "www.educoder.net" str2 = "hello" print(str1.zfill(10)) print(str2.zfill(10))
执行结果:
www.educoder.net 00000hello
6.3 内置函数应用
内置函数是指在代码中不需要导入任何模块就可以使用的函数,它可以简化我们的代码,提高编写代码的速度。下面将介绍一些常用的内置函数。
len():返回一个可迭代对象(字符串、元组、列表等)的长度。
max():返回给定参数的最大值,参数可以为序列。
min():返回给定参数的最小值,参数可以为序列。
str1 = "hello world" print(len(str1)) print(max(1,3,4,5,7,0)) print(min(1,3,4,5,7,0))
执行结果:
11 7 0
eval():将一个字符串转换为可执行的表达式,返回该表达式的值。
str1 = "print('hello world')" eval(str1)
执行结果:
hello world
list():将一个字符串、元组或者可迭代对象转换为列表。
zip([iterable, …]):将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
a = [1,2,3,4] b = ["a","b","c"] result = zip(a,b) # 返回一个对象 print(list(result)) x,y = zip(*zip(a,b)) # 进行解包操作,需要两个变量来接收 print(x) print(y)
执行结果:
[(1, 'a'), (2, 'b'), (3, 'c')] (1, 2, 3) ('a', 'b', 'c')
sorted(iterable, key=None, reverse=False):对所有可迭代的对象进行排序操作。iterable 表示可迭代对象,key 用于指定可迭代对象中的一个元素来进行排序,reverse 表示排序规则,
reverse = True
降序,
reverse = False
升序(默认)。
print(sorted([5, 2, 3, 1, 4])) print(sorted([5, 2, 3, 1, 4], reverse=True)) # 倒序排序 array = [{"age":20,"name":"a"},{"age":25,"name":"b"},{"age":10,"name":"c"}] result = sorted(array, key=lambda x:x["age"]) # 根据字典中的age键的值来排序 print(result)
执行结果:
[1, 2, 3, 4, 5] [5, 4, 3, 2, 1] [{'age': 10, 'name': 'c'}, {'age': 20, 'name': 'a'}, {'age': 25, 'name': 'b'}]
reversed():返回一个反转的迭代器。可以是 tuple、string、list 或 range。
# 字符串 seqString = 'EduCoder' print(list(reversed(seqString))) # 元组 seqTuple = (1, 2, 4, 3, 5) print(list(reversed(seqTuple))) # range seqRange = range(5, 9) print(list(reversed(seqRange))) # 列表 seqList = [1, 2, 4, 3, 5] print(list(reversed(seqList)))
执行结果:
['r', 'e', 'd', 'o', 'C', 'u', 'd', 'E'] [5, 3, 4, 2, 1] [8, 7, 6, 5] [5, 3, 4, 2, 1]
enumerate(sequence, [start=0]):函数用于将一个可迭代对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。sequence 表示一个序列、迭代器或其他支持迭代对象,start 表示下标起始位置。
list1 = ["a","b","c","d"] print(list(enumerate(list1))) print(list(enumerate(list1, start=2))) # 下标从2开始
执行结果:
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')] [(2, 'a'), (3, 'b'), (4, 'c'), (5, 'd')]
6.4 随机生成
6.4.1 string 模块
在之前的实训中,我们学习了 string 模块中的 Template 方法。string 模块中还定义了一些常用的属性,包含所有数字、字母、可打印的所有 ASCII 码等。
string.ascii_letters:生成所有大小写字母。
import string print(string.ascii_letters)
执行结果:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
string.ascii_lowercase:生成所有小写字母。
import string print(string.ascii_lowercase)
执行结果:
abcdefghijklmnopqrstuvwxyz
ascii_uppercase:生成所有大写字母。
import string print(string.ascii_uppercase)
执行结果:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
digits:生成所有数字。
import string print(string.digits)
执行结果:
0123456789
punctuation:生成所有标点符号。
import string print(string.punctuation)
执行结果:
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
6.4.2 random 模块
Python 中的 random 模块是用于生成随机数的,下面将详细介绍 random 模块的常用方法。
random.random():生成 [0,1) 之间的随机浮点数。
import random print(random.random()) # 生成一个随机数,每次运行的结果不一样
random.uniform(a,b):生成一个指定范围内的随机符点数,两个参数其中一个是上限,一个是下限。如果a > b,则生成的随机数 n:b <= n <= a。如果a < b, 则a <= n <= b。
import random print(random.uniform(1,10)) print(random.uniform(10,1))
random.randint(a, b):生成一个指定范围内的整数。其中参数 a 是下限,参数 b 是上限,生成的随机数 n:a <= n <= b
import random print(random.randint(1,10))
random.choice(sequence):从序列 sequene 中获取一个随机元素。
import random list1 = [1,2,3,4] print(random.choice(list1)) # 从列表中随机获取一个元素
random.randrange([start], stop[, step]):等价于
random.choice(range([start], stop[, step])),从递增序列中获取一个元素。
import random print(random.randrange(1,10,2)) # 从 1,3,5,7,9 中选一个数
random.shuffle(iter):将可迭代对象内的元素随机排列。
list1 = [1,2,3,4] print(random.shuffle(list1))
7.1 类的定义与使用
7.1.1 类的定义
Python 是一门面向对象的语言。面向对象编程是一种编程思想,在面向对象编程中,把对象作为程序的基本单元,把程序视为一系列对象的集合。一个对象包括了数据和操作数据的方法,消息传递成为联系对象的方法。
对象可按其性质划分为类,对象是类的实例,类是用来描述具有相同的属性和方法的对象的集合,类是抽象的集合。
class Book(object): # 定义了一个类,类名为Book bookList = ['python','java','c++','ruby'] # booList为类的属性 for book in bookList: print(book)
在 Python 中,类的声明是通过 class 关键字,在用 class 关键字声明一个类之后,此类就被定义了,类名的开头通常是大写。object 表示该类的继承关系,在后面的实训中,我们会详细介绍继承关系,如果父类是 object,则可以省略,也就是说,class Book(object)
等价于class Book
。
7.1.2 类的使用
在定义一个类之后,类里的属性和方法等都可以在类外部调用的;想要访问类内的属性或者方法时,必须先对该类进行实例化操作,该操作类似于变量的赋值操作,如果不对类进行实例化,我们无法访问该类。
class Book(object): # 定义了一个类,类名为Book bookList = ['python','java','c++','ruby'] # booList为类的属性 for book in bookList: print(book) def sale(self,book_name): # 类中定义了一个方法sale self.book_name = book_name # 表示将外部参数赋值给该实例的变量 print("出售一本%s书"% self.book_name) book = Book() # 实例化类 book.sale("python") # 调用类中的方法 print(book.bookList) # 访问类的属性
执行结果:
python java c++ ruby 出售一本python书 ['python', 'java', 'c++', 'ruby']
从执行结果可以得知,我们在实例化类时,类里的执行语句都会被执行一遍,所以,类中的 for 循环在实例化时运行了。
我们可以发现在类中定义函数的时候,参数中多了一个 self,self 只有在类的方法中才会有,独立的函数或方法是不必带有 self 的。self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。
注意:self代表类的实例,而非类,上述例子中 self 表示的是 book。
7.2 属性、对象成员、方法
7.2.1 属性
类属性
类属性是在类中方法的外部但又在类之中定义的属性。在类外部也可以通过“类.类属性” 来访问。类属性在所有对象之间是共享的。
class People(object): name = 'Tom' # 类属性 age = 10 p = People() print(p.name) print(People.age) # 访问类属性
执行结果:
Tom 10
对象属性
而对象属性是在构造函数中定义的(__init__
),定义时候以 self 作为前缀。首先我们需要了解什么是构造函数__init__
。 当我们不使用__init__
构造函数时:
class Box: # 我们在Box类中定义了setDimension方法去设定该Box的属性 def setDimension(self, width, height, depth): self.width = width # 对象属性 self.height = height # 对象属性 self.depth = depth # 对象属性 def getVolume(self): return self.width * self.height * self.depth # 实例化类 b = Box() b.setDimension(10, 20, 30) # 需要先调用setDimension方法 print(b.getVolume())
执行结果:
6000
使用__init__
构造函数时:
class Box: def __init__(self, width, height, depth): self.width = width self.height = height self.depth = depth def getVolume(self): return self.width * self.height * self.depth # 实例化类 b = Box(10, 20, 30) # 使用了构造方法之后,不需要再调用别的方法来设置属性 print(b.getVolume())
执行结果:
6000
我们在构造函数中定义的self.width、self.height、self.depth
都是对象属性;对象属性值可以在类外部修改,而类属性在类外部是不可以修改的。
class People(object): address = '山东' #类属性 def __init__(self): self.name = 'xiaowang' #实例属性 self.age = 20 #实例属性 p = People() print(p.age) print(People.address) p.age = 10 p.address = "北京" print(p.age) print(People.address)
执行结果:
20 山东 10 山东
7.2.2 对象成员
类的成员
类的成员在定义的时候,如果以两个短下滑线__
开头则表示是私有成员,否则就是公有成员。私有成员只允许类函数内部使用,类外部不能访问。
class People: address = '山东' # 公有成员 __area = 1080 # 私有成员 def __init__(self): self.name = '小明' # 公有成员 self.__age = 20 # 私有成员 def func(self): return self.__age # 在类中访问私有成员 print(__area) # 在类中访问私有成员 p = People() print(p.func())# 在类外部无法访问私有成员,代码运行时会报错# print(p.__area)
执行结果:
1080 # 类中有print语句,所以在实例化时会先打印__area的值 20
私有成员只能在类内部使用,但也可以通过特殊的方法进行访问,即对象名._类名+私有成员
的方式。
class People: address = '山东' # 公有成员 __area = 1080 # 私有成员 def __init__(self): self.name = '小明' # 公有成员 self.__age = 20 # 私有成员 p = People() print(p._People__area) print(p._People__age)
执行结果:
1080 20
数据成员包括了私有成员和公有成员,是私有成员和公有成员的总称。
7.2.3 方法
静态方法
静态方法是类中的函数,不需要实例,主要用来存放逻辑性的代码,与类本身没有交互,即在静态方法中不会涉及到类内的方法和属性,若想调用类内的方法和属性则可通过__class__.类属性
来调用;定义一个静态方法需要添加@staticmethod
装饰器。
class People: country="China" # 定义一个静态方法 @staticmethod def getCountry(): # 在这个方法中,一般不会访问类中的方法和属性 return __class__.country p=People() print(p.getCountry()) # 实例对象调用静态方法
执行结果:
China
类方法
类方法是类对象所拥有的方法,需要用修饰器@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以 cls 作为第一个参数;类方法只能访问类变量,不能访问实例变量。
class People: country="china" # 定义一个类方法 @classmethod def getCountry(cls): return cls.country @classmethod def setCountry(cls,country): # 类方法可用于该类属性 cls.country=country p=People() print(p.getCountry()) # 实例对象调用类方法 print(People.getCountry()) # 类对象调用类方法 p.setCountry("Japan") print(p.getCountry())
执行结果:
china china Japan
7.3 继承
继承关系
在面向对象编程中,有一种机制叫做继承。通过继承,子类可以继承其父类所有的属性和方法,这在很大程度上增强了代码的重用。
父类也称基类,其声明方法与一般的类的声明方法一样。父类中存在着一些公共的属性和方法,子类继承于父类,拥有父类中的属性和方法,它自己也可根据实际情况声明一些属于自己的属性和方法。
以下场景便模拟了继承的现实场景:
在自然界中存在着许多的动物,动物间有许多的共性,比如:呼吸、奔跑、觅食等,但是它们之间也存在着不同之处,比如鱼会游泳、豹子会爬树……,在这个场景里,动物就是父类,它具有着所有动物都有的共性,而鱼和豹子是子类,它们不仅具有共性:呼吸、奔跑、觅食,还有着自己独特的特征:游泳、爬树。
# 定义了一个父类 class ParentClass: static_var = 100 def parentMethod(self): print("这是父类")# 定义了一个子类 class SubClass(ParentClass): # 表示SubClass类是继承ParentClass类 def subMethod(self): print("这是子类")# 实例化子类 sc = SubClass() print(sc.static_var) # 访问父类属性 sc.parentMethod() # 访问父类方法 sc.subMethod() # 访问子类方法
执行结果:
100 这是父类 这是子类
如果我们在子类中定义了一个和父类名字相同的属性,那么子类中的属性会覆盖父类中的属性。
# 定义了一个父类 class ParentClass: static_var = 100 def parentMethod(self): print("这是父类") # 定义了一个子类 class SubClass(ParentClass): static_var = 50 # 覆盖了父类中的属性 def subMethod(self): print("这是子类") sc = SubClass() print(sc.static_var)
执行结果:
50
继承还具有传递性,C 类从 B 类继承,B 类又从 A 类继承,那么 C 类就具有 B 类和 A 类的所有属性和方法,也就是说子类拥父类以及父类的父类中封装的所有 属性和方法。
7.4 多态
7.4.1 方法重写
在之前的实训中,我们学习了类的继承,如果我们在父类中定义了一个 run 方法,但是子类中也定义了一个 run 方法,这时我们来调用 run 方法会运行哪一个呢?
class Dog: def run(self): print("所有狗都在跑") class Hashiqi(Dog): def run(self): print("哈士奇在跑") d = Dog() h = Hashiqi() d.run() h.run() # 继承自Dog类,但是Hashiqi类中也定义了一个run方法
执行结果:
所有狗都在跑 哈士奇在跑
在子类中重复定义父类中方法的行为叫做方法重写,这时,我们调用的方法就是子类中重写之后的方法。
当我们调用一个对象的方法时,会优先去当前对象中寻找是否具有该方法,如果有则直接调用,如果没有则去对象的父类中寻找,如果父类中有则直接调用父类中的方法,如果还是没有则去父类中的父类中寻找,以此类推,直到找到 object,如果始祖父类也没有,就报错。
除了可以重写普通方法之外,类中的构造方法也可以被重写。
class Dog: def __init__(self,var1,var2): self.var1 = var1 self.var2 = var2 class Hashiqi(Dog): def __init__(self,var1): # 重写构造方法 self.var1 = var1# 实例化Hashiqi类时,只需要传入一个参数给对象属性 h = Hashiqi(1) print(h.var1)
执行结果:
1
7.5 多继承
类的继承在实际应用中,主要是提高代码的复用性,一个类继承自多个类就是多继承,它将具有多个类的特征,如同一个小孩继承了父母双方的特征。
class A(object): def test(self): print("this is A.test()") class B(object): def test(self): print("this is B.test()") def check(self): print("this is B.check()") class C(A,B): # 继承A和B两个类 pass # pass表示这是一个空类 class D(A,B): def check(self): print("this is D.check()") class E(C,D): pass
在这个例子中,类C
继承自类A
和类B
,类D
继承自类A
和类B
,类E
继承自类C
和类D
。继承情况如图 1 所示。
图 1
在我们调用E.test()
时,在类 A 和类 B 中都存在这个方法,但是由于在多重继承中遵循广度优先的方式,所以程序最先搜索类 E,然后再搜索类 C 和类 D,若还没找到,再到 A 中查找,A 中存在这个方法,于是调用这个方法。如果在 A 中也没有找到,则再到 B 中查找。
调用E.test()
结果为:
this is A.test()
如果调用E.check()
方法,那么先到类 E 中查找,然后再 C 中查找,再到 D 中查找,在类 D 中找到这个方法,调用这个方法。
调用E.check()
的结果为:
this is D.check()
在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super 来实现。
class Animal(object): def __init__(self, name,age): self.name = name self.age = age print("父类中的init") class Dog(Animal): def __init__(self, name,age,sex): super().__init__(name,age) # 继承父类中的name和age属性 self.sex = sex print("%s狗的年龄为%d,性别为%s"%(self.name,self.age,self.sex)) d = Dog("Tom",11,"male")
执行结果:
父类中的init Tom狗的年龄为11,性别为male
看了上面的使用,你可能会觉得 super 的使用很简单,无非就是获取了父类,并调用父类的方法。其实,在上面的情况下,super 获得的类刚好是父类,但在其他情况就不一定了,super 其实和父类没有实质性的关联。
class Base(object): def __init__(self): print("enter Base") print("leave Base") class A(Base): def __init__(self): print("enter A") super().__init__() print("leave A") class B(Base): def __init__(self): print("enter B") super().__init__() print("leave B") class C(A, B): def __init__(self): print("enter C") super().__init__() print("leave C") c = C()
执行结果:
enter C enter A enter B enter Base leave Base leave B leave A leave C
如果你认为 super 代表调用父类的方法,那你很可能会疑惑为什么 enter A 的下一句不是 enter Base 而是 enter B。因为 super 方法如果出现在多继承中的话,会涉及到一个 MRO(继承父类方法时的顺序表) 的调用顺序问题。我们可以使用类.__mro__
查看调用顺序。
print(C.__mro__) # 查看C类的调用顺序
执行结果:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
7.6 运算符方法
运算符重载
一般来说,一个类能够计算,是因为内置了 add(加),sub(减) 等方法,当调用+,- 的时候,其实调用的就是 add、sub 方法,当我们对类的属性进行运算时,通常会这样:
class Programer: def __init__(self, name, age): self.name = name self.age = age a = Programer("Tom", 25) b = Programer("John", 26) print(a.age + b.age) # 两个年龄相加
执行结果:
51
但是当我们将运算符重载之后,就可以使用如下代码:
class Programer: def __init__(self, name, age): self.name = name self.age = age def __add__(self,other): # 运算符重载 return self.age + other.age a = Programer("Tom", 25) b = Programer("John", 26) print(a+b)
执行结果:
51
不同的类也可以使用:
class Programer1: def __init__(self, name, age): self.name = name self.age = age def __add__(self,other): # 运算符重载 return self.age + other.age class Programer2: def __init__(self, name, age): self.name = name self.age = age a = Programer1("Tom", 25) b = Programer2("John", 26) print(a+b)
执行结果:
51
除了正向加法__add__
,还有反向加法__radd__
:
class Programer1: def __init__(self, name, age): self.name = name self.age = age def __radd__(self,other): # 运算符重载 return self.age + other.age class Programer2: def __init__(self, name, age): self.name = name self.age = age a = Programer1("Tom", 25) b = Programer2("John", 26) print(b+a) # 如果是 a+b 则报错
执行结果:
51
除了加法之外,大部分运算符都可以重载。部分可重载的运算符如下表:
重载运算符 | 含义 |
---|---|
add | 加法运算符 +,当类对象 X 做例如 X+Y 等操作,内部会调用此方法。 |
radd | 当类对象 X 做类似 Y+X 的运算时,会调用此方法。 |
iadd | 重载 += 运算符,也就是说,当类对象 X 做类似 X+=Y 的操作时,会调用此方法。 |
sub | 加法运算符 -,当类对象 X 做例如 X-Y 等操作,内部会调用此方法。 |
rsub | 当类对象 X 做类似 Y-X 的运算时,会调用此方法。 |
isub | 重载 -= 运算符,也就是说,当类对象 X 做类似 X-=Y 的操作时,会调用此方法。 |
mul | 加法运算符 ,当类对象 X 做例如 XY 等操作,内部会调用此方法。 |
rmul | 当类对象 X 做类似 Y*X 的运算时,会调用此方法。 |
imul | 重载 = 运算符,也就是说,当类对象 X 做类似 X=Y 的操作时,会调用此方法。 |
truediv | 加法运算符 /,当类对象 X 做例如 X/Y 等操作,内部会调用此方法。 |
rtruediv | 当类对象 X 做类似 Y/X 的运算时,会调用此方法。 |
itruediv | 重载 /= 运算符,也就是说,当类对象 X 做类似 X/=Y 的操作时,会调用此方法。 |
floordiv | 整除,表示运算符中的 // |
mod | 表示取模运算 |
or | “或”运算符 | |
and | “与”运算符 & |
lt | 比较运算符 < |
gt | 比较运算符 > |
le | 比较运算符 <= |
ge | 比较运算符 >= |
eq | 比较运算符 == |
ne | 比较运算符 != |
上表中的所有方法中,除了比较运算符和逻辑运算符都有正向方法(add)、反向方法(radd)和就地方法(iadd)三种。
7.7 魔法方法
在 Pyhton 中,我们经常可以看到以双下滑线__
包裹起来的方法,最常见的就是__init__
,这些方法被称为魔法方法或者特殊方法,这些方法可以给 Python 的类提供特殊功能。我们主要介绍以下几个魔法方法:
__init__
:构造方法,在实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
__new__
: 在实例创建之前被调用的。因为它的任务就是创建实例然后返回该实例,是个静态方法。
class TestClass: def __init__(self): print("调用__init__") def __new__(cls, *args, **kwargs): # 比构造方法更早调用 print("调用__new__") return object.__new__(cls) # 返回当前类的实例 t = TestClass()
执行结果:
调用__new__ 调用__init__
__new__
决定是否要使用当前的__init__
方法,因为__new__
可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果__new__
没有返回实例对象,则__init__
不会被调用。
class A: pass class B(A): def __init__(self): print("调用__init__") def __new__(cls, *args, **kwargs): print("调用__new__") return object.__new__(A) #返回父类的实例,不会调用__init__方法 t = B()
执行结果:
调用__new__
__del__
:有了构造方法自然少不了析构方法。Python 中__del__
可以认为是析构函数了,在一个实例被销毁时它会执行。该方法是解释器自动调用的,一般情况下不重写。
class Testclass: def __new__(cls, *args, **kwargs): print("创建实例") return object.__new__(cls) def __init__(self): print("初始化实例") def __del__(self): print("销毁") print("自动调用del") T = Testclass()
执行结果:
创建实例 初始化实例 销毁 自动调用del
__str__
:当被 str() 调用或者打印对象时的行为。
class A: def __init__(self,name): self.name = name class B: def __init__(self,name): self.name = name def __str__(self): # 重写__str__方法 return self.name a = A("Tom") print(a) b = B("Jake") print(b)
执行结果:
<__main__.A object at 0x000001F48BF4DF08> Jake
__repr__
:当被 repr() 调用或者直接执行对象时的行为。与__str__
类似,不过__repr__
直接执行对象时也会被调用。
__getattr__
:当我们访问一个不存在的属性时会调用此方法,如果属性存在则不会调用。
class TestClass: def __getattr__(self, item): print("不存在的属性") return item def __init__(self,name): self.name = name t = TestClass("Tom") print(t.name) print(t.age) # age属性不存在
执行结果:
Tom 不存在的属性 age
如果我们不重写__getattr__
方法,当我们访问一个不存在的属性时会抛出 AtrributeError 的错误。
__setattr__
:所有的属性设置都会调用此方法,并且只有拥有这个魔法方法的对象才可以设置属性。使用这个方法要注意不要循环调用。
class TestClass: def __setattr__(self, name, value): print("执行__setattr__") object.__setattr__(self, name, value) def __init__(self,name): self.name = name t=TestClass("Tom") print(t.name)
执行结果:
执行__setattr__ Tom
错误示范:
class TestClass: def __setattr__(self, name, value): print("执行__setattr__") self.name = value #错误使用,这条赋值语句会调用自己,从而产生循环调用 def __init__(self,name): self.name = name t=TestClass("Tom") print(t.name)
执行结果:
执行__setattr__ 执行__setattr__ 执行__setattr__ 执行__setattr__ 执行__setattr__ 执行__setattr__ 执行__setattr__ ... ...
最后代码会报错。
__getattribute__
:和__getattr__
方法类似,但是它更加强大,所有访问属性的行为都会调用这个方法,不仅仅是不存在的属性。
class TestClass: def __getattribute__(self, item): print("调用__getattribute") return item def __init__(self,name): self.name = name t=TestClass("Tom") print(t.name) print(t.age)
执行结果:
调用__getattribute name 调用__getattribute age
8.1 文本文件的读取
8.1.1 文本文件
文本文件是一种典型的顺序文件,其文件的逻辑结构又属于流式文件。文本文件是指以 ASCII 码方式(也称文本方式)存储的文件,更确切地说,英文、数字等字符存储的是 ASCII 码,而汉字存储的是机内码。
文本文件中除了存储文件有效字符信息(包括能用 ASCII 码字符表示的回车、换行等信息)外,不能存储其他任何信息,比如图片、视频等等。我们常见的 txt 文件就是文本文件。
8.1.2 open 函数及其参数
Python 的 open 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出异常。
open 方法的语法格式为:open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closed=True, opener=None)
file:表示要打开的文件路径(相对或者绝对路径);
mode:表示文件打开模式;
buffering:用于设置缓冲;
encoding:用于设置编码格式,一般使用 utf8;
errors:指明编码和解码错误时怎么样处理,适用于文本模式;
newline:文本模式之下,控制一行的结束字符;
closed:传入的 file 参数类型;
opener:自定义打开文件方式。
8.1.3 文件打开模式
mode 参数的打开模式具体如下表所示:
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
假设现在有一个 test.txt 文件,位于 step1 目录下,我们可以使用下列代码来打开文件:
f = open("step1/test.txt","r",encoding="utf8") # f就是文件对象,一般情况下,只设置这3个参数
8.1.4 关闭文件 close 函数
close() 方法用于关闭一个已打开的文件。关闭后的文件不能再进行读写操作, 否则会触发 ValueError 错误。 close() 方法允许调用多次。
当 file 对象被引用到操作另外一个文件时,Python 会自动关闭之前的 file 对象。 使用 close() 方法关闭文件是一个好的习惯。
f = open("step1/test.txt","r",encoding="utf8")# 进行相关的读写操作之后使用 close 函数关闭文件f.close()
8.1.5 文件对象常用属性
文件对象常用的属性主要有 3 个:
closed:如果文件已被关闭返回 True,否则返回 False;
f = open("step/test.txt","r",encoding="utf8") print(f.closed) f.close() print(f.closed)
执行结果:
False True
mode:返回被打开文件的访问模式;
f = open("step/test.txt","r",encoding="utf8") print(f.mode)
执行结果:
r
name:返回文件的名称;
f = open("step/test.txt","r",encoding="utf8") print(f.name)
执行结果:
step/test.txt
8.2 文件文本的顺序读写
本实训将介绍如何在文件中进行内容的读写,假设我们现在有 test.txt 文件,内容如下:
hello world
想要对文件内容进行操作,主要涉及到以下几个函数:
read([size]):从文件读取指定的字节数,如果未给定或为负则读取所有;
f = open("test.txt","r",encoding="utf8") print(f.read())
执行结果:
hello world
readable():如果文件可读,该方法返回 True,否则返回 False;
f = open("test.txt","r",encoding="utf8") print(f.readable())
执行结果:
True
readline([size]):读取整行,包括 “\n” 字符;如果指定了一个非负数的参数,则返回指定大小的字节数,包括 “\n” 字符;
f = open("test.txt","r",encoding="utf8") print(f.readline())
执行结果:
hello\n
readlines():读取所有行并返回列表。
f = open("test.txt","r",encoding="utf8") print(f.readlines())
执行结果:
["hello\n", "world"]
write(str):将字符串 str 写入文件,返回的是写入的字符长度。如果该文件不存在,则新建一个文件。
f = open("test.txt","w",encoding="utf8") f.write("Python")
test.txt 的内容将更改为:
Python
writable():如果文件可写,该方法返回 True,否则返回 False;
f = open("test.txt","w",encoding="utf8") print(f.writeable())
执行结果:
True
writelines([str]):向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。如果该文件不存在,则新建一个文件。
f = open("test1.txt","w",encoding="utf8") f.write(["hello\n","Python"])
新建一个 test1.txt,文件的内容为:
hello Python
8.3 文本文件的随机读写
使用 read 等顺序读写函数时,每次都是从每行的第一个字符开始读取,但是,我们不一定每次的需求都是从头开始读取。假设我们有test.txt
文件,内容如下:
hello world
下面我们将介绍随机读取的函数:
seek(offset,whence=0):用于移动文件读取指针到指定位置。offset 表示开始的偏移量,也就是代表需要移动偏移的字节数;whence 表示要从哪个位置开始偏移;0 代表从文件开头开始算起,1 代表从当前位置开始算起,2 代表从文件末尾算起。
f = open("test.txt","rb") f.seek(2) # 将读取指针移动到2的位置 print(f.read())# 移动到文件倒数第5个字节,如果要设置whence参数,文件的打开格式必须为二进制模式 f.seek(-5,2) print(f.read()) f.close()
执行结果:
b'llo world' b'world'
seekable():如果文件是可搜索的,则 seekable 方法返回 True,否则返回 False。如果文件允许访问文件流(例如 seek 方法),则该文件是可搜索的。
tell():返回文件的当前位置,即文件指针当前位置。
f = open("test.txt","r") f.seek(2) print(f.tell()) f.read(1) # 读取一个字节的内容 print(f.tell()) # 读取内容也会改变指针的位置 f.close()
执行结果:
2 3
truncate([size]):方法用于截断文件并返回截断的字节长度。指定 size 的话,就从文件的开头开始截断指定长度,其余内容删除;不指定 size 就从文件开头开始截断到当前位置,其余内容删除。
f = open("test.txt","r+") print(f.truncate(3)) print(f.read()) f.close()
执行结果:
3 hel
8.4 目录访问
8.4.1 os模块常用函数
os 模块简单的来说它是一个 Python 的系统编程的操作模块,可以通过代码来处理文件和目录。假设我们在 Linux 系统中有一个 py 文件,其路径为:/root/edu/test.py
。
getcwd():返回当前的工作目录。
import os print(os.getcwd())
执行结果:
/root/edu
chdir(path):改变当前工作目录到指定的路径。path 表示要切换到的新目录。如果允许访问返回 True,否则返回 False。
import os print(os.chdir("/root/coder")) # 将文件切换到 /root/coder 目录下 print(os.getcwd())
执行结果:
True /root/coder
listdir(path):返回指定路径下的文件和文件夹列表。path 表示路径。不指定 path 则表示当前路径。
import os print(os.listdir()) # 当前文件路径下只有一个test.py文件
执行结果:
["test.py"]
mkdir(path):创建单层目录,如该目录已存在则抛出异常。
makedirs(path):递归创建多层目录,如该目录已存在抛出异常。
remove(path):删除文件。
rmdir(path):删除单层目录,如果该目录非空则抛出异常。
removedirs(path):递归删除目录,从子目录到父目录逐层尝试删除,遇到目录非空则抛出异常。
rename(old,new):将文件 old 重命名为 new。
import os rename("test.py","change.py") # 将当前目录下得test.py修改为change.py print(os.listdir())
执行结果:
["change.py"]
os.system(command):运行系统的 shell 命令,command 表示 shell 命令。
import os os.system('date') # 相当于在命令行中输入date os.system('ls -al') # 相当于在命令行中输入ls -al
如果想更加了解 os 模块以及 os.path 的应用,建议在本地环境中使用下列函数。
8.4.2 os.path 模块
下表是 os.path 模块中的常用方法:
方法 | 说明 |
---|---|
os.path.abspath(path) | 返回绝对路径 |
os.path.basename(path) | 返回文件名 |
os.path.commonprefix(list) | 返回 list(多个路径) 中,所有 path 共有的最长的路径 |
os.path.dirname(path) | 返回文件路径 |
os.path.exists(path) | 路径存在则返回 True,路径损坏返回 False |
os.path.lexists | 路径存在则返回 True,路径损坏也返回 True |
os.path.expanduser(path) | 路径中包含的"~"和"~user"转换成用户目录 |
os.path.expandvars(path) | 根据环境变量的值替换 path 中包含的"$name"和"${name}" |
os.path.getatime(path) | 返回最近访问时间(浮点型秒数) |
os.path.getmtime(path) | 返回最近文件修改时间 |
os.path.getctime(path) | 返回文件 path 创建时间 |
os.path.getsize(path) | 返回文件大小,如果文件不存在就返回错误 |
os.path.isabs(path) | 判断是否为绝对路径 |
os.path.isfile(path) | 判断路径是否为文件 |
os.path.isdir(path) | 判断路径是否为目录 |
os.path.islink(path) | 判断路径是否为链接 |
os.path.ismount(path) | 判断路径是否为挂载点 |
os.path.join(path1[, path2[, ...]]) | 把目录和文件名合成一个路径 |
os.path.normcase(path) | 转换 path 的大小写和斜杠 |
os.path.normpath(path) | 规范 path 字符串形式 |
os.path.realpath(path) | 返回 path 的真实路径 |
os.path.relpath(path[, start]) | 从 start 开始计算相对路径 |
os.path.samefile(path1, path2) | 判断目录或文件是否相同 |
os.path.sameopenfile(fp1, fp2) | 判断 fp1 和 fp2 是否指向同一文件 |
os.path.split(path) | 把路径分割成 dirname 和 basename,返回一个元组 |
os.path.splitext(path) | 分割路径,返回路径名和文件扩展名的元组 |
os.path.splitunc(path) | 把路径分割为加载点与文件 |
os.path.walk(path, visit, arg) | 遍历 path,进入每个目录都调用 visit 函数,visit 函数必须有 3 个参数(arg, dirname, names),dirname 表示当前目录的目录名,names 代表当前目录下的所有文件名,args 则为 walk 的第三个参数 |
os.path.supports_unicode_filenames | 设置是否支持 unicode 路径名 |
8.5 上下文管理语句---with
with
在实际的编码过程中,有时有一些任务,需要事先做一些设置,事后做一些清理,这时就需要 with 出场了,with 能够对这样的需求进行一个比较优雅的处理,最常用的例子就是对访问文件的处理。 不使用 with 语句打开文件时:
f = open("test.txt","r") f.read() f.close()
使用 with 语句打开文件时:
with open("test.txt","r") as f: # 注意格式缩进,这里可以使用的读写函数和普通open函数可以使用的读写函数是一样的 f.read()
如果文件能够正常读取,这两个语句的作用是相等的,如果在读取文件内容时报错了,这两者的差别就很明显了,不使用 with 语句打开的文件在报错之后f.close()
语句不会执行,但使用了 with 语句打开的文件在报错之后是会关闭文件的。
可以发现,with 语句省略了关闭文件这一步,而且,在代码发生报错之后也能关闭文件,比普通的 open() 函数更加简洁,且性能更加强大。
with 语句也可以同时打开多个文件。
with open("test.txt","w") as f,open("test1.txt","r") as f1: f.write("hello world") f1.read()
读写操作完成之后,两个文件都会被关闭。
8.6 二进制文件的读写
8.6.1 二进制文件
二进制文件是基于值编码的文件,你可以根据具体应用,指定某个值是什么意思。文本文件的编码基于字符定长,译码相对要容易一些;二进制文件编码是变长的,灵活利用率要高,而译码要难一些,不同的二进制文件译码方式是不同的。一般不能直接读懂二进制文件,只有通过相应的软件才能将其显示出来。二进制文件一般是可执行程序、图形、图像、声音文件等等。
8.6.2 二进制文件的读写
在之前的实训中,我们学习了打开二进制文件的方式,比如rb、wb
等。假设我们现在有一个图片文件test.png
。
with open("test.png","rb") as f: print(f.read()) print(f.read(10)) # 只读取10个字节
执行结果(不同的图片结果不一致):
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x02\x00\x00\x01X\x08\x06\x00\x00\x00\xb8Z\xf2\xc1\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\....' b'\x89PNG\r\n\x1a\n\x00\x00'
当我们写入二进制文件时,我们只能写入 bytes 类型的数据。
with open("test.png","rb") as f,open("result.png","wb") as fb: data = f.read(10) fb.write(data)
代码运行后,在当前文件夹中就会创建一个result.png
文件,该文件的内容为test.png
的前 10 个字节。
8.7 CSV模块读取文件
CSV (Comma Separated Values) 是逗号分隔符文本格式,常用于 Excel 和数据库的导入和导出,Python 标准库的 CSV 模块提供了读取和写入 CSV 格式文件的对象。CSV 以纯文本存储数和文本。文件的每一行就代表一条数据,每条记录包含由逗号分隔一个或多个属性值。
以文本的形式打开 csv 文件如图 1 所示:
图 1
以 Excel 的形式打开 csv 文件如图 2 所示:
图 2
8.7.1 读取CSV文件
假设我们有一个 test.csv 文件,内容如图 2 所示。
以返回列表的形式读取 csv 文件。
import csv # 导入 csv 模块 with open("test.csv","r") as f: result = csv.reader(f) # 返回的是一个迭代器 next(result) # 使用next可以跳过第一行的读取 print(list(result))
执行结果:
[['5.1', '3.5', '1.4', '0.2'], ['4.9', '3', '1.4', '0.2'], ['4.7', '3.2', '1.3', '0.2'], ['4.6', '3.1', '1.5', '0.2'], ['5', '3.6', '1.4', '0.2'], ['5.4', '3.9', '1.7', '0.4'], ['4.6', '3.4', '1.4', '0.3'], ['5', '3.4', '1.5', '0.2'], ['4.4', '2.9', '1.4', '0.2'], ['4.9', '3.1', '1.5', '0.1'], ['5.4', '3.7', '1.5', '0.2'], ['4.8', '3.4', '1.6', '0.2'], ['4.8', '3', '1.4', '0.1'], ['4.3', '3', '1.1', '0.1'], ['5.8', '4', '1.2', '0.2'], ['5.7', '4.4', '1.5', '0.4'], ['5.4', '3.9', '1.3', '0.4'], ['5.1', '3.5', '1.4', '0.3'], ['5.7', '3.8', '1.7', '0.3'], ['5.1', '3.8', '1.5', '0.3'], ['5.4', '3.4', '1.7', '0.2'], ['5.1', '3.7', '1.5', '0.4'], ['4.6', '3.6', '1', '0.2'], ['5.1', '3.3', '1.7', '0.5']]
以字典的形式读取 csv 文件。
import csv # 导入 csv 模块 with open("test.csv","r+") as f: # 使用DictReader创建的reader是一个字典对象,遍历后,不包含第一行数据 result = csv.DictReader(f) for x in result: print(x)
执行结果:
OrderedDict([('sepal length (cm)', '5.1'), ('sepal width (cm)', '3.5'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.9'), ('sepal width (cm)', '3'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.7'), ('sepal width (cm)', '3.2'), ('petal length (cm)', '1.3'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.6'), ('sepal width (cm)', '3.1'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '5'), ('sepal width (cm)', '3.6'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '5.4'), ('sepal width (cm)', '3.9'), ('petal length (cm)', '1.7'), ('petal width (cm)', '0.4')])OrderedDict([('sepal length (cm)', '4.6'), ('sepal width (cm)', '3.4'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.3')])OrderedDict([('sepal length (cm)', '5'), ('sepal width (cm)', '3.4'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.4'), ('sepal width (cm)', '2.9'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.9'), ('sepal width (cm)', '3.1'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.1')])OrderedDict([('sepal length (cm)', '5.4'), ('sepal width (cm)', '3.7'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.8'), ('sepal width (cm)', '3.4'), ('petal length (cm)', '1.6'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '4.8'), ('sepal width (cm)', '3'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.1')])OrderedDict([('sepal length (cm)', '4.3'), ('sepal width (cm)', '3'), ('petal length (cm)', '1.1'), ('petal width (cm)', '0.1')])OrderedDict([('sepal length (cm)', '5.8'), ('sepal width (cm)', '4'), ('petal length (cm)', '1.2'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '5.7'), ('sepal width (cm)', '4.4'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.4')])OrderedDict([('sepal length (cm)', '5.4'), ('sepal width (cm)', '3.9'), ('petal length (cm)', '1.3'), ('petal width (cm)', '0.4')])OrderedDict([('sepal length (cm)', '5.1'), ('sepal width (cm)', '3.5'), ('petal length (cm)', '1.4'), ('petal width (cm)', '0.3')])OrderedDict([('sepal length (cm)', '5.7'), ('sepal width (cm)', '3.8'), ('petal length (cm)', '1.7'), ('petal width (cm)', '0.3')])OrderedDict([('sepal length (cm)', '5.1'), ('sepal width (cm)', '3.8'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.3')])OrderedDict([('sepal length (cm)', '5.4'), ('sepal width (cm)', '3.4'), ('petal length (cm)', '1.7'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '5.1'), ('sepal width (cm)', '3.7'), ('petal length (cm)', '1.5'), ('petal width (cm)', '0.4')])OrderedDict([('sepal length (cm)', '4.6'), ('sepal width (cm)', '3.6'), ('petal length (cm)', '1'), ('petal width (cm)', '0.2')])OrderedDict([('sepal length (cm)', '5.1'), ('sepal width (cm)', '3.3'), ('petal length (cm)', '1.7'), ('petal width (cm)', '0.5')])
8.7.2 写入 csv 文件
以元组的方式写入。
import csv headers = ["name","age","height"] values = [("小王",18,178),("小张",20,180),("小李",17,166)] with open("test.csv","w",encoding="utf-8",newline="") as f: writer = csv.writer(f) writer.writerow(headers) # 首先写入第一行 writer.writerows(values) # 写入values
代码运行后,test.csv 的内容变更为:
name,age,height 小王,18,178 小张,20,180 小李,17,166
以字典的形式写入。
import csv headers = ["name", "age", "height"] values = [{"name":"小王","age":18,"height":178},{"name":"小王","age":18,"height":178},{"name":"小王","age":18,"height":178}] with open("test.csv","w",encoding="utf-8",newline="") as f: writer = csv.DictWriter(f,headers) # 使用csv.DictWriter()方法,需传入两个参数,第一个为对象,第二个为文件的title writer.writeheader() # 使用此方法,写入表头 writer.writerows(values)
代码运行之后,test.csv 文件的内容为:
name,age,height小王,18,178小张,20,180小李,17,166
8.8 EXCEL的读写
8.8.1 xlrd
xlrd 模块是用于读取 xls 等 excel 文件的读取。假设我们现在有如图 1 和 图 2 所示联系人.xls
文件。
图 1
图 2
打开 excel 文件并获取所有 sheet:
import xlrd data = xlrd.open_workbook('联系人.xls') # 打开Excel文件读取数据 sheet_name = data.sheet_names() # 获取所有sheet名称 print(sheet_name)
执行结果:
['银行2', '银行3']
根据 sheet 索引或者名称获取 sheet 内容,同时获取 sheet 名称、行数、列数:
import xlrd data = xlrd.open_workbook('联系人.xls') sheet1 = data.sheet_by_name('银行2') print('sheet1名称:{}\nsheet1列数: {}\nsheet1行数: {}'.format(sheet1.name, sheet1.ncols, sheet1.nrows)) sheet2 = data.sheet_by_index(1) print('sheet2名称:{}\nsheet2列数: {}\nsheet2行数: {}'.format(sheet2.name, sheet2.ncols, sheet2.nrows))
执行结果:
sheet1名称:银行2 sheet1列数: 8 sheet1行数: 6 sheet2名称:银行3 sheet2列数: 7 sheet2行数: 5
根据 sheet 名称获取整行和整列的值:
import xlrd data = xlrd.open_workbook('联系人.xls') sheet1 = data.sheet_by_name('银行2') print(sheet1.row_values(3)) # 获取第3行的所有值 print(sheet1.col_values(3)) # 获取第3列的所有值
执行结果:
['', '张2', '开发', 'IT编码', 999.0, 133111.0, 41463.0, 'zhang2@164.com'] ['', '工作职责', '', 'IT编码', '网络维修', '']
获取指定单元格的内容:
import xlrd data = xlrd.open_workbook('联系人.xls') sheet1 = data.sheet_by_name('银行2') # 三种方式 print(sheet1.cell(1,0).value) # 第2行1列内容:机构名称 print(sheet1.cell_value(1,0)) # 第2行1列内容:机构名称 print(sheet1.row(1)[0].value) # 第2行1列内容:机构名称
获取单元格内容的数据类型:
import xlrd data = xlrd.open_workbook('联系人.xls') sheet1 = data.sheet_by_name('银行2') print(sheet1.cell(1,0).ctype) # 第2行1列内容:机构名称为string类型 print(sheet1.cell(3,4).ctype) # 第4行5列内容:999为number类型 print(sheet1.cell(3,6).ctype) # 第4行7列内容:2013/7/8为date类型
获取合并单元格的内容:
# 这里需要在读取文件的时候添加个参数,将formatting_info参数设置为True,默认是False,否则可能调用merged_cells属性获取到的是空值。 import xlrd data = xlrd.open_workbook('联系人.xls',formatting_info=True) sheet1 = data.sheet_by_name('银行2') print(sheet1.merged_cells) # 打印[(0, 1, 0, 8), (2, 6, 0, 1)] # merged_cells返回的这四个参数的含义是:(row,row_range,col,col_range),其中[row,row_range)包括row,不包括row_range,col也是一样,下标从0开始。 # (0, 1, 0, 8) 表示1列-8列合并 (2, 6, 0, 1)表示3行-6行合并 # 分别获取合并2个单元格的内容: print(sheet1.cell(0,0).value) # 银行2 print(sheet1.cell_value(2, 0)) # 银行2
8.8.2 xlwt
xlwt 模块是用于写入 excel 文件的模块,下面我们简单介绍该模块的基本使用:
简单使用 xlwt:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') #创建workbook对象 worksheet = workbook.add_sheet('sheet1') #创建工作表sheet worksheet.write(0, 0, 'hello') #往表中写内容,第一个参数行,第二个参数列,第三个参数为内容 workbook.save('students.xls') #保存表为students.xls
为内容设置 style:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') #设置字体样式 font = xlwt.Font() #字体 font.name = 'Time New Roman' #加粗 font.bold = True #下划线 font.underline = True #斜体 font.italic = True #创建style style = xlwt.XFStyle() style.font = font #根据样式创建workbook worksheet.write(0, 1, 'world', style)
合并单元格:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') #合并第0行第0列到第0行第2列的单元格,第二个参数表示单元格的内容 worksheet.write_merge(0, 0, 0, 2, 'first merge') #合并第0行第1行第2行第1列的单元格 worksheet.write_merge(0, 1, 2, 1, 'first merge')
如果需要了解具体调合并单元格规则就自己试着合并,查看合并效果才能清晰明了。
设置单元格的对齐方式:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') alignment = xlwt.Alignment() # 水平居中 alignment.horz = xlwt.Alignment.HORZ_CENTER # 垂直居中 alignment.vert = xlwt.Alignment.VERT_CENTER style = xlwt.XFStyle() style.alignment = alignment #设置单元格宽度 worksheet.col(0).width = 6666 #设置单元格的高度 worksheet.row(0).height_mismatch = True worksheet.row(0).height = 1000 worksheet.write(0, 0, 'hello world', style)
设置单元格边框:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') border = xlwt.Borders() # DASHED虚线 # NO_LINE没有 # THIN实线 border.left = xlwt.Borders.THIN border.right = xlwt.Borders.THIN border.top = xlwt.Borders.THIN border.bottom = xlwt.Borders.THIN style = xlwt.XFStyle() style.borders = border worksheet.write(1, 1, 'love', style)
设置单元格背景色:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') pattern = xlwt.Pattern() pattern.pattern = xlwt.Pattern.SOLID_PATTERN # 0 = Black, 1 = White, # 2 = Red, 3 = Green, 4 = Blue, # 5 = Yellow, 6 = Magenta, 7 = Cyan, # 16 = Maroon, 17 = Dark Green, # 18 = Dark Blue, 19 = Dark Yellow , # almost brown), 20 = Dark Magenta, # 21 = Teal, 22 = Light Gray, # 23 = Dark Gray pattern.pattern_fore_colour = 3 # 将单元格背景色设置为 Green style = xlwt.XFStyle() style.pattern = pattern worksheet.write(1, 1, 'shit', style) workbook.save('shit.xls')
设置字体颜色:
import xlwt workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('sheet1') font = xlwt.Font() # 设置字体为红色 font.colour_index=xlwt.Style.colour_map['red'] style = xlwt.XFStyle() style.font = font worksheet.write(0, 1, 'world', style)
如果想要更加了解每一步的实际意义,建议在本地编写上述代码,可以及时查看表格信息的变化。
9.1 try...expect...
9.1.1 异常
我们在编写代码时,难免会出现代码写错或者方法、参数使用错误的情况,在运行代码的时候,也有可能发生错误,发生的错误也就是我们所说的异常。下面我们列举几个常见的异常:
ImportError,无法引入模块或包,大部分是路径问题或名称错误;
IOError,输入输出异常,大部分是无法打开文件;
TypeError,传入对象类型与要求的不符合;
NameError,使用一个还未被赋予对象的变量;
ValueError,传入一个调用者不期望的值,即使值的类型是正确的;
KeyError,尝试访问字典里不存在的键;
SystemError,一般的解释器系统错误。
9.1.2 异常处理
考虑到程序的健壮与容错性,我们需要对潜在的异常进行处理,防止因异常而导致的程序崩溃。最常用的的异常处理方式就是使用 try…except… 语句。
try: 代码块1 except: 代码块2
当执行代码块 1 发生错误时,停止运行代码块 1,开始运行代码块 2。except 后也可以指定异常类型。
try: num = "1" + 1 # 会发生TypeError错误 print(num) except TypeError: # 只有发生TypeError时才运行except子句 print("发生TypeError错误") try: num = 10 * (1/0) # 不会发生TypeError错误 print(num) except TypeError: print("发生TypeError错误")
执行结果:
发生TypeError错误 Traceback (most recent call last): File "sync_file.py", line 8, in <module> num = 10 * (1/0) ZeroDivisionError: division by zero
由于没有匹配到指定的错误,所以程序还是报错了。
这些还不足以满足我们的需求,在一些自动化的代码中,我们想让代码生成错误日志,保存详细的错误信息,但是又不会停止代码的运行,这时,我们可以使用下列代码:
try: num = "1" + 1 # 会发生TypeError错误 print(num) except TypeError as f: print("发生TypeError错误,错误内容为:%s"%f)
执行结果:
发生TypeError错误,错误内容为:can only concatenate str (not "int") to str
9.2 try...expect...else...
我们之前学过 if-else 语句,如果 if 条件成立,则运行 if 子句,如果不成立,则运行 else 子句;try…except… 语句也有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。
try: num = "1" + 1 # 会发生TypeError错误 print(num) except TypeError: # 只有发生TypeError时才运行except子句 print("发生TypeError错误") else: print("没有发生TypeError错误") try: num = 10 * 1 # 不会发生TypeError错误 print(num) except TypeError: print("发生TypeError错误") else: print("没有发生TypeError错误")
执行结果:
发生TypeError错误 10 没有发生TypeError错误
从执行结果看出,如果 except 子句执行了,else 语句内的语句就不会执行;如果 try 子句没有发生错误,则运行 else 语句的内容。
9.3 try...expect...finally...
try…except… 语句之后除了可以添加 esle 语句之外,还可以添加 finally 语句,try…except…finally… 语句无论是否发生异常都将执行的代码。如图 1 所示。
图 1
下面我们来举一个例子:
try: num = "1" + 1 # 会发生TypeError错误 print(num) except TypeError: # 只有发生TypeError时才运行except子句 print("发生TypeError错误") finally: print("执行finally子句") try: num = 10 * 1 # 不会发生TypeError错误 print(num) except TypeError: print("发生TypeError错误") finally: print("执行finally子句")
执行结果:
发生TypeError错误 执行finally子句 10 执行finally子句
从执行结果发现,finally 子句不管 except 子句是否执行,它都是会运行的。
9.4 多异常处理
在之前的异常处理中,我们都只使用了一个 except 语句,如果我们想要针对不同的异常给出不同的处理方法之时,我们就可以使用多个 except 语句来处理。
try: 执行语句 except IOError: print("发生了输入/输出异常") except KeyError: print("发生了键访问异常") except SyntaxError: print("发生了 Python 语法异常")
如果执行语句发生了 IOError 则输出“发生了输入/输出异常”,如果发生了 KeyError 则输出“发生了键访问异常”,如果发生了 SyntaxError 则输出“发生了 Python 语法异常”。
如果我们想针对多个异常类型做出相同的处理方法时,我们可以使用下列语句。
# 如果执行语句发生了两个异常类型中的一个,则运行except子句 try: 执行语句except (IOError,KeyError): print("发生了IOError或者KeyError异常")
9.5 综合异常处理
之前的实训中,我们了解过 try…except… 之后可以使用 else 和 finally 来实现一些特定的功能。它们之间是可以共存的,使用方式如下:
f = open("test.txt","w") try: f.read() except: print("读取文件时发生了异常") else: print("读取文件成功") finally: f.close()
如果在文件读取内容时发生异常,则打印“读取文件时发生了异常”,然后运行 finally 子句来关闭文件;如果文件读取时没有发生异常,则打印“读取文件成功”,然后运行 finally 子句来关闭文件。
10.1 自定义模块
10.1.1 模块的概念
在 Python 程序的开发过程中,为了代码维护的方便,我们可以把函数进行分组,分别放到不同的.py
文件里。这样,每个文件包含的代码就相对较少,这个.py
文件就称之为一个模块(Module
),也就是说,我们之前每一个关卡所用到的代码文件都可以作为一个模块。
模块能够让我们有逻辑地组织 Python 代码段,可以降低代码的耦合度。模块中能够定义函数、类和变量,模块里也可以包含可执行的代码。
在之前的实训中,我们介绍过 Python 模块的三种导入方式;模块的安装方式也非常简单,通常情况下,我们在命令行中输入以下命令即可。
pip install 模块名pip install 模块名==版本号 # 这种方式可以指定被安装的模块版本号
安装成功之后,我们即可在代码中导入该模块。
10.1.2 自定义模块的使用
每个 Python 文件都可以看作一个模块,模块的名字就是 Python 文件的名字。所以我们完全可以自己写一个 Python 文件,作为自己定义的模块。例如,我们编写了my_module.py
文件,里面定义了plus()
函数:
# my_module.py def plus(a,b): return a+b
之后我们就可以在其他 Python 文件中来使用该文件的代码:
# other_module.py import my_module my_module.plus(5,10) # 调用my_module中的plus方法
先通过import my_module
引入 my_module 模块,然后通过my_module.plus(a,b)
来调用my_module.py
文件中的plus()
函数。我们也可以直接通过from my_module import plus
来导入plus()
函数。
# other_module.py from my_module import plus plus(5,10)
10.1.3 模块的搜索路径
导入过程首先需要定位导入文件的位置,也就是,告诉 Python 到何处去找到要导入的文件,因此,需要设置模块的搜索路径。在大多数情况下,Python 会自动到默认的目录下去搜索模块;如果要在默认的目录之外导入模块,就需要知道 Python 搜索模块路径的机制。
Python 搜索模块的路径是由四部分构成的:程序的主目录、PATHONPATH 目录、标准链接库目录和.pth
文件的目录,这四部分的路径都存储在 sys.path 列表中。
程序的主目录 主目录是指包含程序的顶层脚本的目录,Python 首先会到主目录中搜索模块。因为主目录总是第一个被搜索,如果模块完全处于主目录中,所有的导入都会自动完成,而不需要单独配置路径。
PATHONPATH 目录 PATHONPATH 目录是指 PATHONPATH 环境变量中配置的目录,是第二个被搜索的目录,Python 会从左到右搜索 PATHONPATH 环境变量中设置的所有目录。
标准链接库目录 标准链接库目录是 Python 按照标准模块的目录,是在安装 Python 时自动创建的目录,通常不需要添加到 PYTHONPATH 目录中。
路径文件(.pth 文件) 在模块搜索目录中,创建路径文件,后缀名为.pth
,该文件每一行都是一个有效的目录。Python 会读取路径文件中的内容,每行都作为一个有效的目录,加载到模块搜索路径列表中。简而言之,当路径文件存放到搜索路径中时,其作用和 PYTHONPATH 环境变量的作用相同。
如果运行在 Windows 和 Python3.0 中,Python 安装目录的顶层是 C:\Python30,那么可以把自定义的路径文件 mypath.pth 放到该目录中,也可以放到标准库所在位置的 sitepackages 子目录中(C:\Python30\Lib\sitepackages),来扩展模块的搜索路径。
10.2 模块属性
10.2.1 name 和 main
假设我们现在有下列两个文件及代码:
# 文件名:test.py num1 = 1 num2 = 2 def add(num1,num2): return num1+num2 print(add(num1,num2)) # 文件名:run.py import test print(test.add(10,11))
执行 run.py 文件,执行结果:
3 21
从执行结果可以发现,test.py 中的代码在导入时也运行了一遍,但是我们似乎不需要运行 test.py 中的全部代码,只需要使用到其中的 add 函数。面对这种问题时,我们就可以使用 name 和 main 属性。
# 文件名:test.py def add(num1,num2): return num1+num2 if __name__ == "__main__": num1 = 1 num2 = 2 print(add(num1,num2)) # 文件名:run.py import test print(test.add(10,11))
执行 run.py 文件,执行结果:
21
每个模块都有一个 name 属性,当其值是 ‘main‘ 时,表明该模块运行的是自己的代码,否则是被引入。
注意:当某个 .py 文件会被其它文件引用时,一定要在该 py 文件中使用 name 属性。
10.3 包的使用
包是一种管理 Python 模块命名空间的形式,采用”点模块名称”。比如一个模块的名称是 A.B, 那么他表示一个包 A 中的子模块 B ,比如 os 模块中的子模块 os.path。就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。
为了组织好模块,会将多个模块分为包。Python 处理包也是相当方便的。简单来说,包就是文件夹,但该文件夹下必须存在 init.py 文件。假设我们现在有如图 1 所示的包结构。
图 1
我们可以使用下列代码来调用 package_a 包中的 module_a1 模块:
from package_a import module_a1
包中还有可以有其它包,假设 package_a 包是 package 的子包,我们可以通过下列代码来调用:
from package.package_a import module_a1
注意:当使用 from package import module 这种形式的时候,对应的 module 既可以是包里面的子模块(子包),也可以是包里面定义的其他名称,比如函数,类或者变量。
在之前的实训中,我们了解过from package import *
是导入 package 包或者模块下的所有内容,但是我们引入了包的概念后,就可以对这一句代码的作用进行修改。如果包定义文件 init.py 存在一个叫做 all 的列表变量,那么在使用from package import *
的时候就把这个列表中的所有名字作为包内容导入。
# __init__.py __all__ = ["module_a1"]
定义了上述代码之后,我们使用from package_a import *
就只会导入 module_a1 这一个模块。通常我们并不主张使用 * 这种方法来导入模块,因为这种方法经常会导致代码的可读性降低。