文章目录
6.函数
在实际开发中,如果有若干段代码的执行逻辑完全相同,那么可以考虑将这些代码抽取成一个函数,这样不仅可以提高代码的重用性,而且条理会更加清晰,可靠更高。
6.1:函数概述
函数是组织好的、实现单一功能或相关联功能的代码段。我们可以将函数视为一段有名字的代码,这类代码可以在需要的地方以"函数名()"的形式调用。
函数式编程具有以下优点:
1:将程序模块化,既减少了冗余代码,又让程序结构更为清晰。
2:提高开发人员的编程效率。
3:方便后期的维护与扩展。
6.2:函数的定义和调用
6.2.1:定义函数
在Python中,可以定义个自己想要功能的函数,自定义函数的语法格式如下:

定义一个计算两个数之和的函数。
无参数方式
python
def add():
result=11+22
print(result)
有参数方式
python
def add_modify(a,b):
result=a+b
print(result)
6.2.2:调用函数
函数在定义完成后不会立刻执行,直到被程序调用时才会执行。
语法格式如下:
python
函数名([参数列表])
调用示例:
python
add()
add_modify(10,20)
原理图如下:

- 程序在调用函数的位置暂停执行。
- 将数据传递给函数参数。
- . 执行函数体中的语句。
- 程序回到暂停处继续执行。
嵌套调用:函数内部也可以调用函数,我们称之为嵌套调用,示例代码如下
python
#函数1
def add():
result = 11+22
print(result)
#函数2
def add_modify(a, b):
result = a + b
add() #嵌套调用函数1
print(result)
#调用函数2
add_modify(10,20)
原理图如下:

