1.7 文件操作
1.7.1编码格式的转化
windows系统的默认编码是GBK, 如果你把⼀段在windows系统上⽤gbk编码的字符发 送到mac电脑 上, mac默认编码是utf-8, 那这段⽂字是乱码显示的。同时python中的字符编码是unicode,对python文件的编码格式是utf-8。
name = "利物浦" # 这是unicode编码
print(name) # 输出利物浦
name_2_utf8 = name.encode('utf-8'). decode('utf-8') # 转化为utf-8编码
print(name_2_utf8) # 输出利物浦
s = "利物浦"
print(s.encode("GBK")) # 模拟转化为GBK编码 # 输出 b'\xc0\xfb\xce\xef\xc6\xd6'
s.encode("GBK").decode("GBK") # 转为unicode编码
print(s.encode("utf-8")) # 在转换为GKB格式在转为utf-8格式 # 输出 b'\xe5\x88\xa9\xe7\x89\xa9\xe6\xb5\xa6'
编码后要转成bytes字节类型:为了防⽌显示乱码, 如在mac电脑上,不⽀持gbk编码,如果直接以原格式打印,就会乱码。 但⽤字节形式表示,就没这个问题了。
1.7.2操作文件
python文件操作流程:
1.打开或创建一个文件 2.选择读还是修改 3.保存并关闭文件
选择相应的文件模式(也就是open()
模式的选择):
t | 文本模式 (默认)。 |
---|---|
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(不推荐)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
按照上述流程进行创建文件并写入相应的内容,举例如下:
filename = "liverpool.txt"
f = open(filename, mode="w")
f.write("name:liverpool\n")
f.write("age:23\n")
f.close()
按照上述流程进行创建文件并读取相应的内容,举例如下
filename = "liverpool.txt"
f = open(filename, mode="r")
print(f.readline()) # 读取文件指针在的一行内容
print(f.read()) # 读取文件指针之后的所有内容
按照上述流程进行创建文件并追加相应的内容,举例如下
f = open(filename, "a")
f.write("sex:man\n")
f.close()
并且open
方法可以直接使用绝对路劲来创建文件,如以下这个例子:
f = open(file='D:/Demo/file_test.txt',mode='w')
f.write("name:Liverpool\n")
文件使用混合模式打开(可读可写):
一般推介使用r+
这种模式打开文件的混合模式,这里需要注意一点就是,文件的读取位置是在文件开头,但是文件的写位置确实在文件的最后,所以通常会使用读一下文件光标位置,然后在确定seek()
一下光标位置,那么就可以保证读写光标位置的一致性。但是如果这要写入的话会改变原来文件的内容。举例如下:
filename = "liverpool.txt"
f = open(filename, mode="r+")
f.seek(f.tell())
f.write("sex:man")
f.close()
1.7.2.1 其他文件操作的重要方法
1.seek()
:
可以直接对文本文件的光标指针进行定位,但是这个方法是按字节进行定位的,所以对于utf-8编码来说,汉字的编码是3个字节,数字和字母的编码是1个字节,同时还要注意每行文本结束后,还有一个换行符,占一个字节,举例如下:
f = open(filename, mode="r")
f.seek(4)
print(f.readline()) # 输出为 :liverpool
同时在写文件中也可以使用seek()
方法。
2.tell()
:
这个方法主要是告诉文件中光标的位置
f = open(filename, mode="r")
print(f.readline())
print(f"光标的位置:{f.tell()}") #输出:光标的位置:16
3.flush()
在python中操作文件的速度很慢,为了提高文件的操作速度,所以需要在内存中开辟一块缓存(buffer)中,在文件关闭时候,会统一把在内存中修改的数据保存到硬盘中。或者可以使用flush()
这个方法直接保存。
filename = "liverpool.txt"
f = open(filename, mode="w")
f.write("name:liverpool\n")
f.write("age:23\n")
f.flush()
f.close()
其他文件的操作:
def mode(self) -> str:
返回⽂件•打开的模式
def name(self) -> str:
返回⽂件名
def fileno(self, *args, **kwargs): # real signature unknown
返回⽂件句柄在内核中的索引值,以后做IO多路复⽤时可以⽤到
def flush(self, *args, **kwargs): # real signature unknown
把⽂件从内存buffer⾥强制刷新到硬盘
def readable(self, *args, **kwargs): # real signature unknown
判断是否可读
def readline(self, *args, **kwargs): # real signature unknown
只读⼀⾏,遇到\r or \n为⽌
def seek(self, *args, **kwargs): # real signature unknown
把操作⽂件的光标移到指定位置
*注意seek的⻓度是按字节算的, 字符编码存每个字符所占的字节⻓度不⼀样。
如“路⻜学城” ⽤gbk存是2个字节⼀个字,⽤utf-8就是3个字节,因此以gbk打开时,
seek(4) 就把光标切换到了“⻜”和“学”两个字中间。
但如果是utf8,seek(4)会导致,拿到了⻜这个字的⼀部分字节,打印的话会报错,因为处理剩
下的⽂本时发现⽤utf8处理不了了,因为编码对不上了。少了⼀个字节
def seekable(self, *args, **kwargs): # real signature unknown
判断⽂件是否可进⾏seek操作
def tell(self, *args, **kwargs): # real signature unknown
返回当前⽂件操作光标位置
def truncate(self, *args, **kwargs): # real signature unknown
按指定⻓度截断⽂件
*指定⻓度的话,就从⽂件开头开始截断指定⻓度,不指定⻓度的话,就从当前位置到⽂件尾部
的内容全去掉。
def writable(self, *args, **kwargs): # real signature unknown
判断⽂件是否可写
1.7.3 遍历文件
使用for循环可以对文本文件进行一个遍历,或者使用readlines()
方法就可以读取整个文件的内容,用列表的方式固定下来。举例如下:
1. ⻢纤⽻ 深圳 173 50 13744234523
2. 乔亦菲 ⼴州 172 52 15823423525
3. 罗梦⽵ 北京 175 49 18623423421
4. 刘诺涵 北京 170 48 18623423765
5. 岳妮妮 深圳 177 54 18835324553
6. 贺婉萱 深圳 174 52 18933434452
7. 叶梓萱 上海 171 49 18042432324
f = open(file='美女联系方式.txt',mode='r',encoding='utf-8')
for line in f:
line = line.split()
id,name,addr,height,weight,phone = line
height = int(height)
weight = int(weight)
if height > 170 and weight <= 50: # 只打印身⾼>170 and 体᯿<=50的
print(line)
f.close()
1.7.4操作二进制文件
复制一张图片到指定文件,举例如下:
f = open("liverpool.jpg", mode="rb")
copy_file = open("liverpool_copy.jpg",mode="wb")
for line in f:
copy_file.write(line)
1.7.5文件修改
因为文件是存储在硬盘上的,而硬盘存储一般都是非连续存储的,所以不能随便增加或删除硬盘的内容,一盘需要从硬盘上读取数据到内存中,完成修改后,重新写回硬盘中。所以文件修改的步骤一般如下:
1.打开文件,把文件读取到内存中
2.清空原文件
3.把新内容写回硬盘
举例如下:
# 第一种方法
filename = "美女联系方式.txt"
f = open(file=filename, mode="r+",encoding="utf-8")
# 1.加载到内存中
data = f.read()
new_data = data.replace("深圳", "上海")
# 2.清空文件
f.seek(0)
f.truncate() # 截断文件
# 3.把新内容写回到硬盘中
f.write(new_data)
这是第一种方法,这种方法的缺点是需要的内存较大,遇到大文件的时候可能会卡死
第二种方法就是,打开两个文件,从源文件一行一行的读取数据进行修改后,在一行一行的写入新文件,然后使用新文件去替代旧文件,这种方法需要的内存较少,但是所需要的时间是更多的。
1.打开两个文件,一行行的读取内容写入新文件
2.用新文件替代旧文件
# 第二种方法
import os
filename = "美女联系方式.txt"
filename_new = "".join([filename.split(".")[0], "_new.txt"])
f = open(file=filename, mode="r", encoding="utf-8")
f_new = open(file=filename_new, mode="w", encoding="utf-8")
old_str = "上海"
new_str = "新加坡"
for line in f:
if old_str in line:
new_line = line.replace(old_str, new_str)
else:
new_line = line
f_new.write(new_line)
f.close()
f_new.close()
os.replace(filename_new, filename) # 用新文件来代替旧文件
1.7.6相关程序的练习
1.全局⽂本检索替换 写⼀个脚本,允许⽤户按以下⽅式执⾏时,即可以对指定⽂件内容进⾏全局替换,且替换完毕后打印替 换了多少处内容,脚本调用方式如下:
python your_script.py old_str new_str filename
# 1.全局文本检索替换
import sys
# 读取输入脚本的信息
old_str = sys.argv[1]
new_str = sys.argv[2]
filename = sys.argv[3]
# 打开文件并内容读取到内存中
f = open(filename, mode="r+",encoding="utf-8")
data = f.read()
# 统计旧的字符串并且使用新的字符串替代旧的字符串
old_str_count = data.count(old_str)
new_data = data.replace(old_str, new_str)
# 清除旧文件的内容
f.seek(0)
f.truncate()
f.write(new_data) # 写入新的内容
f.close()
print(f"成功替换字符{old_str}为{new_str},共{old_str_count}个")
2.用户登录认证程序
要求⽤户输⼊帐号密码进⾏登陆 ;⽤户账号信息保存在⽂件内; ⽤户密码输⼊错误三次后锁定⽤户,下次再登录,检测到是这个被锁定的⽤户,则依然不允许其它 登录,提示已被锁
# 2.用户登录认证程序
accounts = {} # 创建字典用来存储文件中的数据
f = open(file="用户登录程序认证.txt", mode="r",encoding="utf-8")
# 把账号信息读到内存中去
f.readline() # 去除首行
for line in f:
line = line.strip().split(",") # 去除换行符并以逗号进行分割
accounts[line[0]] = line
# 用户输入账号信息进行对比
while True:
user = input("Username:").strip()
if user not in accounts: # 用户未注册
print("该用户未注册")
continue
elif accounts[user][2] == "1": # 代表该用户已经被锁定
print("此用户已被锁定")
continue
count = 0
while count < 3:
password = input("Password:").strip()
if password == accounts[user][1]: # 判断密码是否正确
print(f"{user}登录成功")
exit()
else:
print("Wrong password")
count += 1
# 对是否需要锁定进行判断和处理
if count == 3:
print(f"输错了{count}次密码,需要锁定{user}该用户")
accounts[user][2] = "1" # 表示对该用户进行锁定
f2 = open("用户登录程序认证.txt", mode="w")
for user, value in accounts.items():
line = ",".join(value) + "\n"
f2.write(line)
f2.close()
exit()
3.股票数据分析&处理
开发程序对stock_data.txt进⾏以下操作: 1. 程序启动后,给⽤户提供查询接⼝,允许⽤户᯿复查股票⾏情信息(⽤到循环) 2. 允许⽤户通过模糊查询股票名,⽐如输⼊“啤酒”, 就把所有股票名称中包含“啤酒”的信息打印出来 3. 允许按股票价格、涨跌幅、换⼿率这⼏列来筛选信息,⽐如输⼊“价格>50”则把价格⼤于50的股票 都打印,输⼊“市盈率<50“,则把市盈率⼩于50的股票都打印,不⽤判断等于.
# 3.股票数据分析&处理
f = open("stock_data.txt", "r", encoding="utf-8")
query_columns = ["最新价", "涨跌幅", "换手率"]
columns = f.readline().strip().split(',') # 去除首行
stock_data = {} # 创建字典用来记录股票数据
for line in f:
line = line.strip().split(',')
name = line[1]
stock_data[name] = line # 创建股票数据dict{name:data...}
while True:
count = 0
cmd = input("输入查询指令>>:").strip()
if len(cmd) == 0:
continue
print(columns)
# 按股票名字来进行搜索
for s_name in stock_data:
if cmd in s_name:
print(stock_data[s_name])
count += 1
# 按数据来进行搜索
if ">" in cmd:
q_name, q_val = cmd.split('>')
q_name = q_name.strip()
q_val = float(q_val)
if q_name in query_columns:
q_name_index = columns.index(q_name)
for s_name, s_vals in stock_data.items():
if float(s_vals[q_name_index].strip('%')) > q_val:
print(s_vals)
count += 1
elif "<" in cmd:
pass
if count > 0:
print("匹配到%s条" % count)
1.8 函数
定义: 函数是指将⼀组语句的集合通过⼀个名字(函数名)封装起来,要想执⾏这个函数,只需调⽤其函数名即可。
特性:1. 减少重复代码 2. 使程序变的可扩展 3. 使程序变得易维护
def fun1():#函数名
print("Hello, I'm nobody!")
fun1() #调⽤函数
1.8.1函数参数
形参变量:只有在被调⽤时才分配内存单元,在调⽤结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调⽤结束返回主调⽤程序代码后则不能再使⽤该形参变量。
实参:可以是变量、任意数据类型、表达式、函数等,⽆论实参是何种类型的量,在进⾏函数调⽤时,它们都 必须有确定的值,以便把这些值传送给形参。因此应预先给实参赋值
默认参数:就是在函数定义的时候,形参被赋予一个固定值,默认参数如果在函数调用的时候,如果不给相应的形参赋值,那就使用默认参数,注意:默认参数一定要放在所有形参的最后面(防止出现歧义)。
关键参数(指定参数):正常情况下,给函数传参数要按顺序,不想按顺序就可以⽤关键参数,只需指定参数名即可(指定了参数名的参数就叫关键参数),但记住⼀个要求就是,关键参数必须放在位置参数(以位置顺序确定对应关系的参数)之后,并且在一个函数调用中
fun("liverpool",country="China") # 位置参数的优先级大于指定参数
非固定参数:如果函数在定义时不确定⽤户想传⼊多少个参数,就可以使⽤⾮固定参数
通常来说有两种定义方法:
def fun(name,age,*args) # *arg 会把多传入的参数变成一个元组形式
def fun(name,age,**kwargs) # *kwarg 会把多传入的参数变成一个字典
1.8.2函数返回值
函数外部的代码要想获取函数的执⾏结果,就可以在函数⾥⽤return语句把结果返回。不同于C语言,python中可以返回多个变量(单数必须一起返回,因为函数中一遇到return
,就表明该函数已经结束了),本质上返回的还是一个变量,只是这个变量是元组而已。注意如果未在函数中指定return,那这个函数的返回值为None
例如:
def stu_register(name, age, course='数学', country='CN'):
print("----注册学⽣信息------")
print("姓名:", name)
print("age:", age)
print("国籍:", country)
print("课程:", course)
if age > 22:
return False
else:
return True,name,age,course,country
1.8.3局部变量和全局变量
在函数中定义的变量称为局部变量,在程序的⼀开始定义的变量称为全局变量。
全局变量作⽤域(即有效范围)是整个程序,局部变量作⽤域是定义该变量的函数。
变量的查找顺序是局部变量>全局变量:当全局变量与局部变量同名时,在定义局部变量的函数内,局部变量起作⽤;在其它地⽅全局变量 起作⽤。 在函数⾥是不能直接修改全局变量的。
如果想要在函数中,对全局变量进行修改,那就只能使用global
这个关键字,但是在函数中使用的时候需要声明(相当于在函数中定义了一个全局变量 如果外部有定义的相同命名的全局变量那就会直接进行替代),但是这里不进行推荐,容易造成逻辑混乱。如下
def fun():
global 变量 # 声明一个全局变量 函数外部也可以使用
特殊情况:只有当形参传入的是字典,列表等(非连续内存的数据类型时),函数可以对形参中的数据内容进行修改。原因是:字典或列表在python是非连续的内存空间进行存储,相当于C语言中的链表进行存储,函数中直接传入的是放入的是这个数据类型的首个内存地址中的内容(只有连接后面地址的头指针),而这个头指针是在堆栈中实现,在函数中进行修改,在函数运行结束后,该数据类型的头指针位置进行退栈,自然不能保存对这个数据类型头指针的操作处理,而对这个数据类型中的后续内容进行修改时,是在各自内存地址上对内容进行修改,并没有进行进栈和退栈操作,相当于全局修改,也可以函数直接调用外部所定义的这些数据类型进行内容的修改。如下所示:
informational = {"name": "liverpool", "age": 23, "sex": "man"}
list1 = [1, 2.5, 3, "liverpool", [9, 8, 1]]
def fun1(dict1, list1):
dict1.update(name="Liver") # 可以修改
list1.append("中国") # 可以修改
dict1 = {} # 不能修改
fun1(informational, list1)
print(informational, list1) #输出:{'name': 'Liver', 'age': 23, 'sex': 'man'} [1, 2.5, 3, 'liverpool', [9, 8, 1], '中国']
1.8.4匿名函数
python中的匿名函数实现用lambda
这个关键字定义使用,该语法只包含一个语句,表现形式如下:
lambda [参数列表]:表达式
**lambda 函数有输入和输出:**输入是传入到参数列表t的值,输出是根据表达式计算得到的值。
**lambda 函数拥有自己的命名空间:**不能访问自己参数列表之外或全局命名空间里的参数,只能完成非常简单的功能。
lamda函数的几种简单用法:
1.将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数,代码中可以直接使用这个变量的方法,来使用这个函数
add = lambda x, y: x + y
print(add(1, 2))
2.将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换
# 为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:
time.sleep=lambda x: None
# 这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。
# 例如:
time.sleep(3) # 程序不会休眠 3 秒钟,而是因为lambda输出为None,所以这里结果是什么都不做
3.将lambda函数作为参数传递给其他函数
Ⅰ.和map()
函数联合使用
map() 会根据提供的函数对指定序列做影射,第一个参数以参数序列中的每一个元素调用函数,返回包含每次函数返回值的新列表。
在python3中返回值是迭代值,也就是没有执行的。
语法如下:
map(函数,参数列表) # 这个参数列表可以时一个或多个序列
举例如下:
# map 参数只有一个参数列表
square_answer = map(lambda x: x ** 2, [1, 2, 3, 4])c # 返回的是迭代器 没有执行
square_answer_List = []
for i in square_answer: # 现在才开始执行
square_answer_List.append(i)
print(square_answer_List)
# map 参数两个个参数列表
add = lambda x, y: x + y
add_answer = map(add, [1, 2, 3], [1, 2, 3])
add_answer_list = []
for i in add_answer:
add_answer_list.append(i)
print(add_answer_list)
Ⅱ.和reduce()
函数联合使用
reduce() 函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。返回值是函数计算结果。
reduce(函数(有两个参数),可迭代对象(可选)[,初始参数(可选)])
举例如下:
from functools import reduce
print(reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])) # 输出的值是15
Ⅲ.和其他函数联合使用
和sorted()
函数联合使用
students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
print(sorted(students, key=lambda s: s[2])) # 按key=2进行排序
#输出: [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
和 filter()
函数联合使用
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。返回函数迭代器对象。
filter(函数,可迭代对象)
举例如下:
newlist = filter(lambda x: x % 3 == 0, [1, 2, 3])
print(list(newlist)) # 输出: 3
1.8.5相关程序练习
学籍注册小程序
- 要求⽤户输⼊姓名、年龄、⼿机号、身份证号、所选课程,然后为学员完成注册
- ⼿机号、身份证号唯⼀
- 可选的课程只能从Python、Linux、⽹络安全、前端、数据分析 这⼏⻔⾥选
- 学员信息存⼊⽂件
course_list = ["python", "C", "C++", "Java"]
db_student = "student_data.db"
phone_list = []
id_num_list = []
def register_api():
student_data = {}
print("欢迎来到选课系统".center(50, "-"))
print("请完成学籍注册")
name = input("姓名:").strip()
age = input("年龄:").strip()
phone = input("手机号:").strip()
if phone in phone_list:
exit("该手机号已经注册")
id_number = input("身份证号:").strip()
if id_number in id_num_list:
exit("该身份证号已经被注册")
for index, course in enumerate(course_list):
print(f"{index}.{course}")
# 选择课程功能的实现和输入校验
selected_course = input("选择想学的课程编号:").strip()
if selected_course.isdigit():
selected_course = int(selected_course)
if 0 <= selected_course < len(course_list): # 判断是否输入的是合法选项
picked_course = course_list[selected_course]
else:
exit("不合法的选项")
else:
exit("非法输入")
# 保存数据
student_data["name"] = name
student_data["age"] = age
student_data["phone"] = phone
student_data["id_number"] = id_number
student_data["course"] = picked_course
return student_data
def commit_to_db(filename, stu_data):
"""
把学员的信息保存到目标文件中
:param filename:想要保存的文件的名字
:param stu_data:当个学员数据的字典
:return
:author: liverpool
"""
file = open(filename, "a")
row = f"{stu_data['name']},{stu_data['age']},{stu_data['phone']},{stu_data['id_number']},{stu_data['course']}\n"
file.write(row)
file.close()
def load_validated_data(filename):
"""
从学生数据保存文件中读取相应的电话和身份证信息并保存到内存中
:param filename :想要读取的文件名字
:return phonelist,id_num_list
:Author liverpool
"""
f = open(filename, "r")
phone_list1 = []
id_num_list1 = []
for line in f:
line = line.strip().split(",")
if len(line) < 4:
break
phone = line[2]
id_num = line[3]
phone_list1.append(phone)
id_num_list1.append(id_num)
return phone_list1, id_num_list1
if __name__ == "__main__":
phone_list, id_num_list = load_validated_data(db_student)
student_data = register_api()
commit_to_db(db_student, student_data)