py17day03【字符编码,文件操作,函数】
一、python字符编码详解str
1.了解文件读取,保存的过程:
使用文本编译器实则是在内存里打开了一个该程序的进程,然后打开一个文件,是将文件从硬盘里读取到内存,在内存里做相应的对文件的操作;保存文件则是将内存上的文件刷入硬盘的过程。
注:python解释器执行py文件的过程和文本编辑器类似,只不过python解释器从硬盘打开文件到内存后会执行文件内容。
2.字符编码:
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如'a'用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致'a'解析成'b'或者乱码。
所以必须经过一个过程:
字符--------(翻译过程)------->数字
这个过程实际就是一个字符如何对应一个特定数字的标准,这个标准称之为字符编码。
3.字符编码的发展:
第一阶段:ASCII
计算机是美国人发明的,人家用的是美式英语,字符比较少,所以一开始就设计了一个不大的二维表,128个字符,取名叫ASCII(American Standard Code for Information Interchange)。但是7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。即其最多只能用 8 位来表示(一个字节)。
第二阶段:GBK
当计算机传到了亚洲,尤其是东亚,国际标准被秒杀了,路边小孩随便说句话,256个码位就不够用了。于是,中国定制了GBK。用2个字节代表一个字符(汉字)。其他国家也纷纷定制了自己的编码,例如:
日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里。
第三阶段:unicode
当互联网席卷了全球,地域限制被打破了,不同国家和地区的计算机在交换数据的过程中,就会出现乱码的问题,跟语言上的地理隔离差不多。为了解决这个问题,一个伟大的创想产生了——Unicode(万国码)。Unicode编码系统为表达任意语言的任意字符而设计。
规定所有的字符和符号最少由 16 位来表示(2个字节),即:2 **16 = 65536,注:此处说的的是至少2个字节(16位),可能更多。
第四阶段:UTF-8
unicode的编码方式虽然包容万国,但是对于英文等字符就会浪费太多存储空间。于是出现了UTF-8,是对Unicode编码的压缩和优化,遵循能用最少的表示就用最少的表示,他不再使用最少使用2个字节,而是将所有的字符和符号进行分类:ascii码中的内容用1个字节保存、欧洲的字符用2个字节保存,东亚的字符用3个字节保存。
注:
unicode:所有字符都是2Bytes,优点是字符->数字的转换速度快,缺点是占用空间大。
utf-8:精准,对不同的字符用不同的长度表示,优点是节省空间,缺点是:字符->数字的转换速度慢,因为每次都需要计算出字符需要多长的Bytes才能够准确表示。
故内存中使用的编码是unicode,用空间换时间,为了快,因为程序都需要加载到内存才能运行,因而内存应该是尽可能的保证快。硬盘中或者网络传输用utf-8,网络I/O延迟或磁盘I/O延迟要远大与utf-8的转换延迟,而且I/O应该是尽可能地节省带宽,保证数据传输的稳定性。因为数据的传输,追求的是稳定,高效,数据量越小数据传输就越靠谱,于是都转成utf-8格式的,而不是unicode。
所有程序,最终都要加载到内存,程序保存到硬盘不同的国家用不同的编码格式,但是到内存中我们为了兼容万国(计算机可以运行任何国家的程序原因在于此),统一且固定使用unicode,这就是为何内存固定用unicode的原因,你可能会说兼容万国我可以用utf-8啊,可以,完全可以正常工作,之所以不用肯定是unicode比utf-8更高效啊(uicode固定用2个字节编码,utf-8则需要计算),但是unicode更浪费空间,没错,这就是用空间换时间的一种做法,而存放到硬盘,或者网络传输,都需要把unicode转成utf-8,因为数据的传输,追求的是稳定,高效,数据量越小数据传输就越靠谱,于是都转成utf-8格式的,而不是unicode。