嵌套定义:函数在定义时可以在其内部嵌套定义另外一个函数,此时嵌套的函数称为外层函数,被嵌套 的函数称为内层函数。
python
def func(a,b):
result=a+b
def add():
r=10+20
print(r)
print(result)
# 位置参数
func(11,88)
注意:
1)函数外部无法直接调用内层函数
2)只能通过外层函数间接调用内层函数
6.3:函数参数传递
我们通常将定义函数时设置的参数称为形式参数(简称为形参),将调用函数时传入的参数称为实际参数(简称为实参)。函数的参数传递是指将实际参数传递给形式参数的过程。
函数参数的传递可以分为位置参数传递、关键字参数传递、默认参数传递、参数的打包与解包以及混合传递。
6.3.1:位置参数的传递
函数在被调用时会将实参按照相应的位置依次传递给形参,也就是说将第一个实参传递给第一个形参,将第二个实参传递给第二个形参,以此类推。
定义函数比较两数大小,代码示例:
python
def add(a,b):
result=a+b
print(result)
add(10,20)
# 运行结果
30
6.3.2:关键字参数的传递
关键字参数的传递是通过"形参=实参"的格式将实参与形参相关联,将实参按照相应的关键字传递给形参。
python
def func(ip,port):
print(f'连接主机地址{ip},端口{port}')
func(ip='10.0.0.1',port=8080)
# 运行结果
连接主机地址10.0.0.1,端口8080
扩展:无论实参采用位置参数的方式传递,还是关键字参数的方式传递,每个形参都是有名称的,怎么区分用哪种方式传递呢?
python
使用"/"前面的必须为位置参数
使用"*"后面的必须为关键字参数
Python从3.8版本开始新增了仅限位置形参的语法,使用符号"/"和"*"来限定部分形参只接收采用位置传 递方式的实参。
python
def func(a,/,b,*,c):
print(a,b,c)
func(10,20,c=30)
func(10,b=20,c=30)
# 运行结果
10 20 30
10 20 30
总结:b既可以为位置参数,也可以为关键字参数。
6.3.3:默认参数的传递
函数在定义时可以指定形参的默认值,如此在被调用时可以选择是否给带有默认值的形参传值,若没有给带有默认值的形参传值,则直接使用该形参的默认值。
python
def func (ip,port=3306):
print(f'连接主机地址{ip},端口{port}')
func (ip='10.0.0.1')
func (ip='10.0.0.1',port=8080)
# 运行结果
连接主机地址10.0.0.1,端口3306
连接主机地址10.0.0.1,端口8080
6.3.4:参数的打包与解包
1)打包
如果函数在定义时无法确定需要接收多少个数据,那么可以在定义函数时为形参添加"*"或"**":
python
"*" 接收以元组形式打包的多个值
"**" 接收以字典形式打包的多个值
元组形式打包代码示例:
python
def func(*args):
print(type(args))
for item in args:
print(item)
func(1,2,3,4,5,6,7,8,9)
# 运行结果
1
2
3
4
5
6
7
8
9
字典形式打包代码示例:
python
def func(**kwargs):
print(type(kwargs))
for item in kwargs:
print(item) #只输出键名
for item in kwargs.items(): #输入键名和值
print(item)
func(a=1,b=2,c=3)
# 运行结果
<class 'dict'>
a
b
c
('a', 1)
('b', 2)
('c', 3)
总结:
1:虽然函数中添加"*"和"**"的形参可以是符合命名规范的任意名称,但这里建议使用 * args和 * *kwargs。
2:若函数没有接收到任何数据,参数 * args 和 * * kwargs为空,即它们为空元组和空字典。
2)解包
实参是元组 → 可以使用 * 拆分成多个值 → 按位置参数传给形参
实参是字典 → 可以使用 * * 拆分成多个键值对 → 按关键字参数传给形参
python
tuple_one=(10,20,30,40)
dict_one={'A':1,'B':2,'C':3,'D':4}
def func(A,B,C,D):
print(A,B,C,D)
func(*tuple_one)
func(**dict_one)
# 运行结果
10 20 30 40
1 2 3 4
6.3.5:混合传递
前面介绍的参数传递的方式在定义函数或调用函数时可以混合使用,但是需要遵循一定的规则,具体规则如下。
- 优先按位置参数传递的方式。
- 然后按关键字参数传递的方式。
- 之后按默认参数传递的方式。
- 最后按打包传递的方式。
在定义函数时:
- 带有默认值的参数必须位于普通参数之后。
- 带有 * 标识的参数必须位于带有默认值的参数之后。
- 带有 * * 标识的参数必须位于带有 * 标识的参数之后。
混合参数传递代码示例:
python
def test(a,b,c=99,*args,**kwargs):
print(a,b,c,args,kwargs)
test(1,2)
test(1,2,3)
test(1,2,3,4)
test(1,2,3,4,e=5)
# 运行结果
1 2 99 () {}
1 2 3 () {}
1 2 3 (4,) {}
1 2 3 (4,) {'e': 5}
6.4:函数的返回值
函数中的return语句会在函数结束时将数据返回给程序,同时让程序回到函数被调用的位置继续执行。
python
def test(words:str) -> str:
if '翻墙' in words:
result=words.replace('翻墙','技术工具')
print(result)
print(test(input('请输入一句话:')))
# 运行结果
请输入一句话:我会翻墙
我会技术工具
None
如果函数使用return语句返回了多个值,那么这些值将被保存到元组中。
python
def test(a:int,b:int):
result1=a+b
result2=a*b
return result1,result2
k,v=test(10,20)
print(f'两数之和:{k}')
print(f'两数之积:{v}')
result=test(1,3)
print(result,type(result))
# 运行结果
两数之和:30
两数之积:200
(4, 3) <class 'tuple'>
6.5:变量作用域
变量并非在程序的任意位置都可以被访问,其访问权限取决于变量定义的位置,其所处的有效范围称为 变量的作用域。
6.5.1:局部变量和全局变量
根据作用域的不同,变量可以划分为局部变量和全局变量。
1)局部变量
- 函数内部定义的变量,只能在函数内部被使用
- 函数执行结束之后局部变量会被释放,此时无法再进行访问。
例如,在test()函数中定义一个局部变量number,分别在该函数内和函数外访问number,代码所示:
python
def test():
num=10
print(num)
test()
print(num)
# 运行结果
# 报错
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\函数\变量作用范围.py", line 6, in <module>
print(num) #函数外部访问
^^^
NameError: name 'num' is not defined. Did you mean: 'sum'?
不同函数内部可以包含同名的局部变量,这些局部变量的关系类似于不同目录下同名文件的关系,它们 相互独立,互不影响。
python
def test():
num=10
print(num)
test()
# 运行结果
10
2)全局变量
全局变量可以在整个程序的范围内起作用,可以在程序的任意位置被访问,它不会受到函数范围的影响。
python
sum=100
def test():
print(sum)
test()
print(sum)
# 运行结果
100
100
强调:全局变量在函数内部只能被访问,而无法直接修改。
python
sum=100
def test():
sum+=1
print(sum)
test()
print(sum)
# 运行结果
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\函数\变量作用范围.py", line 15, in <module>
test()
~~~~^^
File "D:\PycharmProjects\PythonProject\.venv\scr\函数\变量作用范围.py", line 12, in test
sum+=1
^^^
UnboundLocalError: cannot access local variable 'sum' where it is not associated with a value
报错原因:这是因为函数内部的变量number视为局部变量,而在执行"number+=1"这行代码之前并未 声明过局部变量number。
扩展:LEGB原则
LEGB是程序中搜索变量时所遵循的原则,该原则中的每个字母指代一种作用域:
- L-local:局部作用域。例如,局部变量和形参生效的区域。
- E-enclosing:嵌套作用域。例如,嵌套定义的函数中外层函数声明的变量生效的区域。
- G-global:全局作用域。例如,全局变量生效的区域。
- B-built-in:内置作用域。例如,内置模块声明的变量生效的区域。
Python在搜索变量时会按照"L-E-G-B "这个顺序依次在这四种区域中搜索变量:若搜索到变量则终止搜 索,使用搜索到的变量;若搜索完L、E、G、B这四种区域仍无法找到变量,程序将抛出异常。
代码示例:
python
import math
g_num=10 #g_number,全局作用域为整个程序任意位置
def outer():
e_num=20 #e_number,嵌套作用域为outer()和inner()范围
def inner():
l_num=30 #l_number,局部作用域为inner()范围
print(l_num)
print(math.pi) #内置作用域,内置模块中声明
inner()
print(e_num)
outer()
print(g_num)
# 运行结果
30
3.141592653589793
20
10
6.5.2:global和nonlocal关键字
函数内部无法直接修改全局变量或在嵌套函数的外层函数声明的变量,但可以使用global或nonlocal关 键字修饰变量以间接修改以上变量。
1)global关键字
使用global关键字可以将局部变量声明为全局变量,其使用方法如下:
python
sum=100 #全局变量
def test():
global sum # 声明全局变量
sum+=1
print(sum)
test()
print(sum)
# 运行结果
101
101
2)nonlocal关键字
使用nonlocal关键字可以在局部作用域中修改嵌套作用域中定义的变量,其使用方法如下:
python
def outer():
num=10
def inner():
nonlocal num
num+=1
print(num)
inner()
print(num)
outer()
# 运行结果
11
11
6.6:实训案例
6.6.1:角谷猜想
角谷猜想又称冰雹猜想,是由日本数学家角谷静夫发现的一种数学现象,具体内容:以一个正整数n为 例,如果n为偶数,就将它变为n/2,如果除后变为奇数,则将它乘3加1(即3n+1)。不断重复这样的 运算,经过有限步后,必然会得到1。
本实例要求编写代码,计算用户输入的数据按照以上规律经多少次运算后可变为1。
python
def odd_num(n):
n=3*n+1
return n
def even_num(n):
n=n/2
return n
num=int(input('请输入一个正整数:'))
count=0
while (num!=1):
if num%2==0:
num=even_num(num)
count+=1
else:
num=odd_num(num)
count+=1
print(f'一共计算{count}次')
6.7:特殊形式的函数
除了前面按标准定义的函数外,Python还提供了两种具有特殊形式的函数:递归函数和匿名函数。
6.7.1:递归函数
函数在定义时可以直接或间接地调用其他函数。若函数内部调用了自身,则这个函数被称为递归函数。 递归函数在定义时需要满足两个基本条件:一个是递归公式,另一个是边界条件。其中:
- 递归公式是求解原问题或相似的子问题的结构。
- 边界条件是最小化的子问题,也是递归终止的条件。
递归函数的执行可以分为以下两个阶段:
-
递推:递归本次的执行都基于上一次的运算结果。
-
回溯:遇到终止条件时,则沿着递推往回一级一级地把值返回来。
递归函数的一般定义格式如下所示:
python
def 函数名([参数列表]):
if 边界条件:
rerun 结果
else:
return 递归公式
递归经典案例:
阶乘n!:
n! = 1 * 2 * 3 * ... * n,可以分为以下两种情况:
- 当n=1时,所得的结果为1。
- 当n>1时,所得的结果为n*(n-1)!。
python
def func(num):
if num == 1:
return 1
else:
return num * func(num-1)
num = int(input('请输入一个整数:'))
result = func(num)
print(f'{num}!=%d' %result)
6.7.2:匿名函数
匿名函数是一类无需定义标识符的函数,它与普通函数一样可以在程序的任何位置使用。Python中使用lambda关键字定义匿名函数,它的语法格式如下:
python
lambda <形式参数列表> :<表达式>
匿名函数与普通函数的主要区别如下:
- 普通函数在定义时有名称,而匿名函数没有名称;
- 普通函数的函数体中包含有多条语句,而匿名函数的函数体只能是一个表达式;
- 普通函数可以实现比较复杂的功能,而匿名函数可实现的功能比较简单;
- 普通函数能被其他程序使用,而匿名函数不能被其他程序使用。
定义好的匿名函数不能直接使用,最好使用一个变量保存它,以便后期可以随时使用这个函数。
python
#定义匿名函数,并将它返回的函数对象赋值给变量temp
temp = lambda x : pow(x,2) #pow()为幂运算函数
# 此时,变量temp可以作为匿名函数的临时名称来调用函数
print(temp(10))
# 运行结果
100
多参数传递:
python
temp = lambda x,y : x+y
print(temp(10,20))
# 运行结果
30
6.8:项目案例**-**学生管理系统
本案例要求开发一个具有添加、删除、修改、查询学生信息及退出系统功能的简易版的学生管理系统。
习题,1:录入成绩 2:查询成绩 3:修改成绩 4:删除成绩 5:退出系统
python
score=[['张三',80,83,92],['李四',67,83,73]]
while True:
print("1:录入成绩\n2:查询成绩\n3:修改成绩\n4:删除成绩\n5:退出系统")
print()
num1=int(input("请输入选项:"))
if num1==1:
person_list=[]
name=input("请输入要录入成绩的学生姓名:")
person_list.append(name)
doing=True
for i in score:
if i[0]==name:
print("姓名已存在,请重新输入")
doing=False
if doing:
person_list.append(int(input("请输入语文成绩:")))
person_list.append(int(input("请输入数学成绩:")))
person_list.append(int(input("请输入英语成绩:")))
score.append(person_list)
print("成绩录入完成")
print(score)
elif num1==2:
print("1:查询全部学生成绩\n2:查询学生个人成绩")
num2=int(input())
if num2==1:
for i in score:
print("姓名:",i[0])
print("语文成绩为:", i[1])
print("数学成绩:", i[2])
print("英语成绩为:", i[3])
print()
if num2==2:
print("请输入姓名:")
name=input()
for i in score:
if name==i[0]:
print("语文成绩为:",i[1])
print("数学成绩:",i[2])
print("英语成绩为:",i[3])
print()
elif num1==3:
print("请输入要修改成绩的学生姓名:")
name=input()
for i in score:
if name==i[0]:
print("请输入要修改的学科:")
subject=input()
if subject=='语文':
i[1]=input('成绩为:')
print(f'{name}的语文成绩为:',i[1])
print()
if subject=='数学':
i[2]=input('成绩为:')
print(f'{name}的数学成绩为:', i[2])
print()
if subject=='英语':
i[3]=input('成绩为:')
print(f'{name}的英语成绩为:', i[3])
print()
elif num1==4:
print("请输入要删除成绩的学生姓名:")
name=input()
for i in score:
if name== i[0]:
score.remove(i)
print('删除成功')
elif num1==5:
print("退出系统")
break
第7章:文件与数据格式化
程序中使用变量保存运行时产生的临时数据,程序结束后,临时数据随之消失。但一些程序中的数据需 要持久保存,例如游戏程序中角色的属性数据,装备数据,物品数据等。那么使用什么方法能够持久保 存数据呢?计算机可以使用文件持久地保存数据。本章将从计算机中文件的定义、基本操作、管理方式 与数据维度等多个方面对计算机中与数据持久存储相关的知识进行介绍。
7.1:文件概述
文件在计算机中应用广泛,计算机中的文件是以硬盘等外部介质为载体,存储在计算机中的数据的集 合,文本文档、图片、程序、音频等都是文件。
类似于程序中使用的变量,计算机中的每个文件也有唯一确定的标识,以便识别和引用文件。
1)文件标识
- 文件标识的意义:找到计算机中唯一确定的文件。
- 文件标识的组成:文件路径、文件名主干、文件扩展名。

