【Python基础】4-文件处理

不同字符编码间的转换

GBK编码的字符如何转成utf-8存储?

上节课我们讲过,windows系统的默认编码是GBK, 如果你把⼀段在windows系统上⽤gbk编码的字符发送到mac电脑 上, mac默认编码是utf-8, 那这段⽂字是乱码显示的。 如何实现在mac上正常显示这段gbk⽂本呢?

为何要先转成Unicode呢?

若你认真听了昨天的编码课,你应该知道,gbk和utf-8之间没任何关系的,就跟中⽂跟⽇语没关系⼀样。 ⽽Unicode是和所有的编码都有映射关系,所以可以先转成unicode, 再转成utf-8

编码与解码

把unicode转换成任意编码的过程 ,都叫编码,把内存⾥的数据存到⽂件⾥,需要编码过程

>>> s = "路⻜" # unicode格式
>>> s.encode("utf-8") # 编码成utf-8
b'\xe8\xb7\xaf\xe9\xa3\x9e'

任意编码转换成unicode的过程,都叫解码,⼀般把⽂件内容读到内存时,需要进⾏解码过程

> s
'路⻜'
>> s.encode("utf-8")
b'\xe8\xb7\xaf\xe9\xa3\x9e'
>>
>> s.encode("utf-8").decode("utf-8") # 把utf-8编码的字符再解码成unicode
'路⻜'

更详细例⼦

>>> s
'路⻜'
>>> s.encode("utf-8") # 编码成utf-8
b'\xe8\xb7\xaf\xe9\xa3\x9e' # 会变成bytes字节格式, 后边会解释为什么。
>>>
>>> s.encode("gbk") # gbk ⼀个中⽂2个字节
b'\xc2\xb7\xb7\xc9'
>>>
>>>
>>> s.encode("gbk").decode() # gbk要解成unicode
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb7 in position 2:
invalid start byte
>>> # 思考上⾯为何报错了呢? 因为decode()函数不跟参数的话,会以解释器默认编码来解码,py3的默认编码是utf-8, 但你这是个gbk格式的, 你却告诉解释器,我是utf-8的字条,那解释器就会以utf-8来解码成unicode, 结果肯定出错呀。
>>> s.encode("gbk").decode("gbk") # 必须明确指定这是gbk字符
'路⻜'

bytes字节类型是⽤16进制表示的, 像这样 \xe8 这样2个16进制数是代表⼀个字节(因为⼀个16进制是占4位,2个就是8位,共1个字节啦)0-15

字节类型是什么东东?

你可以理解为,字节类型就是2进制格式 , 只不过为了易于理解和⼈类⾁眼计算,是⽤16进制来表示⽽已。

为何编码后要转成bytes字节类型?

为了防⽌显示乱码, 我的mac电脑上,不⽀持gbk编码,如果直接以原格式打印,就会乱码。 但⽤字节形式表示,就没这个问题 了。

下⾯⽤py2来演示下乱码效果

Alexs-MacBook-Pro:~ alex$ python # 注意这是py2
Python 2.7.10 (default, Aug 17 2018, 17:41:52)
[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> s = "路⻜"
>>> s
'\xe8\xb7\xaf\xe9\xa3\x9e' # py2默认编码是ascii,但是进⼊交互器格式 , 相当于在开头进⾏了-*-encoding:utf-8 -*- , 所以我可以输出中⽂, 你的windows上会默认⽤gbk
>>>
>>> s.decode("utf-8") # 解码成unicode
u'\u8def\u98de' # 在py3上,没有开头带u的类型了, 因为内存⾥所有字符默认都是unicode
>>>
>>> s.decode("utf-8").encode("gbk") # 转成了gbk编码
'\xc2\xb7\xb7\xc9'
>>>
>>> print(s.decode("utf-8").encode("gbk"))
·?? # 乱码了。。。
>>>

这是因为,在python2上,print时没有打印字节码,⽽是尝试⽤utf-8的编码去解释这段gbk字符,⾃然就乱了。

⽂件操作

python操作⽂件流程

⽤word操作⼀个⽂件的流程如下

  1. 找到⽂件,双击打开
  2. 读或修改
  3. 保存&关闭

⽤python操作⽂件也差不多:

f=open(filename) # 打开⽂件
f.write("我是ᰀ⽣程序员") # 写操作
f.read() #读操作
f.close() #保存并关闭

不过有⼀点跟⼈⾁操作word⽂档不同,就是word⽂档只要打开了,就即可以读、⼜可以修改。 但Python⽐较变态,只能以读、创建、追加 3种模式中的任意⼀种打开⽂件,不能即写⼜读。

⽂件打开模式(⽂本模式)

  1. r 只读模式

  2. w 创建模式,若⽂件已存在,则覆盖旧⽂件

  3. a 追加模式,新数据会写到⽂件末尾

创建⽂件

f = open(file='D:/⼯作⽇常/staff.txt',mode='w') # 若⽂件已存在,则覆盖
f.write("Alex CEO 600\n")
f.write("⿊姑娘 ⾏政 5000\n")
f.close()

只读模式

f = open(file='兼职⽩领学⽣空姐模特护⼠联系⽅式.txt',mode='r')
print(f.readline()) # 读⼀⾏
print('------分隔符-------')
data = f.read() # 读所有,剩下的所有
print(data)
f.close()

执⾏输出

⻢纤⽻ 深圳 173 50 13744234523 # print(f.readline()) # 读⼀⾏
------分隔符-------
乔亦菲 ⼴州 172 52 15823423525 # f.read() # 读所有,剩下的所有
罗梦⽵ 北京 175 49 18623423421
刘诺涵 北京 170 48 18623423765
岳妮妮 深圳 177 54 18835324553
贺婉萱 深圳 174 52 18933434452
叶梓萱 上海 171 49 18042432324

追加模式

f = open(file='兼职⽩领学⽣空姐模特护⼠联系⽅式.txt',mode='a')
f.write("⿊姑娘 北京 168 48\n") # 会追加到⽂件尾部
f.close()

循环⽂件

数据源:

  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')
for line in f:
 line = line.split()
 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()
['⻢纤⽻', '深圳', '173', '50', '13744234523']
['罗梦⽵', '北京', '175', '49', '18623423421']
['叶梓萱', '上海', '171', '49', '18042432324']

⼆进制模式操作⽂件

上⾯操作的只是⽂本⽂件 ,但是如果遇到视频呀、图⽚呀,你直接打开的话会报错

为何报utf-8不能解码的错?

是因为,open()有个encoding参数,默认是None, 是⽤来告诉解释器,要操作的这个⽂件 是什么编码。不填的话,就⽤解释器默认编码,即utf-8。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None,
closefd=True, opener=None)

