2. 列表和元组
2.1 列表是什么?元组是什么?
编程中,经常需要使用变量,来保存/表示数据。
如果代码中需要表示的数据个数比较少,我们直接创建多个变量即可。
python
num1 = 10
num2 = 20
num3 = 30
......
但是有的时候,代码中需要表示的数据特别多,甚至也不知道要表示多少个数据。
这个时候,就需要用到列表。
- 列表是一种让程序猿在代码中批量表示/保存数据的方式。
- **元组和列表相比,****是非常相似的,**只是:
- 列表中放哪些元素可以修改调整;
- **元组中放的元素是创建元组的时候****就设定好的,**不能修改调整。

列表能进行的操作比较多,元组能进行的操作比较少,以下先介绍列表的操作。
2.2 创建列表
创建列表主要有两种方式。**[ ]**表示一个空的列表。
python
alist = [ ]
alist = list()
print(type(alist))

list()是python中的一个内建函数。



使用[ ]创建的优势在于可以在创建列表的时候,很方便地给多个初始值。
如果需要往里面设置初始值,可以直接写在**[ ]**当中。
可以直接使用 print 来打印 alist 中的元素内容。
python
# 可以在创建列表的时候, 在 [ ] 中指定列表的初始值.
# 元素之间使用 , 来分割
alist = [1, 2, 3, 4]
print(alist)

列表中存放的元素允许是不同的类型,这一点和 C++ Java 差别较大。

python
a = [1, 'hello', True, [4, 5, 6]]
print(alist)


2.3 访问下标
可以通过下标访问操作符**[ ]**来获取到列表中的任意元素。
我们把**[ ]中填写的数字,称为下标或者索引**。

python
alist = [1, 2, 3, 4]
print(alist[2])

**【注意】**下标是从 0 开始计数的,因此下标为 2,则对应着 3 这个元素。

通过下标不光能读取元素内容,还能修改元素的值。
python
alist = [1, 2, 3, 4]
alist[2] = 100
print(alist)

如果下标超出列表的有效范围,会抛出异常。
python
alist = [1, 2, 3, 4]
print(alist[100])

print(alist)因为下标是从 0 开始的,因此下标的有效范围是 [0, 列表长度 - 1],使用 len 函数可以获取到列表的元素个数。
python
alist = [1, 2, 3, 4]
print(len(alist))

len可以传:字符串、列表、元组、字典、自定义的类......
一个函数可以支持这么多种类型,背后的原理就是动态类型。
python中的下标可以取负数,表示 "倒数第几个元素"。
例如写成 -1 , 其实等价于 len(a) - 1

python
alist = [1, 2, 3, 4]
print(alist[3])
print(alist[-1])
print(alist[len(alist) - 1])


2.4 切片操作
【对比】
- 通过下标操作,是一次取出里面的下标位置的一个元素。
- 通过切片操作,是一次取出一组连续的元素,相当于得到一个 子列表。
2.4.1 基本操作
【格式】使用 [ : ] 的方式进行切片操作。
python
alist = [1, 2, 3, 4]
print(alist[1:3]) # [1,3]左闭右开



2.4.2 省略边界
切片操作中可以省略前后边界。
python
alist = [1, 2, 3, 4]
print(alist[1:]) # 省略后边界, 表示获取到列表末尾
print(alist[:2]) # 省略前边界, 表示从列表开头获取
print(alist[:-1]) # 省略前边界, 表示从列表开头获取
print(alist[:]) # 省略两个边界, 表示获取到整个列表


2.4.3 指定步长
切片操作还可以指定 "步长",也就是 "每访问一个元素后,下标自增几步"。

python
alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(alist[::1])
print(alist[::2])
print(alist[::3])
print(alist[::5])

python
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(a[3:-1:2])

- 切片操作指定的步长还可以是负数。
- 含义是:在指定区间,从后往前进行取元素,"每访问一个元素之后,下标自减几步"。
python
alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(alist[::-1])
print(alist[::-2])
print(alist[::-3])
print(alist[::-5])