- 操作系统以文件为单位对数据进行管理。
2)文件类型
根据数据的逻辑存储结构,人们将计算机中的文件分为文本文件和二进制文件。
-
文本文件:专门存储文本字符数据(使用记事本)。
-
二进制文件:不能直接使用文字处理程序正常读写,必须先了解其结构和序列化规则,再设计正确的反序列化规则,才能正确获取文件信息。
总结:二进制文件和文本文件这两种类型的划分基于数据逻辑存储结构而非物理存储结构,计算机中的数据在物理层面都以二进制形式存储。
扩展:标准文件
Python的sys模块中定义了3个标准文件,分别为:
-
stdin(标准输入文件)。标准输入文件对应输入设备,如键盘。
-
stdout(标准输出文件)。
-
stderr(标准错误文件)。标准输出文件和标准错误文件对应输出设备,如显示器。
在解释器中导入sys模块后,便可对标准文件进行操作。
python
import sys
file = sys.stdout
file.write('lucky cloud')
7.2:文件的基础操作
文件的打开、关闭与读写是文件的基础操作,任何更复杂的文件操作都离不开这些操作。
7.2.1:文件的打开与关闭
将数据写入到文件之前需要先打开文件;数据写入完毕后需要将文件关闭以释放计算机内存。
1)打开文件
内置函数open()用于打开文件,该方法的声明如下:
python
open(file, mode='r', buffering=None)
- file:文件的路径。
- mode:设置文件的打开模式,取值有r、w、a。
- r:以只读方式打开文件(mode参数的默认值)。
- w:以只写方式打开文件。
- a:以追加方式打开文件。
- b:以二进制形式打开文件。
- +:以更新的方式打开文件(可读可写)
- buffering:设置访问文件的缓冲方式。取值为0或1。
| 打开模 式 | 名称 | 描述 |
|---|---|---|
| r/rb | 只读模式 | 以只读的形式打开文本文件/二进制文件,若文件不存在或无法找 到,文件打开失败 |
| w/wb | 只写模式 | 以只写的形式打开文本文件/二进制文件,若文件已存在,则重写 文件,否则创建新文件 |
| a/ab | 追加模式 | 以只写的形式打开文本文件/二进制文件,只允许在该文件末尾追 加数据,若文件不存在,则创建新文件 |
| r+/rb+ | 读取(更 新)模式 | 以读/写的形式打开文本文件/二进制文件,若文件不存在,文件打 开失败 |
| w+/wb+ | 写入(更 新)模式 | 以读/写的形式打开文本文件/二进制文件,若文件已存在,则重写 文件 |
| a+/ab+ | 追加(更新)模式 | 以读/写的形式打开文本/二进制文件,只允许在文件末尾添加数 据,若文件不存在,则创建新文件 |
返回值:若open()函数调用成功,返回一个文件对象。
python
file1 = open('f:\\a.txt') #以只读方式打开f盘的文本文件a.txt
file2 = open('f:\\b.txt','w') #以只写方式打开f盘的文本文件b.txt,若文件不存在则自动创建
新文件
file3 = open('f:\\c.txt','w+') #以读/写方式打开f盘的文本文件c.txt,若文件不存在则自动创建
新文件
file4 = open('f:\\d.txt','wb+') #以读/写方式打开f盘的二进制文件d.txt,若文件不存在则自动创
建新文件
若a.txt问价不存在,则会抛出异常
2)关闭文件
Python可通过close()方法关闭文件,也可以使用with语句实现文件的自动关闭。
1:close()方法,是文件对象的内置方法。
python
file.close()
2:with语句,可预定义清理操作,以实现文件的自动关闭。
python
with open('f:\\a.txt') as f:
print('文件关闭')
pass
以上示例中as后的变量f用于接收with语句打开的文件对象。程序中无须再调用close()方法关闭文件,文件对象使用完毕后,with语句会自动关闭文件。
思考:为什么要及时关闭文件?
- 计算机中可打开的文件数量是有限。
- 打开的文件占用系统资源。
- 若程序因异常关闭,可能产生数据丢失。
7.2.2:文件的读写
Python提供了一系列读写文件的方法,包括读取文件的read()、readline()、readlines()方法和写文件的write()、writelines()方法,下面结合这些方法分别介绍如何读写文件。
1)读取文件-read()方法
read()方法可以从指定文件中读取指定字节的数据,其语法格式如下:
python
read(size)
size:表示要从文件中读取的数据的长度,单位:字节,如果没有指定size,那么就表示读取文件的全部数据。
读取f盘中.txt的数据,代码示例如下
方法一:
python
file=open('D:\\test\\test1.txt',mode='r')
connect=file.read(4)
print(connect)
print('-'*30)
connect=file.read()
print(connect)
file.close()
方法二:
python
with open('D:\\test\\test1.txt',mode='r') as f:
print(f.read(4)) #读取两个字节的数据
print(f.read()) #读取剩余的全部数据
该方法是一次性的,执行结束后文件自动关闭
2)读取文件-readline()方法
readline()方法可以从指定文件中读取一行数据,其语法格式如下:
python
with open('D:\\test\\test1.txt',mode='r') as f:
while True:
content = f.readline()
if content=='':
break
print(content)
# 运行结果
Life is short
i use python
扩展:假如出现如下报错如何解决
python
Traceback (most recent call last):
File "F:\PycharmProjects\pythonProject1\pycode\文件操作\文件开打和关闭.py", line
10, in <module>
print(f.readline())
^^^^^^^^^^^^
UnicodeDecodeError: 'gbk' codec can't decode byte 0xff in position 0: illegal
multibyte sequence
原因:表示在尝试使用GBK编码解码字节数据时,遇到了一个不合法的字节(0xff),解码器无法继续 解码。这通常发生在读取文件时指定了错误的编码,而文件实际上并非GBK编码。
解决方法如下:
当处理文本数据时,经常会遇到各种不同的字符编码。这可能导致乱码和其他问题,因此需要一种方法 来准确识别文本的编码。Python中的 chardet 库就是为了解决这个问题而设计的,它可以自动检测文本 数据的字符编码。
python
pip install chardet
python
# 获取文本字符集
filepath='D:\\test\\test1.txt'
#
import chardet
def file_get_encoding(file:str)->str:
with open(file,'rb') as f:
data=f.read()
encoding=chardet.detect(data)['encoding']
return encoding
with open(filepath,mode='r',encoding=file_get_encoding(filepath)) as f:
while True:
line= f.readline()
if line=='':
break
print(line)
运行结果:默认UTF-16字符集
3)readlines()方法
readlines()方法可以一次读取文件中的所有数据,若读取成功,该方法会返回一个列表,文件中的每一 行对应列表中的一个元素。语法格式如下:
python
readlines(hint=-1)
- hint:单位为字节,用于控制要读取的行数如果行中数据的总大小超出了hint字节,readlines()不 会再读取更多的行。
使用readlines()读取D盘中test1.txt文件,代码示例如下
python
import chardet
with open("D:\\test\\test1.txt","rb") as f:
raw_data = f.read()
encoding = chardet.detect(raw_data)['encoding']
with open("D:\\test\\test1.txt","r",encoding=encoding,errors='ignore') as f:
print(f.readlines())
# 运行结果
['Life is short\n', 'i use python']
总结:
- read()(参数缺省时)和readlines()方法都可一次读取文件中的全部数据,但因为计算机的内存是 有限的,若文件较大,read()和readlines()的一次读取便会耗尽系统内存,所以这两种操作都不够 安全。
- 为了保证读取安全,通常多次调用read()方法,每次读取size字节的数据。
4)写文件-write()方法
write()方法可以将指定字符串写入文件,其语法格式如下:
python
write(data)
以上格式中的参数data表示要写入文件的数据,若数据写入成功,write()方法会返回本次写入文件的数 据的字节数。
使用write()向test1.txt文件中写入数据,示例代码如下:
python
words=input('写入一段话:')
with open('D:\\test\\test1.txt','w',encoding='utf-8') as f:
size = f.write(words)
print('成功写入',size,'字节')
# 运行结果 字符串数据成功写入文件。此时打开.txt文件,可在该文件中看到写入的字符串。
写入一段话:i am a person
成功写入 13 字节
5)writelines()方法
writelines()方法用于将行列表写入文件,其语法格式如下:
python
writelines(lines)
- 以上格式中的参数lines表示要写入文件中的数据,该参数可以是一个字符串或者字符串列表。
- 若写入文件的数据在文件中需要换行,需要显式指定换行符。
示例代码如下:
python
string=['Life is short\n','i use python']
with open('D:\\test\\test1.txt','w',encoding='utf-8') as f:
size=f.writelines(string)
print(size) #写入成功,无任何输出信息
# 运行代码,若没有输出信息,说明字符串被成功写入文件
None
总结:
- write :将字符串写入文件,适用于单行写入。要求传入的参数必须是字符串类型。如果传入其他 类型(如列表),会导致错误。
- writelines :将字符串按行写入文件,适用于多行写入。可以接受字符串序列(如列表),但序列 中的元素必须是字符串类型。如果传入其他类型(如数字),会导致错误。
扩展:字符与编码
文本文件支持多种编码方式,不同编码方式下字符与字节的对应关系不同,常见的编码方式以及字符与 字节的对应关系如表所示。
| 编码方式 | 语言 | 字符数 | 字节数 |
|---|---|---|---|
| ASCII | 中文 | 1 | 2 |
| ASCII | 英文 | 1 | 1 |
| UTF-8 | 中文 | 1 | 3 |
| UTF-8 | 英文 | 1 | 1 |
| Unicode | 中文 | 1 | 2 |
| Unicode | 英文 | 1 | 2 |
| GBK | 中文 | 1 | 2 |
| GBK | 英文 | 1 | 1 |
7.2.3:文件的定位读写
read()方法读取了文件.txt,结合代码与程序运行结果进行分析,可以发现read()方法第1次读取了2个字符,第2次从第3个字符开始读取了剩余字符。
-
在文件的一次打开与关闭之间进行的读写操作是连续的,程序总是从上次读写的位置继续向下进行读写操作。
-
每个文件对象都有一个称为"文件读写位置"的属性,该属性会记录当前读写的位置。
-
文件读写位置默认为0,即在文件首部。
Python提供了一些获取与修改文件读写位置的方法,以实现文件的定位读写。
-
tell():获取文件当前的读写位置。
-
seek():控制文件的读写位置。
1)tell()方法
tell()方法用于获取文件当前的读写位置,tell()的用法如下:
python
with open ('D:\\test\\test1.txt')as f:
print(f.tell())
print(f.read(3))
print(f.tell())
# 运行结果
0
Lif
3
2)seek()方法
Python提供了seek()方法,使用该方法可控制文件的读写位置,实现文件的随机读写。seek()方法的语 法格式如下:
python
seek(offset, from)
- offset:表示偏移量,即读写位置需要移动的字节数。
- from:用于指定文件的读写位置,该参数的取值为0、1、2。
- 0:表示文件开头
- 1:表示使用当前读写位置
- 2:表示文件末尾 seek()方法调用成功后会返回当前读写位置。
python
with open ('D:\\test\\test1.txt')as f:
f.tell()
sep=f.seek(3,0)
print(sep)
# 运行结果
3
注意:在Python3中,若打开的是文本文件,那么seek()方法只允许相对于文件开头移动文件位置,若在参数from值为1、2的情况下对文本文件进行位移操作,将会产生错误。
python
with open ('D:\\test\\test1.txt')as f:
f.seek(3,0)
f.seek(5,1)
# 运行结果
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\文件\文件的读取.py", line 79, in <module>
f.seek(5,1)
~~~~~~^^^^^
io.UnsupportedOperation: can't do nonzero cur-relative seeks
解决:若要相对当前读写位置或文件末尾进行位移操作,需以二进制形式打开文件。
python
with open ('D:\\test\\test1.txt','rb')as f:
f.seek(3,0)
sep=f.seek(5,1)
print(sep)
# 运行结果
8
7.3:文件与目录管理
对于用户而言,文件和目录以不同的形式展现,但对计算机而言,目录是文件属性信息集合,它本质上 也是一种文件。
os模块中定义了与文件操作相关的函数,利用这些函数可以实现删除文件、文件重命名、创建/删除目 录、获取当前目录、更改默认目录与获取目录列表等操作。
1)删除文件-remove()函数
使用os模块的remove()函数可删除文件,该函数要求目标文件存在,语法如下:
python
remove(文件名)
调用remove()函数处理文件,指定的文件会被删除。
python
import os
os.remove('D:\\test\\none.txt')
2)文件重命名-rename()函数
使用os模块中的rename()函数可以更改文件名,该函数要求目标文件已存在,语法如下:
python
rename(原文件名,新文件名)
rename()函数的用法,.txt变更为cloud.txt
python
import os
os.rename('D:\\test\\test1.txt','D:\\test\\test01.txt')
3)获取当前目录-getcwd()函数
当前目录即是Python当前的工作路径,os模块中的getcwd()函数用于获取当前目录,调用该函数可获取 当前工作目录的绝对路径。
python
import os
print(os.getcwd())
# 运行结果
D:\PycharmProjects\PythonProject\.venv\scr\文件
4)创建/删除目录-mkdir()/rmdir()
os模块中的mkdir()函数用于创建目录,rmdir()函数用于删除目录,这两个函数的参数都是目录名称。 mkdir()函数用于在当前目录下创建目录,需要注意,待创建的目录不能与已有目录重名,否则将创建失 败。
python
import os
os.mkdir('D:\\test2\\test')
rmdir()用于删除目录。
python
import os
os.rmdir('D:\\test2\\test')
5)更改默认目录-chdir()函数
os模块中的chdir()函数用于更改默认目录。若在对文件或文件夹进行操作时传入的是文件名而非路径 名,Python解释器会从默认目录中查找指定文件,或将新建的文件放在默认目录下。若没有特别设置,当前目录即为默认目录。
使用chdir()函数更改默认目录为F盘,再次使用getcwd()函数获取当前目录,代码示例:
python
import os
print(os.getcwd())
os.chdir('D:\\')
print(os.getcwd())
#运行结果
D:\PycharmProjects\PythonProject\.venv\scr\文件
D:\
6)获取文件名列表-listdir()函数
实际使用中常常需要先获取指定目录下的所有文件,再对目标文件进行相应操作。os模块中提供了 listdir()函数,使用该函数可方便快捷地获取指定目录下所有文件的文件名列表。
python
import os
files=os.listdir('D:\\test')
print(files)
# 运行结果
['count.csv', 'info.csv', 'test', 'test02', 'test1.txt']
7.4:实训案例
7.4.1:信息安全策略-文件备份
当下是信息时代,信息在当今社会占据的地位不言而喻,信息安全更是当前人类重视的问题之一。人类 考虑从传输和存储两方面保障信息的安全,备份是在存储工作中保障信息安全的有效方式。本案例要求 编写程序,实现一个具有备份文件与文件夹功能的备份工具。
python
file=open('D:\\test\\test1.txt','r',encoding='utf-8')
content=[file.read()]
print(content)
with open('D:\\test2\\test.txt',mode='w',encoding='utf-8') as f:
size = f.writelines(content)
print(size)
7.5:数据维度与数据格式化
从广义上看,维度是与事物"有联系"的概念的数量。根据"有联系"的概念的数量,事物可分为不同维度, 例如与线有联系的概念为长度,因此线为一维事物;与长方形面积有联系的概念为长度和宽度,因此面 积为二维事物;与长方体体积有联系的概念为长度,宽度和高度,因此体积为三维事物。
7.5.1:基于维度的数据分类
根据组织数据时与数据有联系的参数的数量,数据可分为一维数据、二维数据和多维数据。
1)一维数据
具有对等关系的一组线性数据
- 一维列表
- 一维元组
- 集合
2)二维数据
二维数据关联参数的数量为2
-
矩阵
-
二维数组
-
二维列表
-
二维元组
3)多维数据
利用键值对等简单的二元关系展示数据间的复杂结构
- 字典
7.5.2:一维数据和二维数据的存储与读写
程序中与数据相关的操作分为数据的存储与读写,下面我们就一起掌握如何存储与读写不同维度的数 据。
1)数据存储
数据通常存储在文件中,为了方便后续的读写操作,数据通常需要按照约定的组织方式进行存储。 一维数据呈线性排列,一般用特殊字符分隔,例如:
- 使用空格分隔:成都 杭州 重庆 武汉 苏州 西安 天津
- 使用逗号分隔:成都,杭州,重庆,武汉,苏州,西安,天津
- 使用&分隔:成都&杭州&重庆&武汉&苏州&西安&天津
一维数据的存储需要注意以下几点:
- 同一文件或同组文件一般使用同一分隔符分隔。
- 分隔数据的分隔符不应出现在数据中。
- 分隔符为英文半角符号,一般不使用中文符号作为分隔符。
二维数据可视为多条一维数据的集合,当二维数据只有一个元素时,这个二维数据就是一维数据。
CSV(Commae-Separeted Values,逗号分隔值)是国际上通用的一二维数据存储格式。
CSV格式规范:
- 以纯文本形式存储表格数据
- 文件的每一行对应表格中的一条数据记录
- 每条记录由一个或多个字段组成
- 字段之间使用逗号(英文、半角)分隔
CSV也称字符分隔值,具体示例如下:
python
姓名,语文,数学,英语,理综
刘备,124,137,145,260
张飞,116,143,139,263
关羽,120,130,148,255
周瑜,115,145,131,240
诸葛亮,123,108,121,235
黄月英,132,100,112,210
CSV广泛应用于不同体系结构下网络应用程序之间表格信息的交换中,它本身并无明确格式标准,具体标准一般由传输双方协议决定。
2)数据读取
Windows平台中CSV文件的后缀名为.csv,可通过Office Excel或记事本打开。
Python在程序中读取.csv文件后会以二维列表形式存储其中内容。
python
import chardet
filename='D:\\test\\info.csv'
def get_encoding(filepath):
with open(filepath,'rb') as f:
deta=f.read()
encoding=chardet.detect(deta)['encoding']
return encoding
with open(filename,'r',encoding=get_encoding(filename)) as f:
lines=list()
for line in f:
line=line.replace('\n','')
lines.append(line.split(','))
print(lines)
# 运行结果
# 打开文件score.csv后通过对文件对象进行迭代,在循环中逐条获取文件中的记录,根据分隔符","分隔记录,将记录存储到列表lines中,最后在终端打印列表lines。
[['姓名', '语文', '数学', '英语', '理综'], ['刘备', '124', '137', '145', '260'], ['张飞', '116', '143', '139', '263'], ['关羽', '120', '130', '148', '255'], ['周瑜', '115', '145', '131', '240'], ['诸葛亮', '123', '108', '121', '235'], ['黄月英', '132', '100', '112', '210']]
3)数据写入
将一、二维数据写入文件中,即按照数据的组织形式,在文件中添加新的数据。
在保存学生成绩的文件score.csv中写入每名学生的总分,代码示例如下
python
import chardet
filename='D:\\test\\info.csv'
#默认字符集处理
def get_encoding(filepath):
with open(filepath,'rb') as f:
deta=f.read()
encoding=chardet.detect(deta)['encoding']
return encoding
with open(filename,'r',encoding=get_encoding(filename)) as f:
new_file=open('D:\\test\\count.csv','w+')
lines=list()
for line in f:
line=line.replace('\n','')
lines.append(line.split(','))
#添加表头字段
lines[0].append('合计')
#添加总计
for i in range(len(lines)-1):
idx=i+1
sum_score=0
for j in range(len(lines[idx])):
if lines[idx][j].isnumeric():
sum_score+=int(lines[idx][j])
lines[idx].append(str(sum_score))
print(lines)
for line in lines:
new_file.write(','.join(line)+'\n')
# 运行结果
[['姓名', '语文', '数学', '英语', '理综', '合计'], ['刘备', '124', '137', '145', '260', '666'], ['张飞', '116', '143', '139', '263', '661'], ['关羽', '120', '130', '148', '255', '653'], ['周瑜', '115', '145', '131', '240', '631'], ['诸葛亮', '123', '108', '121', '235', '587'], ['黄月英', '132', '100', '112', '210', '554']]
扩展: isnumeric() 方法用于检查字符串是否只包含数字字符。这个方法会返回一个布尔值,如果字符串中的所有字符都是数字,则返回 True ,否则返回 False 。
运行结果,使用Excel打开文件