内存中的unicode到硬盘的utf-8是一个encode过程,反则为decode过程。
4.文本编辑器存取文件原理分析:
当打开文本编辑器写入文字时,是以unicode的形式写入内存,点击保存后,以选择的编码格式:如gbk等,保存到硬盘中,这个过程相当于unicode--encode--gbk。
再次使用文本编辑器打开该文件时,指定以gbk的方式打开文件到内存中,即从硬盘的gbk--decode--到内存的unicode。
若未指定编码格式打开文件,用了非保存文件时使用的编码格式(如utf-8),则会造成打开文件乱码,即gbk格式的编码以utf-8方式解码。
故:无论是何种编辑器,要防止文件出现乱码,核心法则就是,文件以什么编码保存的,就以什么编码方式打开。
5.python解释器打开执行py文件原理分析:
阶段一:在内存中启动python解释器。
阶段二:python解释器此时就是一个文本编辑器,负责打开文件test.py,即从硬盘中读取test.py的内容到内存中。
此时,python解释器会读取test.py的第一行内容,#coding:utf-8,来决定以什么编码格式来读入内存,即从硬盘里的编码以何种转码方式decode到内存里的unicode,可以用sys.getdefaultencoding()查看,如果不在python文件指定头信息#-*-coding:utf-8-*-,那就使用默认的:
python2中默认使用ascii,python3中默认使用utf-8。
阶段三:读取已经加载到内存的代码(unicode编码的二进制),然后执行,执行过程中可能会开辟新的内存空间,比如x=''elaine''。
内存的编码使用unicode,不代表内存中全都是unicode编码的二进制,在程序执行之前,内存中确实都是unicode编码的二进制,比如从文件中读取了一行x=''elaine'',其中的x,等号,引号,地位都一样,都是普通字符而已,都是以unicode编码的二进制形式存放与内存中的;
但是程序在执行过程中,会申请内存(与程序代码所存在的内存是俩个空间),可以存放任意编码格式的数据,比如x=''elaine'',会被python解释器识别为字符串,会申请内存空间来存放"elaine",然后让x指向该内存地址,此时新申请的该内存地址保存也是unicode编码的elaine,如果代码换成x=''elaine''.encode('utf-8'),那么新申请的内存空间里存放的就是utf-8编码的字符串elaine了。
对于变量名x,有如图解释:

上述过程,如下图:

6.打开执行过程中python2和python3的一些区别:
- 不在文件行首指定 # -*- coding:utf-8 -*- 时,python2打开文件默认使用ascii进行解码,python3中默认使用utf-8进行解码。
- 在Python2中执行代码定义一个str的时候,与python3不同,首先会申请内存,然后将str encode为文件开头指定的编码格式放入内存,所以str在python2中就是编码后的结果bytes,str=bytes,所以str只能decode。