如果切片中填写的数字越界了,不会有负面效果,只会尽可能的把满足条件的元素过去到。
python
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(a[1:100])

python
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(a[50:100])

切片操作不只适用于列表,对于元组、字符串、......等都是使用的。
2.5 遍历列表元素
**遍历:**指的是,把可迭代对象中的元素一个一个的取出来,再分别进行处理。


遍历往往需要搭配循环结构来实现。
2.5.1 for循环遍历
最简单的办法就是使用 for 循环
python
alist = [1, 2, 3, 4, 5]
for elem in alist:
print(elem)



也可以使用 for 按照范围生成下标,按下标访问。
python
alist = [1, 2, 3, 4, 5]
for i in range(0, len(alist)): # 左闭右开,访问范围[0, len-1]
print(alist[i])



方法1对临时变量进行修改,不会修改列表本身。
2.5.2 while循环遍历
还可以使用 while 循环,手动控制下标的变化。
python
alist = [1, 2, 3, 4, 5]
i = 0
while i < len(alist):
print(alist[i])
i += 1

最常用的是第一种,直接取alist里面的元素赋值给elem。
2.6 新增元素
2.6.1 append
使用 append (列表)方法,向列表末尾插入一个元素(尾插)。
python
alist = [1, 2, 3, 4]
alist.append(5)
alist.append('hello')
print(alist)


这种要搭配对象来使用的函数(function),也叫做方法(method)。
前面讲变量的时候,重点讲的是变量里面能存储数据。
对象则是不仅能存储数据,里面还能存储方法。
可以通过特定对象调用这些方法,来对对象进行操作。
append就是列表对象的内置方法。可以通过a.append的方式进行调用,来进行数据的插入操作。

方法不仅需要传参,还需要是指定针对哪个对象进行展开的。


代码更简洁了。
这里只是为了演示append的用法,才这样写的。
2.6.2 insert
使用 insert (列表)方法,向任意位置插入一个元素。
- 形参1:插入的位置------若越界,就插入在最后。
- 形参2:插入的数据。

python
alist = [1, 2, 3, 4]
alist.insert(1, 'hello')
alist.insert(100, 'hello')
print(alist)


插入时更常用的方法还是append。
2.7 查找元素
2.7.1 in操作符
使用in操作符,判定元素在列表中,返回值是布尔类型。
python
alist = [1, 2, 3, 4]
print(2 in alist)
print(10 in alist)

使用not in操作符,判定元素不在列表中,返回值是布尔类型。
python
a = [1, 2, 3, 4]
print(1 not in a)
print(10 not in a)

【in操作符查找的本质】遍历一遍列表,进行查找。
只是不用我们手动去遍历。
2.7.2 index方法
- 使用index方法,查找元素在列表中的下标。
- 返回值是一个整数。
- 如果元素不存在,则会抛出异常。
python
alist = [1, 2, 3, 4]
print(alist.index(2))
print(alist.index(3))
print(alist.index(10))

所以使用index方法,需要保证元素一定在。否则需要搭配异常的捕获代码。
由于C++、JAVA的下标不能是-1,所以可以返回-1(非法下标),表示查找的元素不存在。
python中允许负数下标,所以没办法返回-1。
没法返回任何一个非法的整数下标,就只能抛异常。
上述查找方法,本质都是遍历,只是把遍历过程在内部封装好了,我们只需要使用就好了
2.8 删除元素
2.8.1 pop方法
使用pop方法删除最末尾元素。
python
alist = [1, 2, 3, 4]
alist.pop()
print(alist)

pop 也能按照下标来删除元素。
python
alist = [1, 2, 3, 4]
alist.pop(2)
print(alist)

如果给一个不存在的下标,就会抛异常。

2.8.2 remove方法
使用remove方法,按照值删除元素。
python
alist = [1, 2, 3, 4]
alist.remove(2)
print(alist)