7.5.3:多维数据的格式化
为了直观地表示多维数据,也为了便于组织和操作,三维及以上的多维数据统一采用键值对的形式进行 格式化。
网络平台上传递的数据大多是高维数据,JSON是网络中常见的高维数据格式。JSON格式的数据遵循以 下语法规则:
- 数据存储在键值对(key:value)中,例如"姓名": "张飞"。
- 数据的字段由逗号分隔,例如"姓名": "张飞", "语文": "116"。
- 一个花括号保存一个JSON对象,例如{"姓名": "张飞", "语文": "116"}。
- 一个方括号保存一个数组,例如[{"姓名": "张飞", "语文": "116"}]。
python
"班级考试成绩":[
{"姓名": "王小天",
"语文": "124",
"数学": "127",
"英语": "145",
"理综": "259" };
{"姓名": "张大同",
"语文": "116",
"数学": "143",
"英语": "119",
"理综": "273" };
......
]
以上数据是一个键值对,它的key为"班级考试成绩",之后跟着以冒号分隔的value。此value本身是一个 数组,该数组中存储了多名学生的成绩,通过中括号组织,其中的元素通过分号分隔,分号作为数组元 素的学生成绩的每项属性亦为键值对,每项属性通过逗号分隔。
除JSON外,网络平台也会使用XML(可扩展标记语言)、HTML等格式组织多维数据,XML和HTML格 式通过标签组织数据。例如,将学生成绩以XML格式存储,示例如下
python
<班级考试成绩>
<姓名>王小天</姓名><语文>124</语文><数学>127<数学/><英语>145<英语/><理综>259<理综/>
<姓名>张大同</姓名><语文>116</语文><数学>143<数学/><英语>119<英语/><理综>273<理综/>
......
</班级考试成绩>
对比JSON格式与XML、HTML格式可知,JSON格式组织的多维数据更为直观,且数据属性的key只需存 储一次,在网络中进行数据交换时耗费的流量更少
JSON模块------json
利用json模块的dumps()函数和loads()函数可以实现Python对象和JSON数据之间的转换,这两个函数的 具体功能如表所示。
| 函数 | 功能 |
|---|---|
| dumps() | 对Python对象进行转码,将其转化为JSON字符串 |
| loads() | 将JSON字符串解析为Python对象 |
Python对象与JSON数据转化时的类型对照表
| Python对象 | JSON数据 |
|---|---|
| dict | object |
| list,tuple | array |
| str,unicode | string |
| int,long,float | number |
| True | true |
| False | false |
| None | null |
1)dumps()函数
使用dumps()函数对Python对象进行转码。
python
import json
pyobj=[[1,2,3],10,2,32,'tom',{'java':34,'python':'29'},True,False,None]
jsonobj=json.dumps(pyobj)
print(jsonobj)
# 运行结果
[[1, 2, 3], 10, 2, 32, "tom", {"java": 34, "python": "29"}, true, false, null]
2)loads()函数
使用loads()函数将JSON数据转换为符合Python语法要求的数据类型。
python
import json
pyobj=[[1,2,3],10,2,32,'tom',{'java':34,'python':'29'},True,False,None]
jsonobj=json.dumps(pyobj)
print(jsonobj)
# 反序列化操作
new_obj=json.loads(jsonobj)
print(new_obj)
# 运行结果
[[1, 2, 3], 10, 2, 32, "tom", {"java": 34, "python": "29"}, true, false, null]
[[1, 2, 3], 10, 2, 32, 'tom', {'java': 34, 'python': '29'}, True, False, None]
7.6:本章小结
本章主要介绍了文件与数据格式化相关的知识,包括计算机中文件的定义、文件的基本操作、文件与目 录管理、数据维度与数据格式化。通过本章的学习,能了解计算机中文件的意义、熟练地读取和管理文 件,并掌握常见的数据组织形式。
第8章:面向对象
面向对象是程序开发领域的重要思想,这种思想模拟了人类认识客观世界的思维方式,将开发中遇到的 事物皆看作对象。
8.1:面向对象概述
面向过程:
- 分析解决问题的步骤
- 使用函数实现每个步骤的功能
- 按步骤依次调用函数
面向对象:
- 分析问题,从中提炼出多个对象
- 将不同对象各自的特征和行为进行封装
- 通过控制对象的行为来解决问题。
面向对象编程与面向过程编程是两种不同的编程范式,它们在处理问题的方式上有显著的不同。下面我 将通过一个日常生活中的例子 ------"制作一杯咖啡" 来说明这两种编程风格之间的区别。
python
面向过程方式:
假设我们要编写一段程序来描述如何在家里制作一杯简单的美式咖啡。使用面向过程的方法,我们会按照步骤
顺序执行一系列操作:
1)准备好所需的材料:水、咖啡粉。
2)将一定量的水倒入电热水壶中并加热至沸腾。
3)同时,在滤杯中放置一张滤纸,并预热杯子。
4)待水煮沸后稍冷却片刻,使温度降至约 90°C 左右。
5)用量勺取适量咖啡粉放入滤纸内。
6)慢慢地将热水倒在咖啡粉上进行冲泡。
7)等待所有水分过滤完毕,一杯美味的手冲咖啡就完成了!
在这个过程中,我们关注的是完成任务的一系列具体步骤。每个步骤都是独立的任务,按照特定的顺序执行以
达成最终目标。
---------------------------------------------------------------------------------
--------------------------------------------------------
面向对象方式:
如果采用面向对象的方法来看待同样的问题,则会有所不同。首先,我们将识别出几个关键的对象:
咖啡机
水
咖啡粉
杯子
然后为这些对象定义属性和方法。例如:
咖啡机:煮制咖啡的过程;添加原料。
水: 允许设置加热水到特定温度。
咖啡粉: 表示研磨程度。
杯子: 接收冲好的咖啡液。
思考:如果想喝茶怎么办?
使用面向过程就需要把过程从头到尾给进行相关调整;使用面向对象只需要把咖啡粉换成茶叶即可。
8.2:类的定义与使用
- 面向对象编程有两个非常重要的概念:类和对象。
- 对象映射现实中真实存在的事物,如一本书。
- 具有相同特征和行为的事物的集合统称为类。
- 对象是根据类创建的,一个类可以对应多个对象。
- 类是对象的抽象,对象是类的实例。
8.2.1:类的定义
类是由3部分组成的:
- 类的名称:大驼峰命名法,首字母一般大写,比如Person。
- 类的属性:用于描述事物的特征,比如姓名,性别,身高,体重等(静态描述)。
- 类的方法:用于描述事物的行为,比如吃饭,睡觉,健身,娱乐等(动态描述)。
语法如下:
python
class 类名:
属性名 = 属性值
def 方法名(self):
方法体
示例:
python
class Car:
color = '红' # 属性
def drive(self ): # 方法
return '正在行驶'
car=Car()
print(f'一辆{car.color}颜色的车,',car.drive())
#运行结果
一辆红颜色的车, 正在行驶
8.2.2:对象的创建与使用
根据类创建对象的语法格式如下:
python
对象名 = 类名() #实例
car = Car()
使用对象的本质是访问对象成员:
python
对象名.属性名
car.wheels
对象名.方法名()
car.drive()
8.3:类的成员
8.3.1:属性