如果你是⼀个gbk编码的⽂件 ,就必须指定 encoding=gbk

f = open("gbk_file",encoding="gbk")
for line in f:
 print(line)

但是像图⽚、视频,是有⾃⼰特殊的编码的,⽽⾮什么unicode\utf-8这样的⽂本编码 。 所以要操作这样的⽂件 ,你⽤utf-8什么的去解,⾃然会报错。

如何处理图⽚、视频⽂件呢?

可以⽤2进制模式打开⽂件

  1. rb 2进制只读模式

  2. wb 2进制创建模式,若⽂件已存在,则覆盖旧⽂件

  3. ab 2进制追加模式,新数据会写到⽂件末尾

  4. 这样,你读出来的数据,就是bytes字节类型了,当然写进去的也必须是bytes格式了

f = open("gbk_file2","wb")
f.write("哈".encode("gbk") ) # 写⼊的⽂本要⽤字节类型

其它功能

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
 判断⽂件是否可写

⽂件打开模式(混合模式)

其实我⼀直像你隐瞒,因为怕你觉得复杂。 打开⽂件其实还有3种混合模式

w+ 写读 , 这个功能基本没什么意义,它会创建⼀个新⽂件 ,写⼀段内容,可以再把写的内容读出来,没什么卵⽤。

r+ 读写,能读能写,但都是写在⽂件最后,跟追加⼀样

a+ 追加读,⽂件 ⼀打开时光标会在⽂件尾部,写的数据全会是追加的形式

r+模式

因为默认就是往⽂件 尾部写

修改⽂件

尝试直接以r+模式打开⽂件,默认会把新增的内容追加到⽂件最后⾯。但我想要的是修改中间的内容 ,怎么办? 为什么会把内容添加到尾部呢?(最新测试r+会从头覆盖,测试代码如下)

问:为什么原有数据会被覆盖呢?

这是硬盘的存储原理导致的,当你把⽂件存到硬盘上,就在硬盘上划了⼀块空间,存数据,等你下次打开这个⽂件 ,seek到⼀个位置,每改⼀个字,就是把原来的覆盖掉,如果要插⼊,是不可能的,因为后⾯的数据在硬盘上不会整体向后移。所以就出现 当前这个情况 ,你想插⼊,却变成了会把旧内容覆盖掉。

问:但是⼈家word, vim 都可以修改⽂件 呀,你这不能修改算个什么玩意?

我并没说就不能修改了,你想修改当然可以,就是不要在硬盘上修改,把内容全部读到内存⾥,数据在内存⾥可以随便增删改查,修改之后,把内容再全部写回硬盘,把原来的数据全部覆盖掉。vim word等各种⽂本编辑器都是这么⼲的。

问:说的好像有道理,但你⼜没看过word软件的源码,你凭什么这么笃定?

哈哈,我不需要看源码,硬盘 的存储原理决定了word必须这么⼲ ,不信的话,还有个简单的办法来确认我说的,就是⽤word or vim读⼀个编辑⼀个⼤⽂件 ,⾄少⼏百MB的,你 会发现,加载过程会花个数⼗秒,这段时间⼲嘛了? cpu 去玩了?去上厕所啦? 当然不是,是在努⼒把数据 从硬盘上读到内存⾥。