python
a = ['aa', 'bb', 'cc', 'dd']
a.remove('cc')
print(a)

给一个不存在的值,就会抛异常。

2.9 连接列表
2.9.1 +操作符
使用**+**能够把两个列表拼接在一起。

python
alist = [1, 2, 3, 4]
blist = [5, 6, 7]
clist = alist + blist
print(alist + blist)
print(alist)
print(blist)
print(clist)

+操作符在字符串、列表......等可迭代对象中的行为都是类似的。
2.9.2 extend方法
使用extend方法,相当于把一个列表拼接到另一个列表的后面。

python
alist = [1, 2, 3, 4]
blist = [5, 6, 7]
alist.extend(blist)
print(alist)
print(blist)


2.9.3 +=操作符

虽然**+=** 和extend的效果差不多,但是底层的实现细节是差异很大的。

a += b的写法是相对更低效的。
2.10 关于元组
元组的功能和列表相比,基本是一致的。
2.10.1 创建元组
元组使用 ( ) 来表示。
python
atuple = ( ) # 方式1
atuple = tuple() # 方式2

使用方式1创建元组,可以在创建元组的时候指定初始值。

和列表类似,元组中的元素也可以是任意类型。

元组不能修改里面的元素,列表则可以修改里面的元素。
因此,像读操作,比如访问下标、切片、遍历、in、index、+ 等,元组也是一样支持的。
但是,像写操作,比如修改元素、新增元素、删除元素、extend 等,元组则不能支持。
通过下标来访问元组中的元素,下标也是从 0 开始,到 len - 1 结束。

通过切片来获取元组中的一个部分。

也同样可以使用 for 循环等方式来进行遍历元素。

可以使用 in 来判定元素是否存在,使用 index 查找元素的下标。

可以使用 + 来拼接两个元组。

a元组和b元组本身都没变。
元组只是支持 "读" 操作,不能支持 "修改" 类操作。

另外,在 Python 中,很多时候:
默认的集合类型就是元组。
例如,当一个函数返回多个值的时候。
python
def getPoint():
return 10, 20
result = getPoint()
print(type(result))

此处的result的类型,其实是元组。
python
# 10. 当进行多元赋值的时候, 其实本质上是按照元组的方式来进行工作的~~
def getPoint():
x = 10
y = 20
return x, y
x, y = getPoint()
print(type(getPoint()))
getPoint的返回值是一个元组,= 支持右侧使用元组给左侧的变量们依次赋值。
python
x, y = 10, 20









【问题】既然已经有了列表,为啥还需要有元组?


这是不可变对象的优势------可哈希的,可以作字典的键。
包括多线程环境下,不可变对象天然就是线程安全的。
2.11 小结
- 列表和元组都是日常开发最常用到的类型,最核心的操作就是根据 [ ] 来按下标操作。
- 在需要表示一个 "序列" 的场景下,就可以考虑使用列表和元组。
- 如果元素不需要改变,则优先考虑元组。
- 如果元素需要改变,则优先考虑列表。
3. 字典
3.1 字典是什么
**字典:**是一种存储 键值对的结构。
啥是键值对?这是计算机/生活中一个非常广泛使用的概念。
把 键(key) 和 值(value) 进行一个一对一的映射,然后就可以根据键,快速找到值。


网络编程里面的http协议中就包含了很多组键值对。
3.2 创建字典
- 创建一个空的字典,使用 { } 表示字典。
python
a = { } # 方法1
b = dict() # 方法2
print(type(a))
print(type(b))

- 也可以在创建的同时指定初始值
- 键值对之间使用**,** 分割,键和值之间使用**:分割。(冒号后面推荐加一个空格)**
- 使用 print 可以打印字典内容。
python
student = { 'id': 1, 'name': 'zhangsan' }
print(student)