属性按声明的方式可以分为两类:类属性和实例属性。
1)类属性
- 声明在类内部、方法外部的属性。
- 可以通过类或对象进行访问,但只能通过类进行修改。
python
class Car:
color = '红' # 属性
# 实例方法,self代表对象本身
def drive(self ):
return '正在行驶'
# 创建对象
car=Car()
# 通过类访问类属性
print('类的属性:',Car.color)
# 通过对象访问类属性
print('对象属性:',car.color)
# 通过对象修改类属性color(只影响对象值,不影响类属性值)
car.color='蓝'
print('类的属性:',Car.color)
print('对象属性:',car.color)
# 通过类修改color(类修改影响所有值)
Car.color='蓝'
print('类的属性:',Car.color)
print('对象属性:',car.color)
# 运行结果
类的属性: 红
对象属性: 红
类的属性: 红
对象属性: 蓝
类的属性: 蓝
对象属性: 蓝
2)实例属性,又称对象属性
-
实例属性是在方法内部声明的属性。
-
Python支持动态添加实例属性。
(1)访问实例属性------只能通过对象访问
python
class Car:
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4 #添加实例属性
return '正在行驶'
car=Car()
car.drive()
# 通过对象访问实例属性
print(car.wheels)
# 通过类访问实例属性(报错)
print(Car.wheels)
# 运行结果
4
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\面向对象\属性.py", line 27, in <module>
print(Car.wheels)
^^^^^^^^^^
AttributeError: type object 'Car' has no attribute 'wheels'
(2)修改实例属性------通过对象修改
python
class Car:
# 类属性,定义在类内部,方法外部,只能通过类修改
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4 #添加实例属性
return '正在行驶'
car=Car()
car.drive()
car.wheels=2
print(car.wheels)
# 运行结果
2
(3)动态添加实例属性------类外部使用对象动态添加实例属性
python
class Car:
color = '红'
def drive(self ):
self.wheels=4 #添加实例属性
return '正在行驶'
car=Car()
# 动态添加实例属性
car.name='问界'
print('对象调用',car.name)
8.3.2:方法
Python中的方法按定义方式和用途可以分为三类:实例方法、类方法和静态方法。
1)实例方法
-
形似函数,但它定义在类内部。
-
以self为第一个形参,self参数代表对象本身。
-
只能通过对象调用。
python
class Car:
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4
return '正在行驶'
car=Car()
car.drive()
# 通过类调用实例方法,报错
Car.drive()
2)类方法
- 类方法是定义在类内部
- 使用装饰器@classmethod修饰的方法
- 第一个参数为cls,代表类本身
- 可以通过类和对象调用
python
class Car:
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4
return '正在行驶'
# 类方法
@classmethod
def stop(cls):
return '刹车停止'
car=Car()
print (car.stop()) # 通过对象调用类方法
print(Car.stop()) # 通过对象类调用类方法
# 运行结果
刹车停止
刹车停止
类方法中可以使用cls访问和修改类属性的值
python
class Car:
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4
return '正在行驶'
# 类方法
@classmethod
def stop(cls):
print(cls.color)
cls.color='蓝' # 使用cls访问类属性
print(cls.color)
return '刹车停止'
car=Car()
print(car.color)
print (car.stop())
# 运行结果
红
蓝
刹车停止
3)静态方法
- 静态方法是定义在类内部
- 使用装饰器@staticmethod修饰的方法
- 没有任何默认参数
静态方法可以通过类和对象调用,独立于类所存在
静态方法内部不能直接访问属性或方法,但可以使用类名访问类属性或调用类方法
python
class Car:
color = '红'
# 实例方法,self代表对象本身
def drive(self ):
self.wheels=4
return '正在行驶'
@staticmethod
def test():
print(f'静态方法,车辆的颜色是{Car.color}色') # 静态方法中访问类属性
car=Car()
car.test()
Car.test()
# 运行结果
静态方法,车辆的颜色是红色
静态方法,车辆的颜色是红色
8.3.3:私有成员
类的成员默认是公有成员,可以在类的外部通过类或对象随意地访问,这样显然不够安全。 为了保证类中数据的安全,Python支持将公有成员改为私有成员,在一定程度上限制在类的外部对类成 员的访问。
Python通过在类成员的名称前面添加双下画线(__)的方式来表示私有成员,语法格式如下:
- __属性名
- __方法名
私有成员在类的内部可以直接访问,在类的外部不能直接访问,但可以通过调用类的公有成员方法的方式进行访问。
python
class Car:
__color='红' # 私有属性
def __drive(self): # 私有方法
print('开车')
def test(self):
# 公有方法中访问私有属性
print(f'这辆车是{self.__color}色的')
# 公有方法中调用私有方法
self.__drive()
car=Car()
car.test()
# 运行属性
这辆车是红色的
开车
8.4:特殊方法
类中还包括两个特殊的方法:构造方法和析构方法,这两个方法都是系统内置方法。
8.4.1:构造方法
构造方法指的是init()方法。
创建对象时系统自动调用,从而实现对象的初始化。
每个类默认都有一个init ()方法,可以在类中显式定义init()方法。
init()方法可以分为无参构造方法和有参构造方法。
- 当使用无参构造方法创建对象时,所有对象的属性都有相同的初始值。
python
class Car:
name='BMW'
# 无参构造方法
def __init__(self):
print('制造完成')
self.color='红'
def drive(self):
self.wheels=4
print('正在行驶')
car=Car() # 创建对象并初始化
print(f'今天买了一辆{car.name},颜色为{car.color}色')
car.drive()
print(car.wheels)
# 运行结果
制造完成
今天买了一辆BMW,颜色为红色
正在行驶
4
- 当使用有参构造方法创建对象时,对象的属性可以有不同的初始值。
python
class Car:
name='BMW'
# 有参构造方法
def __init__(self,my_color):
self.color=my_color # 将形参赋值给属性
print(f'{self.color}车辆制造完成')
def drive(self):
self.wheels=4
print('正在行驶')
car1=Car('樱花粉') # 创建对象,并根据实参初始化属性
print(f'今天买了一辆{car1.name},颜色为{car1.color}色')
car2=Car('火山灰')
print(f'今天买了一辆{car2.name},颜色为{car2.color}色')
# 运行结果
樱花粉车辆制造完成
今天买了一辆BMW,颜色为樱花粉色
火山灰车辆制造完成
今天买了一辆BMW,颜色为火山灰色
8.4.2:析构方法
析构方法(即del()方法)是销毁对象时系统自动调用的方法。
每个类默认都有一个del()方法,可以显式定义析构方法。
python
class Car:
name='BMW'
def __init__(self,my_color):
self.color=my_color
print(f'{self.color}车辆制造完成')
def drive(self):
self.wheels=4
print('正在行驶')
def __del__(self): #析构方法
print(f'{self.color}的车辆被销毁')
car1=Car('樱花粉')
print(f'今天买了一辆{car1.name},颜色为{car1.color}色')
# 手动销毁
# del car1
car2=Car('火山灰')
print(f'今天买了一辆{car2.name},颜色为{car2.color}色')
# 运行结果
# 前面运行完后,统一销毁,销毁前每个对象都会已占用内存,可手动销毁
樱花粉车辆制造完成
今天买了一辆BMW,颜色为樱花粉色
火山灰车辆制造完成
今天买了一辆BMW,颜色为火山灰色
樱花粉的车辆被销毁
火山灰的车辆被销毁
扩展:与文件类似,每个对象都会占用系统的一块内存,使用之后若不及时销毁,会浪费系统资源。那么对象什么时候销毁呢?
Python通过引用计数器记录所有对象的引用(可以理解为对象所占内存的别名)数量,一旦某个对象的引用计数器的值为0,系统就会销毁这个对象,收回对象所占用的内存空间。
8.5:实训案例
8.5.1:好友管理系统
本实例要求编写代码,实现一个基于面向对象思想的、具有添加好友、删除好友、备注好友、展示好 友、好友分组、退出功能的好友管理系统。
8.6:封装
封装是面向对象的重要特性之一,它的基本思想是对外隐藏类的细节,提供用于访问类成员的公开接口。
如此,类的外部无需知道类的实现细节,只需要使用公开接口便可访问类的内容,这在一定程度上保证了类内数据的安全。
为了契合封装思想,我们在定义类时需要满足以下两点要求。
1.将类属性声明为私有属性。
2.添加两类供外界调用的公有方法,分别用于设置或获取私有属性的值。
python
class Person:
def __init__(self,name):
self.name=name
# 私有属性
self.__age=1
# 公有方法设置
def set_age(self,age):
# 合法处理
if 0<age<=120:
self.__age=age
# 共有方法查看
def get_age(self):
return self.__age
zhangsan=Person('zhangsan')
zhangsan.set_age(122)
print(f'他的姓名是{zhangsan.name},年龄是{zhangsan.get_age()}')
# 运行结果
他的姓名是zhangsan,年龄是1
zhangsan=Person('zhangsan')
zhangsan.set_age(22)
print(f'他的姓名是{zhangsan.name},年龄是{zhangsan.get_age()}')
# 运行结果
他的姓名是zhangsan,年龄是22
8.7:继承
继承是面向对象的重要特性之一,它主要用于描述类与类之间的关系,在不改变原有类的基础上扩展原有类的功能。
若类与类之间具有继承关系,被继承的类称为父类或基类 ,继承其他类的类称为子类或派生类 ,子类会自动拥有父类的公有成员。
8.7.1:单继承
单继承即子类只继承一个父类。现实生活中,波斯猫、折耳猫、短毛猫都属于猫类,它们之间存在的继承关系即为单继承,如图所示。
Python中单继承的语法格式如下所示:
python
class 子类名(父类名):
- 子类继承父类的同时会自动拥有父类的公有成员。
- 自定义类默认继承基类object。
python
class Cat:
def __init__(self,color):
self.color=color
def play(self):
print('喜欢玩逗猫棒')
class Scottishfold(Cat):
pass
fold = Scottishfold('白色') # 创建子类的对象
print(f'它是一只{fold.color}的折耳猫') # 子类访问从父类继承的属性
fold.play() # 子类调用从父类继承的方法
# 运行结果
它是一只白色的折耳猫
喜欢玩逗猫棒
注意:子类不会拥有父类的私有成员,也不能访问父类的私有成员。
python
class Cat:
def __init__(self,color):
self.color=color
def play(self):
print('喜欢玩逗猫棒')
def __age(self): # 增加私有属性
print('年龄是5岁')
class Scottishfold(Cat):
pass
fold = Scottishfold('白色')
print(f'它是一只{fold.color}的折耳猫')
fold.__age() #子类访问父类私有方法
# 运行结果,被禁止访问
它是一只白色的折耳猫
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\面向对象\继承.py", line 16, in <module>
fold.__age()
^^^^^^^^^^
AttributeError: 'Scottishfold' object has no attribute '__age'
8.7.2:多继承
程序中的一个类也可以继承多个类,如此子类具有多个父类,也自动拥有所有父类的公有成员。
Python中多继承的语法格式如下所示:
python
class 子类名(父类名1, 父类名2, ...):
创建房车代码示例:
python
class House:
def live(self):
print('居住')
def test(self):
print('房屋类的方法')
class Car:
def drive(self):
print('行驶')
def test(self):
print('车辆类的方法')
class TouringCar(Car,House):
pass
tc=TouringCar()
tc.drive() # 子类对象调用父类Car的方法
tc.live() # 子类对象调用父类House的方法
# 如果父类都有同名方法,继承的父类哪个在用那个的方法
tc.test()
# 运行方法
行驶
居住
车辆类的方法
**提问:如果House类和Car类中有一个同名的方法,那么子类会调用哪个父类的同名方法呢? **
如果子类继承的多个父类是平行关系的类,那么子类先继承哪个类,便会先调用哪个类的方法。
8.7.3:重写
子类会原封不动地继承父类的方法,但子类有时需要按照自己的需求对继承来的方法进行调整,也就是 在子类中重写从父类继承来的方法。
在子类中定义与父类方法同名的方法,在方法中按照子类需求重新编写功能代码即可。
python
class Person:
def sayhi(self):
print('Hello')
class China(Person):
def sayhi(self): # 重写的方法
print('你好')
china=China()
china.sayhi()
# 运行结果
你好
子类重写了父类的方法之后,无法直接访问父类的同名方法,但可以使用super()函数间接调用父类中被 重写的方法。
python
class Person:
def sayhi(self):
print('Hello')
class China(Person):
def sayhi(self):
super().sayhi() # 子类可以直接调用父类的方法
print('你好')
china=China()
china.sayhi()
# 运行结果
Hello
你好
super表示上一级,即Person类,super()=Person(),故super()是Person的类
8.8:多态
多态是面向对象的重要特性之一,它的直接表现即让不同类的同一功能可以通过同一个接口调用,表现 出不同的行为。
python
# 类1
class Cat:
def shout(self):
print('喵~')
# 类2
class Dog:
def shout(self):
print('汪!')
# 接口
def call(obj):
obj.shout()
cat=Cat()
dog=Dog()
call(dog)
call(cat)
# 运行结果
汪!
喵~
8.9:运算符重载
运算符重载是指赋予内置运算符新的功能,使内置运算符能适应更多的数据类型。
基类object中提供的一些特殊方法及其对应的运算符如表所示。