#coding:utf-8 s='吴' #在执行时,'吴'会被以conding:utf-8的形式保存到新的内存空间中 print repr(s) #'\xe5\x90\xb4' 三个Bytes,证明确实是utf-8 print type(s) #<type 'str'> s.decode('utf-8') # s.encode('utf-8') #报错,s为编码后的结果bytes,所以只能decode
- python3的字符串与python2中的u'字符串',都是unicode,只能encode,所以无论如何打印都不会乱码,因为可以理解为从内存打印到内存,即内存->内存,unicode->unicode。
- 当python解释器执行到产生字符串的代码时(例如s=u'吴'),会申请新的内存地址,然后将'吴'以unicode的格式存放到新的内存空间中,所以s只能encode,不能decode。
s=u'吴' print repr(s) #u'\u5434' print type(s) #<type 'unicode'> # s.decode('utf-8') #报错,s为unicode,所以只能encode s.encode('utf-8')
- 在windows打开终端编码为gbk,linux是UTF-8。
7.关于打印到终端的一些解释:
当程序执行时,比如
x='吴'
print(x) #这一步是将x指向的那块新的内存空间(非代码所在的内存空间)中的内存,打印到终端,而终端仍然是运行于内存中的,所以这打印可以理解为从内存打印到内存,即内存->内存,unicode->unicode。
二、python文件操作str
1.文件处理的流程:
- 打开文件,得到文件句柄并赋值给一个变量
- 通过句柄对文件进行操作
- 关闭文件
歌曲名:Mr Blue Sky
歌手:Lily Allen
专辑:Alright, Still (Deluxe)
Lily Allen - Mr Blue Sky
Sun is shining in the sky
There ain't a cloud in sight
It's stopped rainin', everybody's in a play
And don't you know
It's a beautiful new day, hey
Runnin' down the avenue
See how the sun shines brightly in the city
On the streets where once was pity
Mr. Blue Sky is living here today hey, hey, hey
Mr. Blue Sky, please tell us why
You had to hide away for so long
Where did we go wrong?
Mr. Blue Sky, please tell us why
You had to hide away for so long
Where did we go wrong?
Hey you with the pretty face
Welcome to the human race
A celebration, mister Blue Sky's up there waitin'
And today is the day we've waited for, ooh
Mr. Blue Sky, please tell us why
You had to hide away for so long
Where did we go wrong?
Hey there mr. Blue
We're so pleased to be with you
Look around see what you do
Everybody smiles at you
Hey there mr. Blue
We're so pleased to be with you
Look around see what you do
Everybody smiles at you
Mr. Blue Sky, mr. Blue Sky
Mr. Blue Sky
Mister Blue, you did it right
But soon comes mister Night creepin' over
Now his hand is on your shoulder
Never mind I'll remember you this
I'll remember you this
Mister Blue Sky, please tell us why
You had to hide away for so long
Where did we go wrong?
Mister Blue Sky, please tell us why
You had to hide away for so long
Where did we go wrong?
Hey there, mister Blue
We're so pleased to be with you
Look around see what you do
Everybody smiles at you
如下:
f = open('test.txt') #打开文件 first_line = f.readline() #读一行 print('first line:',first_line) print('我是分隔线'.center(50,'-')) data = f.read()# 读取剩下的所有内容,文件大时不要用 print(data) #打印读取内容 f.close() #关闭文件
2.open函数用法:
file_object = open(file_name, access_mode = ‘r’, buffering = -1)
open函数有很多的参数,常用的是file_name,mode和encoding
-
- file_name:打开的文件名,若非当前路径,需指出具体路径
- access_mode文件打开模式
- buffering的可取值有0,1,>1三个,0代表buffer关闭(只适用于二进制模式),1代表line buffer(只适用于文本模式),>1表示初始化的buffer大小;
- encoding表示的是返回的数据采用何种编码,一般采用utf8或者gbk;
access_mode:
-
- r ,只读模式【默认模式,文件必须存在,不存在则抛出异常】
- w,只写模式【不可读;不存在则创建;存在则清空内容】
- x, 只写模式【不可读;不存在则创建,存在则报错】
- a, 追加模式【可读; 不存在则创建;存在则只追加内容】
"+" 表示可以同时读写某个文件
-
- r+, 读写【可读,可写】
- w+,写读【可读,可写】
- x+ ,写读【可读,可写】
- a+, 写读【可读,可写】
"b"表示以字节的方式操作
-
- rb 或 r+b
- wb 或 w+b
- xb 或 w+b
- ab 或 a+b
注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码
常用组合:
-
- r或rt 默认模式,文本模式读
- rb 二进制文件
- w或wt 文本模式写,打开前文件存储被清空
- wb 二进制写,文件存储同样被清空
- a 追加模式,只能写在文件末尾
- a+ 可读写模式,写只能写在文件末尾
- w+ 可读写,与a+的区别是要清空文件内容
- r+ 可读写,与a+的区别是可以写到文件任何位置
读取图片文件:
with open('diablo.png','rb') as r_file: data = r_file.read() with open('diablo3.png', 'wb') as w_file: w_file.write(data)
encoding:
不指定打开编码,默认使用操作系统的编码,windows为gbk,linux为utf-8,与解释器编码无关。
3.补充:
file_object的一些方法:
-
- 当文件以读模式打开时:
file_object.read() #一次读取全部的文件内容。 file_object.readline() #每次读取文件的一行。 file_object.readlines() #读取文件的所有行,返回一个列表,每一个元素为文件的每一行的str。 file_object.readable() #判断文件是否是r模式打开 file_object.closed #判断文件是否是关闭状态
file_object.read(4) #数字的意思是读取的是4个字符,而seek则是字节
-
- 光标在文本文件中的操作:
file_object.seek(offset,whence=0) #移动光标,从whence(0起始,1当前,2末尾)移动offset个字节,正数往末尾方向移动,负往开始方向移动 file_object.tell() #获得文件当前光标位置 file_object.truncate(size=file_object.tell()) #截取文件到最大size个字节,默认为当前文件位置
-
- 文件以写模式打开时:
file.write(str) #向文件中写入字符串(文本或二进制) file.writelines(seq) #写入多行,向文件中写入一个列表(注意:要自己加入每行的换行符) file.flush() #刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入.
4.对文件进行修改的方法:
基本思路:以读的方式打开修改更改的文件,然后用写的方式打开一个新文件,然后将需要修改好的文件写入新文件,删除原文件,再把新文件命名为原文件。
import os read_file = open('test.txt','r',encoding='utf-8') write_file = open('new_test.txt','w',encoding='utf-8') for line in read_file: if line.startswith('Mr. Blue Sky'): line = line.replace('Mr. Blue Sky','Mr. Night') write_file.write(line) read_file.close() write_file.close() os.remove('test.txt') os.rename('new_test.txt','test.txt')
or
file = open('test.txt','r+',encoding='utf-8') data = file.readlines() new_data = [] for line in data: if line.startswith('Mr. Blue Sky'): line = line.replace('Mr. Blue Sky','Mr. Night') new_data.append(line) print(new_data) file.seek(0) file.writelines(new_data) file.close()
5.上下文管理with:
https://foofish.net/with-and-context-manager.html
当你做文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。
正常情况下,代码如下:
file = open("/tmp/foo.txt")
data = file.read()
file.close()
这里有两个问题。一是可能忘记关闭文件句柄;二是文件读取数据发生异常,没有进行任何处理。
然而with可以很好的处理上下文环境产生的异常。下面是with版本的代码:
with open("/tmp /foo.txt") as file:
data = file.read()
with的基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。
模拟tail -f a.log:
import time with open('a.log','r',encoding='utf-8') as f: f.seek(0,2) #光标移动到文件末尾 while True: new_line = f.readline().strip() #读取并去掉换行符 if new_line: print('new_line>>',new_line) time.sleep(1)
三、python函数str
1.函数的分类:
在python中函数分两类:内置函数,自定义函数
内置函数:
如:
>>> len('abc') 3 >>> max('1','2') '2' >>> min('1','2') '1'
自定义函数:
根据需求,自己定义功能
2.函数的定义:
为什么要定义函数?:
先定义后使用,如果没有定义而直接使用,就相当于引用了一个不存在的变量名
函数的使用包含两个阶段:定义阶段和使用阶段
fun() def fun(): print('from fun') print(fun)
定义函数的语法:
def functionname( parameters ): "函数_文档字符串" function_suite return [expression]
如:
def fun(arg ): "print arg" print(arg) return
定义函数的三种形式:
- 无参数函数:如果函数的功能仅仅只是执行一些操作而已,就定义成无参函数,无参函数通常都有返回值
def fun(): print('this is a function!')
- 有参函数:函数的功能的执行依赖于外部传入的参数,有参函数通常都有返回值
def func(arg1,arg2): print(arg1,arg2)
- 空函数:
def fun(): pass
三元表达式:
x = 1 y = 2 if x > y : print(x) else: print(y) print(x) if x > y else print(y)
3.函数的调用:
按照有参和无参可以将函数调用分两种:
- 定义时无参,调用时也无需传入参数
- 定义时有参,调用时也必须有参数
按照函数的调用形式和出现的位置,分三种:
def foo(): print('from foo') foo() #调用函数的语句形式
def my_max(x,y): res=x if x >y else y return res # res=my_max(1,2)*10000000 #调用函数的表达式形式 # print(res)
res=my_max(my_max(10,20),30) #把函数调用当中另外一个函数的参数 print(res)
4.函数的参数:
从大的角度去看,函数的参数分两种:形参(变量名),实参(值)
详细的区分函数的参数分为五种:
位置参数,关键字参数,默认参数,可变长参数(*args,**kwargs),命名关键字参数
位置参数:
def foo(x,y,z):#位置形参:必须被传值的参数 print(x,y,z) # foo(1,2,3) foo(1,2,3) #位置实参数:与形参一一对应
关键字参数:
#关键字参数:相当于key=value形式 def foo(x,y,z): print(x,y,z) # foo(z=3,x=1,y=2)
关键字参数需要注意的问题:
-
- 关键字实参必须在位置实参后面
- 不能重复对一个形参数传值
- foo(1,z=3,y=2) #正确
- foo(x=1,2,z=3) #错误
默认参数:
def register(name,age,sex='male'): #形参:默认参数 print(name,age,sex)
默认参数需要注意的问题:
-
- 默认参数必须跟在非默认参数后
def register(sex='male',name,age): #在定义阶段就会报错 print(name,age,sex)
-
- 默认参数在定义阶段就已经赋值了,而且只在定义阶段赋值一次
a=100000000 def foo(x,y=a): print(x,y) a=0 foo(1) 结果为: 1 100000000
-
- 默认参数的值通常定义成不可变类型
可变长参数:
*args:
def foo(x,y,*args): # *会把溢出的按位置定义的实参都接收,以元组的形式赋值给args print(x,y) print(args) foo(1,2,3,4,5) 运行结果: 1 2 (3, 4, 5)
例如,可以用这个特性创造一个求和函数:
def add(*args): res = 0 for i in args: res+=i return res print(add(1,2,3,4,5,6))
**kwargs:
def foo(x, y, **kwargs): # **会把溢出的按关键字定义的实参都接收,以字典的形式赋值给kwargs print(x, y) print(kwargs) foo(1,2,id=1,name='elaine',age=18) 运行结果: 1 2 {'name': 'elaine', 'id': 1, 'age': 18}
命名关键字参数:
def foo(name,age,*,sex='male',height): print(name,age) print(sex) print(height) #*后定义的参数为命名关键字参数,这类参数,必须被传值,而且必须以关键字实参的形式去传值 foo('dylan',29,height='182')
关于参数的补充:
多种参数一起使用时要按如下顺序:
def foo(name,age=10,*args,sex='male',height,**kwargs): print(name) print(age) print(args) print(sex) print(height) print(kwargs) foo('dylan',1,2,3,4,5,sex='female',height='182',a=1,b=2,c=3) 运行结果: dylan 1 #这个1占了默认参数的位置 (2, 3, 4, 5) female 182 {'c': 3, 'a': 1, 'b': 2}
关于args和*args:
如下运行结果可以推测出
args = (1,2,3,4)
*args = *(1,2,3,4)
def foo(*args): print(args) foo(1,2,3,4) 运行结果: (1,2,3,4)
那么当传参数的时候,也可以foo(*(1,2,3,4))或者foo(*[1,2,3,4])
def foo(*args): print(args) foo(*(1,2,3,4)) foo(*[1,2,3,4]) 运行结果: (1,2,3,4) (1,2,3,4)
所以:foo(1,2,3)相当于foo(*(1,2,3))
def foo(x,y,z): print(x,y,z) foo(*[1,2,3]) #相当于foo(1,2,3) # foo(*[1,2]) #相当于foo(1,2),故会报错
运行结果:
1 2 3
同理**kwargs:
def foo(**kwargs): print(kwargs) foo(**{'y': 2, 'x': 1,'a':1}) foo(a=1,y=2,x=1) 运行结果: {'y': 2, 'a': 1, 'x': 1} {'y': 2, 'a': 1, 'x': 1}
def foo(x,y,z): print(x,y,z) foo(**{'z':3,'x':1,'y':2}) #相当于foo(x=1,z=3,y=2) #foo(**{'z':3,'x':1}) #相当于foo(x=1,z=3),故会报错
运行结果:
1 2 3
装饰器传参初探:
def foo(x,y,z): print('from foo',x,y,z) def wrapper(*args,**kwargs): #这里的*args和**kwargs要作为参数传给foo print(args) #args=(1,2,3) print(kwargs) #kwargs={'a':1,'b':2} foo(*args,**kwargs) #由上面推导可知foo(*(1,2,3),**{'a':1,'b':2})就相当于foo(1,2,3,b=2,a=1) # wrapper(1,2,3,a=1,b=2) #所以会报错 wrapper(1,z=2,y=3) #故wrapper函数传参要参照foo函数
运行结果:
(1,)
{'z': 2, 'y': 3}
from foo 1 3 2
故正确的执行顺序如下:
def foo(x,y,z): print('from foo',x,y,z) def wrapper(*args,**kwargs): print(args) #args=(1,) print(kwargs) #kwargs={'y':3,'z':2} foo(*args,**kwargs) #foo(*(1,),**{'y':3,'z':2})相当于foo(1,y=3,z=2) # wrapper(1,2,3,a=1,b=2) wrapper(1,z=2,y=3)
5.函数的返回值:
return语句[表达式]退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。
def foo(): print('from foo') return None res=foo() print(res)
运行结果:
from foo None
以下三种情况,均返回None:
- 没有return语句
- return后无任何语句
- return None
def fun1(): pass def fun2(): return def fun3(): return None res1 = fun1() res2 = fun2() res3 = fun3() print(res1) print(res2) print(res3) 运行结果: None None None
补充:
-
- 函数调用时返回的结果为return的值
- return 值1,值2,值3,... 返回结果:(值1,值2,值3,...)
def fun(): return 1, [2, 3], (4, 5), {} res = fun() print(res) a,b,c,d = fun() print(d) 运行结果: (1, [2, 3], (4, 5), {}) {}

浙公网安备 33010602011771号