- 为了代码更规范美观,在创建字典的时候往往会把多个键值对,分成多行来书写。
python
student = {
'id': 1,
'name': 'zhangsan'
}
打印结果还是以紧凑形式呈现。
- 最后一个键值对,后面可以写**,**,也可以不写。
python
student = {
'id': 1,
'name': 'zhangsan',
}
3.3 查找 key
3.3.1 in操作符
使用in 可以判定 key 是否在字典中存在,返回布尔值。
python
student = {
'id': 1,
'name': 'zhangsan',
}
print('id' in student)
print('score' in student)

in 只是判定 key 是否存在,和 value 无关!
python
a = {
'id': 1,
'name': 'zhangsan'
}
print('zhangsan' in a)

由于zhangsan是作为值存在在字典中,所以使用in去判定的时候,会给出False的结果。
not in 来判定 key 在字典中不存在。
python
student = {
'id': 1,
'name': 'zhangsan',
}
print('id' not in student)
print('score' not in student)

3.3.2 []操作符
- 使用**[ ]**通过类似于取下标的方式,获取到key对应的value,只不过此处的 "下标" 是 key。
- key可能是整数,也可能是字符串等其他类型。
python
student = {
'id': 1,
'name': 'zhangsan',
}
print(student['id'])
print(student['name'])
如果 key 在字典中不存在,则会抛出异常。
python
student = {
'id': 1,
'name': 'zhangsan',
}
print(student['score'])

此处的a[100]的100不是下标,100是key。

3.4 新增/修改元素
使用**[ ]**可以根据 key 来新增/修改 value。
- 如果 key 不存在,对取下标操作赋值,即为新增键值对。
- 如果 key 已经存在,对取下标操作赋值,即为修改键值对的值。
【插入】
python
student = {
'id': 1,
'name': 'zhangsan',
}
student['score'] = 90
print(student)

读操作时,a[score]必须存在,否则抛异常。
写操作时,a[score]可以不在,就变成插入。
【修改】
python
student = {
'id': 1,
'name': 'zhangsan',
'score': 80
}
student['score'] = 90
print(student)

3.5 删除元素
使用pop方法根据 key 删除对应的键值对。
python
# 3. 使用 pop 方法, 根据 key 来删除键值对~
student = {
'id': 1,
'name': 'zhangsan',
'score': 80
}
print(student)
student.pop('score')
print(student)


生活中的很多数据是很复杂的,具有多重属性,很多时候都是选一个比较独特的属性去标识这些数据。
3.6 遍历字典元素

字典:擅长增删查改(效率常数级),不擅长遍历。
但是python还是提供字典的遍历方法。
随数据规模变化而改变时间的情景:班级站队报数,消耗时间随班级人数增加而增加。
随数据规模变化而固定时间的情景:班级站队点名,点名"张三",就是常数级的时间,是一个固定时间,无论当前有多少名同学,都是常数时间。
前提:只有一个张三。
字典里面的key就是唯一的,不能重复。
直接使用 for 循环能够获取到字典中的所有的 key,进一步的就可以取出每个值了。
python
student = {
'name': 'zhangsan',
'id': 1,
'score': 90
}
for key in student:
print(key, student[key])


python的字典,既是按照哈希表的形式来组织键值对,同时又有一个类似队列的结构来控制数据的先进先出。
3.7 取出所有 key 和 value
3.7.1 key方法
keys 方法:获取到字典中的所有的 key。
python
student = {
'id': 1,
'name': 'zhangsan',
'score': 80
}
print(student.keys())



3.7.2 value方法
values 方法:获取到字典中的所有 value。
python
student = {
'name': 'zhangsan',
'id': 1,
'score': 80
}
print(student.values())


3.7.3 items方法
items 方法:获取到字典中所有的键值对。
python
student = {
'id': 1,
'name': 'zhangsan',
'score': 80
}
print(student.items())


使用这些方法,也是可以完成字典的遍历。
python
a = {
'name': 'zhangsan',
'id': 1,
'score': 90
}
for key, value in a.items():
print(key, value)