如果类中重写了Python基类object内置的有关运算符的特殊方法,那么该特殊方法对应的运算符将支持 对该类的实例进行运算。
python
class Calculator(object):
def __init__(self, number): # 记录数值
self.number = number
def __add__(self, other): # 重载运算符+
self.number = self.number + other
return self.number
def __sub__(self, other): # 重载运算符-
self.number = self.number - other
return self.number
def __mul__(self, other): # 重载运算符*
self.number = self.number * other
return self.number
def __truediv__(self, other): # 重载运算符/
self.number = self.number / other
return self.number
calculator = Calculator(10)
print(calculator + 5)
print(calculator - 5)
print(calculator * 5)
print(calculator / 5)
第9章:异常
程序开发或运行时可能出现异常,开发人员和运维人员需要辨别程序的异常,明确这些异常是源于程序 本身的设计问题,还是由外界环境的变化引起,以便有针对性地处理异常。

程序运行出现异常时,若程序中没有设置异常处理功能,解释器会采用系统的默认方式处理异常,即返 回异常信息、终止程序。
9.1:异常概述
异常信息中通常包含异常代码 所在行号 、异常的类型 和异常的描述信息。
9.1.1:异常示例
python
Traceback (most recent call last):
File "E:\pyproject\异常\异常概念.py", line 1, in <module>
print(5/0)
~^~
ZeroDivisionError: division by zero
9.1.2:异常类型
Python程序运行出错时产生的每个异常类型都对应一个类,程序运行时出现的异常大多继承自 Exception类 ,Exception类又继承了异常类的基类BaseException。