问:但是⽂件如果特别⼤,⽐如5个GB,读到内存,就⼀下⼦吃掉了5GB内存,好费资源呀,有没有更好的办法呢?

如果不想占内存,只能⽤另外⼀种办法啦,就是边读边改, 什么意思? 不是不能改么?是不能改原⽂件 ,但你可以打开旧⽂件 的同时,⽣成⼀个新⽂件呀,边从旧的⾥⾯⼀⾏⾏的读,边往新的⼀⾏⾏写,遇到需要修改就改了再写到新⽂件 ,这样,在内存⾥⼀直只存⼀⾏内容。就不占内存了。 但这样也有⼀个缺点,就是虽然不占内存 ,但是占硬盘,每次修改,都要⽣成⼀份新⽂件,虽然改完后,可以把旧的覆盖掉,但在改的过程中,还是有2份数据 的。

问:还有更好的⽅式 么?

有完没完? 没了。

占硬盘⽅式的⽂件修改代码示例

f_name = "兼职⽩领学⽣空姐模特护⼠联系⽅式.txt"
f_new_name = "%s.new" % f_name
old_str = "刘诺涵"
new_str = "[⿊姑娘]"
f = open(f_name,'r')
f_new = open(f_new_name,'w')
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()

上⾯的代码,会⽣成⼀个修改后的新⽂件 ,原⽂件不动,若想覆盖原⽂件

import os
f_name = "兼职⽩领学⽣空姐模特护⼠联系⽅式.txt"
f_new_name = "%s.new" % f_name
old_str = "刘诺涵"
new_str = "[⿊姑娘]"
f = open(f_name,'r')
f_new = open(f_new_name,'w')
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.rename(f_new_name,f_name) #把新⽂件名字改成原⽂件 的名字,就把之前的覆盖掉了,windows使⽤os.replace # 帮助⽂档说明replace会覆盖原⽂件

练习题-全局⽂本检索替换

写⼀个脚本,允许⽤户按以下⽅式执⾏时,即可以对指定⽂件内容进⾏全局替换,且替换完毕后打印替换了多少处内容
写完后的脚本调⽤⽅式:

python your_script.py old_str new_str filename

练习题- ⽤户登录认证程序

要求⽤户输⼊帐号密码进⾏登陆

⽤户账号信息保存在⽂件内

⽤户密码输⼊错误三次后锁定⽤户,下次再登录,检测到是这个被锁定的⽤户,则依然不允许其它登录,提示已被锁

综合实战-股票数据分析&处理

把以下股票数据存⼊stock_data.txt

开发程序对stock_data.txt进⾏以下操作:

  1. 程序启动后,给⽤户提供查询接⼝,允许⽤户᯿复查股票⾏情信息(⽤到循环)
  2. 允许⽤户通过模糊查询股票名,⽐如输⼊“啤酒”, 就把所有股票名称中包含“啤酒”的信息打印出来
  3. 允许按股票价格、涨跌幅、换⼿率这⼏列来筛选信息,⽐如输⼊“价格>50”则把价格⼤于50的股票都打印,输⼊“市盈率<50“,则把市盈率⼩于50的股票都打印,不⽤判断等于。

思路提示:加载⽂件内容到内存,转成dict or list结构,然后对dict or list 进⾏查询等操作。 这样以后就不⽤每查⼀次就要打开⼀次⽂件了,效率会⾼。

程序启动后执⾏效果参考:

股票查询接⼝>>:换⼿率>25
['序号', '代码', '名称', '最新价', '涨跌幅', '涨跌额', '成交量(⼿)', '成交额', '振幅',
'最⾼', '最低', '今开', '昨收', '量⽐', '换⼿率', '市盈率', '市净率']
['18', '603697', '有友⻝品', '22.73', '10.02%', '2.07', '34.93万', '7.68亿',
'8.23%', '22.73', '21.03', '21.17', '20.66', '1.4', '43.94%', '38.1', '4.66']
['23', '603956', '威派格', '22.52', '10.01%', '2.05', '18.33万', '4.01亿',
'10.60%', '22.52', '20.35', '20.35', '20.47', '2.16', '43.02%', '-', '9.82']
['36', '300748', '⾦⼒永磁', '59.7', '10.01%', '5.43', '11.02万', '6.38亿',
'6.98%', '59.7', '55.91', '56.88', '54.27', '0.9', '26.49%', '234.09',
'23.54']
['37', '300767', '震安科技', '41.13', '10.00%', '3.74', '6.22万', '2.49亿',
'10.32%', '41.13', '37.27', '37.48', '37.39', '3.86', '31.11%', '43.32',
'3.68']
['38', '603045', '福达合⾦', '32', '10.00%', '2.91', '17.06万', '5.31亿',
'9.87%', '32', '29.13', '29.13', '29.09', '1.39', '25.17%', '52.74', '4.02']
['39', '2952', '亚世光电', '58.98', '10.00%', '5.36', '4.18万', '2.41亿',
'7.42%', '58.98', '55', '55.91', '53.62', '3.04', '27.44%', '53.09', '5.51']
找到6
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值