a.items返回一个类似列表的结构,列表中的每一个元素是一个元组。
元组就可以通过多元赋值的方式,把数据给到key,value。
3.8 合法的 key 类型
不是所有的类型都可以作为字典的 key。
字典本质上是一个 哈希表 ,哈希表的 key 要求是 "可哈希的",也就是可以计算出一个哈希值。
- 可以使用hash函数计算某个对象的哈希值。
- 但凡能够计算出哈希值的类型,都可以作为字典的 key。
python
print(hash(0))
print(hash(3.14))
print(hash('hello'))
print(hash(True))
print(hash(())) # ( ) 是一个空的元组

- 列表无法计算哈希值。
python
print(hash([1, 2, 3]))

- 字典也无法计算哈希值。
python
print(hash({ 'id': 1 }))


3.9 小结
字典也是一个常用的结构,字典的所有操作都是围绕 key 来展开的。
需要表示 "键值对映射" 这种场景时就可以考虑使用字典。

4. 文件
4.1 文件是什么
变量是把数据保存到内存中,如果程序重启/主机重启,内存中的数据就会丢失。
要想能让数据被持久化存储,就可以把数据存储到硬盘中,也就是在 文件中保存。
在 Windows "此电脑" 中,看到的内容都是 文件。

通过文件的后缀名,可以看到文件的类型。常见的文件的类型如下:
- 文本文件(txt)
- 可执行文件(exe、dll)
- 图片文件(jpg、png、gif)
- 视频文件(mp4、mov)
- office 文件(ppt、docx)
- ......
咱们课堂上主要研究最简单的文本文件。



4.2 文件路径
一个机器上,会存在很多文件,为了让这些文件更方面的被组织,往往会使用很多的 "文件夹"(也叫做**目录)**来整理文件。
实际一个文件往往是放在一系列的目录结构之中的。
为了方便确定一个文件所在的位置,使用 文件路径来进行描述。

例如,本节首张截图中的 QQ.exe 这个文件,描述这个文件的位置,就可以使用路径
D:\program\qq\Bin\QQ.exe
来表示。
- windowsD: 表示 盘符。不区分大小写。
- 每一个 \ 表示一级目录,当前 QQ.exe 就是放在 "D 盘下的 program 目录下的 qq 目录下的 Bin 目录中"。
- 目录之间的分隔符,可以使用**\** 也可以使用**/** ,一般在编写代码的时候使用**/**更方便。


同一路径下不允许同名文件,也就使得文件路径和某个具体的文件是一一对应的。
windows诞生之前,主流的计算机都是使用正斜杠来分隔文件路径。
windows诞生的时候,就是使用反斜杠来分隔文件路径。
但是windows上两种风格都支持。macOS、linux下都是正斜杠。
转义字符:有些符号不太方便直接在字符串里面去输,使用转义字符的方式去代替表示。
双反斜杠在字符串里面才表示一个反斜杠。
单反斜杠在字符串里面是特定含义的转义字符。
4.3 文件操作
要使用文件,主要是通过文件来保存数据,并且在后续把保存的数据读取出来。
但是要想读写文件,需要先 "打开文件",读写完毕之后还要 "关闭文件"。
4.3.1 打开文件
使用内建函数open打开一个文件。
python
f = open('d:/test.txt', 'r')
- 第一个参数是一个字符串,表示要打开的文件路径。
- 第二个参数是一个字符串,表示打开方式。
- r表示按照读方式打开;
- w表示按照写方式打开;
- a 表示追加写方式打开;
- 如果打开文件成功,返回一个文件对象,后续的读写文件操作都是围绕这个文件对象展开。
- 如果打开文件失败(比如路径指定的文件不存在),就会抛出异常。

更多的打开方式请参考python的官方文档。




cpp
# 使用 open 打开一个文件.
f = open('d:/123pan/test.txt', 'r')
print(f)
print(type(f))


在目标路径下创建对应的文件之后:

'_io.TextIOWrapper' 是python内部给文件对象起的名字。

通过天子下达诏书,控制天下。
直接操作文件,在磁盘中不好操作。
于是在内存中搞一个遥控器,内存中好操作。
即直接定义一个变量(存储在内存中),对这个变量进行操作,进而控制文件的行为。
硬盘不好操作,就借助内存中的文件对象,间接操作硬盘。
就像电视,直接去电视上按按键,也能操控电视换台、调音量、开关机,但是不方便。
更多地还是远程操控,就依赖于遥控器。
4.3.2 关闭文件
使用close 方法关闭已经打开的文件。
python
f.close()
使用完毕的文件要记得及时关闭!
一个程序能同时打开的文件个数,是存在上限的。
python
flist = []
count = 0
while True:
f = open('d:/test.txt', 'r')
flist.append(f)
count += 1
print(f'count = {count}')



只打开,不关闭,就会造成资源的泄露。



每次打开文件,都把文件对象放到了列表中。
python
flist.append(f)
没有这一句代码,那就能一直打开文件了。(系统帮我们在每次循环结束时,关闭文件)


4.3.3 写文件
文件打开之后,就可以写文件了。
- 写文件,要使用写方式打开,open 第二个参数设为**'w'**。
- 使用write方法写入文件。
python
f = open('d:/test.txt', 'w')
f.write('hello')
f.close()

用记事本打开文件,即可看到文件修改后的内容。
- 如果是使用**'r'**方式打开文件,则写入时会抛出异常。
python
f = open('d:/test.txt', 'r')
f.write('hello')
f.close()

方式打开,其实又有两种情况:
- 直接写方式打开;
- 追加写方式打开;
- 使用**'w'**一旦打开文件成功,就会清空文件原有的数据。
- 使用**'a'**实现 "追加写",此时原有内容不变,写入的内容会存在于之前文件内容的末尾。
python
f = open('d:/test.txt', 'w')
f.write('world')
f.close()

之前的hello被覆盖了。
python
f = open('d:/test.txt', 'a')
f.write('world')
f.close()

之前的world还在。
如果希望追加的数据换行,就可以使用转义字符------换行符'\n'。


- 针对已经关闭的文件对象进行写操作,也会抛出异常。
python
f = open('d:/test.txt', 'w')
f.write('hello')
f.close()
f.write('world')
如果文件对象已经被关闭,那么意味着系统中和该文件相关的内存资源已经释放了。
强行去写,就会出异常。

对于关闭的文件,f.write('world')之前需要先重新打开文件f = open......。
使用write对文件的修改,在文件查看器上能看到,不会因为程序重启、电脑重启等情况而丢失。
4.3.4 读文件
(1)按字符
- 读文件内容需要使用**'r'**的方式打开文件。
- 使用read方法完成读操作,参数表示 "读取几个字符"。(是字符数,不是字节数)
python
f = open('d:/test.txt', 'r')
result = f.read(2)
print(result)
f.close()


先构造一个多行文件。


python
# 1. 使用 read 来读取文件内容. 指定读几个字符.
f = open('d:/123pan/test.txt', 'r', encoding='utf8')
result = f.read(2)
print(result)
f.close()

读取两个字符,一个汉字视为一个字符。(不是两字节)


open的encoding默认参数是跟随系统的。
(2)按行
更常见的需求,是按行来读取。
① for循环
如果文件是多行文本,可以使用 for 循环一次读取一行。
python
f = open('d:/Python环境/test.txt', 'r', encoding='utf8')
for line in f:
print(f'line = {line}')
f.close()




② readlines方法
- 使用readlines直接把文件整个内容读取出来,返回一个列表,每个元素即为一行。
使用 readlines 方法直接把整个文件所有内容都读出来,按照行组织到一个列表里。
python
f = open('d:/Python环境/test.txt', 'r', encoding='utf8')
lines = f.readlines()
print(lines)
f.close()