1)NameError
- NameError是程序中使用了未定义的变量时会引发的异常。
- 例如,访问一个未声明过的变量test,代码如下:

2)IndexError
- IndexError是程序越界访问时会引发的异常。
- 例如,使用索引0访问空列表num_list,代码如下:

3) AttributeError
- AttributeError是使用对象访问不存在的属性时引发的异常。
- 例如,Car类中动态地添加了两个属性color和brand,使用Car类的对象依次访问color、brand属 性及不存在的name属性,代码如下:

4)FileNotFoundError
- FileNotFoundError是未找到指定文件或目录时引发的异常。
- 例如,打开一个本地不存在的文件,代码如下:

9.2:异常捕获语句
Python既可以直接通过try-except 语句实现简单的异常捕获与处理的功能,也可以将try-except语句与 else 或finally子句组合实现更强大的异常捕获与处理的功能。
9.2.1:try-except语句捕获异常
python
try:
可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋error
捕获异常后的处理代码
try-except语句可以捕获与处理程序的单个 、多个 或全部异常。
python
num1=int(input('输入第1个数字:'))
num2=int(input('输入第1个数字:'))
try:
print(num1/num2)
except ZeroDivisionError:
print('出错了')
python
num1=int(input('输入第1个数字:'))
num2=int(input('输入第1个数字:'))
try:
print(num1/num2)
except ZeroDivisionError as error: #定义error变量输出异常内容
print('出错了',error)
如果输入的是字母进行异常捕捉
python
try:
# 监控,可能会出现异常的语句
num1 = int(input('输入第1个数字:'))
num2 = int(input('输入第1个数字:'))
print(num1/num2)
except (ZeroDivisionError,ValueError) as error: #多异常类型
print('出错了',error)
使用父类捕捉异常
python
try:
# 监控,可能会出现异常的语句
num1 = int(input('输入第1个数字:'))
num2 = int(input('输入第1个数字:'))
print(num1/num2)
#Exception效率低,但全面
except Exception as error: #父类异常
print('出错了',error)
9.2.2:异常结构中的else子句
else子句 可以与try-except语句组合成try-except-else结构,若try监控的代码没有异常,程序会执行else 子句后的代码。
python
try:
可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋值error
捕获异常后的处理代码
else:
未捕获异常后的处理代码
如果除法过程没有异常则输出结果
python
try:
# 监控,可能会出现异常的语句
num1 = int(input('输入第1个数字:'))
num2 = int(input('输入第1个数字:'))
result=num1/num2
except Exception as error:
print('出错了',error)
else:
print('结果:',result)
9.2.3:异常结构中的finally子句
finally子句可以和try-except一起使用,语法格式如下:
python
try:
可能出错的代码
except [异常类型 [as error]]: # 将捕获到的异常对象赋值error
捕获异常后的处理代码
finally:
一定执行的代码
- 无论try子句监控的代码是否产生异常,finally子句都会被执行。
- finally子句多用于预设资源的清理操作,如关闭文件、关闭网络连接。
python
try:
# 监控,可能会出现异常的语句
num1 = int(input('输入第1个数字:'))
num2 = int(input('输入第1个数字:'))
result=num1/num2
except Exception as error:
print('出错了',error)
else:
print('结果:',result)
finally:
# 无论是否异常都会被执行
print('执行结束')
9.3:抛出异常
Python程序中的异常不仅可以自动触发异常,而且还可以由开发人员使用raise和assert语句主动抛出异常。

Python程序中的异常不仅可以自动触发异常,而且还可以由开发人员使用raise和assert语句主动抛出异常。
9.3.1:raise语句抛出异常
使用raise语句可以显式地抛出异常, raise语句的语法格式如下:
python
raise 异常类 # 格式1:使用异常类名引发指定的异常
raise 异常类对象 # 格式2:使用异常类的对象引发指定的异常
raise # 格式3: 使用刚出现过的异常重新引发异常
1)异常类

2)异常对象

指定描述信息

3)重新引发异常
python
try:
print(5/0)
except Exception as e:
print(e)
raise
4)异常引发异常
如果需要在异常中抛出另外一个异常,可以使用raise-from语句实现。
python
try:
print(5/0)
except Exception as err:
raise IndexError('下标越界') from err
由ZeroDivisionError异常赋值给exception变量,从而引发IndexError('下标越界')异常抛出。
9.3.2:assert语句抛出异常
assert语句又称为断言语句,其语法格式如下所示:
python
assert 表达式[, 异常信息]
python
num1=int(input('输入第1个数字:'))
num2=int(input('输入第1个数字:'))
assert num2 !=0,'除数不能为0' #assert语句判定num2不等于0
result=num1/num2
print(num1,'/',num2,'=',result)
# 运行结果
输入第1个数字:1
输入第1个数字:0
Traceback (most recent call last):
File "D:\PycharmProjects\PythonProject\.venv\scr\异常\基础.py", line 56, in <module>
assert num2 !=0,'除数不能为0'
^^^^^^^^
AssertionError: 除数不能为0
9.3.3:异常的传递
异常(exceptions)是通过栈(stack)传递的。当函数中发生异常,该异常会被抛出(raised),然后 由最内层的函数向外传递,直到被捕获(caught)或者导致程序终止。
python
def inner():
print('内层函数')
raise ValueError('内层函数抛出的异常')
def outer():
print('外层函数')
inner()
try:
outer()
except Exception as e:
print(e)
在这个例子中, ValueError 异常在 inner中被抛出,然后传递到 outer ,最后在 try 语句块中 被捕获并处理。输出将会是:
python
外层函数
内层函数
内层函数抛出的异常
9.4:自定义异常
有时我们需要自定义异常类 ,以满足当前程序的需求。自定义异常的方法比较简单,只需要创建一个继承Exception类或Exception子类的类(类名一般以"Error"为结尾)即可。
python
class ShortPwd(Exception):
def __init__(self,length,atlease):
self.length = length
self.atlease = atlease
try:
text=input('请输入密码:')
if len(text)<3:
raise ShortPwd(len(text),3)
except Exception as e:
print(f'你输入的密码长度是{e.length},最少不能少于{e.atlease}')
运行结果
python
请输入密码:12
你输入的密码长度是2,最少不能少于3