此处的 \n 即为换行符。
readlines比for循环的好处:
- readlines是一次性读完,for循环是分很多次读,读文件操作(读硬盘操作)比较低效。
- 一般来说,认为读的次数越多,耗时越长,分多次读,不如一次性读完。
这里能一次性读完,是因为文件不大,内存中放得下。
大型文件还是使用for循环分多次读更好。
4.4 关于中文的处理
当文件内容存在中文的时候,读取文件内容不一定就顺利。
同样上述代码,有的同学执行时可能会出现异常。

也有的同学可能出现乱码。

计算机表示中文的时候,会采取一定的编码方式,我们称为 "字符集"。

必须要保证文件本身的编码方式,和 Python 代码中读取文件使用的编码方式匹配,才能避免出现上述问题。

使用记事本打开文本文件,在 "菜单栏" -> "文件" -> "另存为" 窗口中,可以看到当前文件的编码方式。

如果此处的编码为ANSI ,则表示GBK编码。
如果此处为UTF-8 ,则表示UTF-8编码。
此时修改打开文件的代码,给 open 方法加上encoding参数,显式的指定为和文本相同的字符集,问题即可解决。
python
f = open('d:/test.txt', 'r', encoding='utf8')

4.5 使用上下文管理器
打开文件之后,是容易忘记关闭的。
Python 提供了 上下文管理器 ,来帮助程序猿自动关闭文件。
- 使用with语句打开文件。
- 当with内部的代码块执行完毕后,就会自动调用关闭方法。
python
with open('d:/test.txt', 'r', encoding='utf8') as f:
lines = f.readlines()
print(lines)
有些情况还是非常容易遗漏 close 的,防不胜防。
python
def func():
f = open('d:/123pan/test.txt', 'r', encoding='utf8')
# 中间来写其他的操作文件的逻辑~~
f.close()
先把open和fclose写上,再写中间的代码逻辑。
但是这并不保险------万一中间的代码里,有:条件判定、函数返回、抛出异常、......
python
def func():
f = open('d:/123pan/test.txt', 'r', encoding='utf8')
# 中间来写其他的操作文件的逻辑~~
# 万一中间的代码里, 有 条件判定, 函数返回, 抛出异常~
if cond:
# 进行条件处理
# ......
return
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# ......
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# ......
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
f.close()
一旦中间哪个条件语句的内部忘了关文件,就会造成资源泄露。
如果说想着每个if语句内都加上close。
python
def func():
f = open('d:/123pan/test.txt', 'r', encoding='utf8')
# 中间来写其他的操作文件的逻辑~~
# 万一中间的代码里, 有 条件判定, 函数返回, 抛出异常~
if cond:
# 进行条件处理
# ......
f.close()
return
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
if cond:
# 进行条件处理
f.close()
return
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
if cond:
return
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# ......
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
# 另外一些代码.
f.close()
也是不推荐的,因为很多代码通常都是多人维护的,一旦有人没注意加了if语句但是里面没close,就会造成资源泄露。
就算能保证自己这里没问题,也无法保证别人那里没问题。
因此,为了避免这些问题,使用上下文管理器就很有必要了。
python
# 使用上下文管理器, 就能解决这个问题.
def func():
with open('d:/test.txt', 'r', encoding='utf8') as f:
# 进行文件这里的处理逻辑
# 假设这里有很多代码
# 假设这里有很多代码
if cond:
return
# 假设这里有很多代码
# 假设这里有很多代码
if cond:
return
# 假设这里有很多代码
# 假设这里有很多代码
if cond:
return
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
# 假设这里有很多代码
这里就不是使用等号赋值接收open返回值了,而是使用with语句,通过as赋值。
f同样是接收open的返回值,但是通过with监控起来了。
冒号下面是代码块,需要缩进。
不过在那个if语句内return了,只要结束了with下面的代码块,都会由with自动地针对f调用close。
把文件关闭的工作委托给with来完成。

这样就能及时释放资源,避免资源泄露。
完