Python中,有控制函数流程和上下文档案管理办法的办法么?

第3天,Python字符编码/文件操作/函数 - 简书
第3天,Python字符编码/文件操作/函数
文章目录:
一、字符编码
1、字符编码发展史
阶段一:现代计算机起源于美国,最早诞生的也是基于英文考虑的ASCII码;
ASCII:一个bytes代表一个字符(英文字符/键盘上的所有其他字符),1bytes = 8bit,8个bit位可以产生2**8 = 256种变化,即可以表示256个字符。
阶段二:为了满足中文,中国人定制了GBK编码
GBK:2bytes表示一个字符;
其他国家也纷纷定制自己的编码,如日本把日文编到shift_JIS里,韩国把韩文编到Euc-kr里
阶段三:各国都有各自的编码标准,就会不可避免地出现冲突,结果就是在多语言混合的文本中,显示出来就会乱码。
于是产生了unicode,统一用2bytes代表一个字符,2bytes = 16bit ,16个bit位可以产生2**16 = 65536种变化,可以表示65536个字符,然后将全世界各国的文字符号全都包含在内,因而兼容万国文字语言。
但对于英文文本来说,这种编码方式就会多出一倍的存储空间,为了解决这个问题,于是又产生了UTF-8,它是对unicode的压缩优化,英文字符只占用1bytes,中文字符占用3bytes。
注意:需要强调一点;
unicode:简单粗暴,所有字符都占2bytes,优点是字符 ---& 二进制表示 转换速度快,缺点是占用空间大
utf-8:精准,对不同的字符用不同的长度表示,优点是节省空间,缺点是:字符 ---& 二进制表示 转换速度慢,因为每次都需要计算出字符需要多长的bytes才能够准确表示
内存中使用的编码是unicode,用空间换时间(程序都需要加载到内存中才能运行,因为内存应该是尽可能保证快)
硬盘中或者网络传输用utf-8,网络 I/O 或磁盘 I/O 远大于utf-8的转换延迟,而且 I/O 应该是飞可能地节省带宽,保证数据传输的稳定性。
注:所有程序,最终都要加载到内存,程序保存到硬盘,不同的国家用不同的编码格式,但是到内存中我们为了兼容万国(计算机可以运行任何国家的程序原因在于此),统一且固定使用unicode,这就是为何内存固定用unicode的原因,你可能会说兼容万国我可以用utf-8啊,可以,完全可以正常工作,之所以不用肯定是unicode比utf-8更高效啊(uicode固定用2个字节编码,utf-8则需要计算),但是unicode更浪费空间,没错,这就是用空间换时间的一种做法,而存放到硬盘,或者网络传输,都需要把unicode转成utf-8,因为数据的传输,追求的是稳定,高效,数据量越小数据传输就越靠谱,于是都转成utf-8格式的,而不是unicode。
2、字符编码的使用
无论是何种编辑器,要防止文件出现乱码(请一定注意,存放一段代码的文件也仅仅只是一个普通文件而已,此处指的是文件没有执行前,我们打开文件时出现的乱码)
核心法则就是,文件以什么编码保存的,就以什么编码方式打开
Paste_Image.png
2.1 程序的执行
python test.py (执行程序,一定是先将文件内容加载到内存中)
步骤一:启动python解释器步骤二:python解释器此时就是一个文本编辑器,负责打开文件test.py,即从硬盘中读取test.py的内容到内存中。
此时,python解释器会读取test.py的第一行内容,# -*-coding:utf-8 -*-,来决定以什么编码格式来读入内存,这一行就是来设定python解释器这个软件的编码使用的编码格式,这个编码可以用sys.getdefaultencoding()查看,如果不在python文件指定头信息#-*-coding:utf-8-*-,那就使用默认的python2中默认使用ascii,python3中默认使用utf-8。
步骤三:读取已经加载到内存的代码(unicode编码的二进制),然后执行,执行过程中可能会开辟新的内存空间,比如 name = Alex
内存的编码使用unicode,不代表内存中全都是unicode编码的二进制;
在程序执行之前,内存中确实都是unicode编码的二进制,比如从文件中读取了一行name = Alex,其中“name”、“=”、引号,地位都一样,都是普通字符而已,都是 unicode编码的二进制形式存放在内存中的;
但是程序在执行过程中,会申请内存(与程序代码所存在的内存是两个空间),可以存放任意编码格式的数据,比如name = Alex会被python解释器识别为字符串,会申请内存空间来存放“Hello”,然后让“name”指向该内存地址,此时新申请的该内存地址保存的也是unicode编码的“Alex”,如果代码换成name = "Alex".encode('utf-8'),那么新申请的内存空间里存放的就是utf-8编码的字符串“Alex”了。
针对python3 如下图:
Paste_Image.png
浏览网页的时候,服务器会把动态生成的unicode内容转换为utf-8再传输到浏览器:
Paste_Image.png
如果服务器端encode的编码格式是utf-8,客户端内存中收到的也是utf-8编码的二进制。
2.2、Python2与Python3的区别
在Python2中有两种字符串类型:str 和 unicode
str类型:当Python解释器执行到产生字符串的代码时(例如:s = "林"),会申请新的内存地址,然后将“林” encode成文件开头指定的编码格式,这已经是encode之后的结果,所以 s 只能decode,所以很重要的一点是:在Python2中,str 就是编码后的结果bytes,str = bytes,所以在Python2中,unicode字符编码结果就 str/bytes 。
#coding:utf-8
s='林' #在执行时,'林'会被以conding:utf-8的形式保存到新的内存空间中
print repr(s) #'\xe6\x9e\x97' 三个Bytes,证明确实是utf-8
print type(s) #&type 'str'&
s.decode('utf-8')
# s.encode('utf-8') #报错,s为编码后的结果bytes,所以只能decode
unicode类型:当Python解释器执行到产生字符串的代码时(例如:s = u"林"),会申请新的内存地址,然后将”林“以unicode的格式存放到新的内存空间,所以 s 只能encode,不能decode 。
print repr(s) #u'\u6797'
print type(s) #&type 'unicode'&
# s.decode('utf-8') #报错,s为unicode,所以只能encode
s.encode('utf-8')
打印到终端:对于print需要特别说明的是:当程序执行时,比如name = '蔡' ,print(name) #这一步是将name指向的那块新的内存空间(非代码所在的内存空间)中的内容,打印到终端,而终端仍然是运行于内存中的,所以这打印可以理解为从一块内存打印到另一块内存,unicode --& unicode 。
对于unicode格式的数据来说,无论怎么打印,都不会乱码。
Python3中的字符串与Python2中的 u"字符串” 一样,都是unicode,所以无论如何打印都不会乱码。
在pycharm中,
Paste_Image.png
在windows终端
Paste_Image.png
但是在python2中存在另外一种非unicode的字符串,此时,print x,会按照终端的编码执行x.decode('终端编码'),变成unicode后,再打印,此时终端编码若与文件开头指定的编码不一致,乱码就产生了
在pycharm中(终端编码为utf-8,文件编码为utf-8,不会乱码)
Paste_Image.png
在windows终端(终端编码为gbk,文件编码为utf-8,乱码产生)
Paste_Image.png
验证:分别验证在pycharm(默认是utf-8)中和cmd(默认是gbk)下述的打印结果:
#coding:utf-8
s=u'林' #当程序执行时,'林'会被以unicode形式保存新的内存空间中
#s指向的是unicode,因而可以编码成任意格式,都不会报encode错误
s1=s.encode('utf-8')
s2=s.encode('gbk')
print s1 #打印正常否?#在pycharm打印正,cmd中打印会乱码
print s2 #打印正常否,正好与上面相反
print repr(s) #u'\u6797'
print repr(s1) #'\xe6\x9e\x97' 编码一个汉字utf-8用3Bytes
print repr(s2) #'\xc1\xd6' 编码一个汉字gbk用2Bytes
print type(s) #&type 'unicode'&
print type(s1) #&type 'str'&
print type(s2) #&type 'str'&
2.3、在Python3中也有两种字符串类型str 和 bytes
str 是 unicode
#coding:utf-8
s='林' #当程序执行时,无需加u,'林'也会被以unicode形式保存新的内存空间中,
#s可以直接encode成任意编码格式
s1 = s.encode('utf-8')
s2 = s.encode('gbk')
#b'\xe6\x9e\x97'(utf-8三个字节)
#b'\xc1\xd6' (gbk两个字节)
print(type(s)) #&class 'str'&
bytes 就是 bytes
#coding:utf-8
s='林' #当程序执行时,无需加u,'林'也会被以unicode形式保存新的内存空间中,
#s可以直接encode成任意编码格式
s1=s.encode('utf-8')
s2=s.encode('gbk')
print(s) #林
print(s1) #b'\xe6\x9e\x97' 在python3中,是什么就打印什么
print(s2) #b'\xc1\xd6' 同上
print(type(s)) #&class 'str'&
print(type(s1)) #&class 'bytes'&
print(type(s2)) #&class 'bytes'&
二、文件操作
1、文件处理流程
1.打开文件,得到文件句柄并赋值给一个变量
2.通过文件句柄对文件进行操作
3.关闭文件
2、基本操作
2.1 打开文件并读取
打开文件时,需要指定文件路径和以何等方式打开文件,打开后,即可获取该文件句柄,日后通过此文件句柄对该文件操作。
f = open('data','r',encoding='utf-8')
#这里的f指向的是一个内存空间地址
file = f.read()
#通过read方法将内存空间中的内容读取出来并赋值给file
#输出一个内存空间地址,及文件名、打开的模式、编码格式。
print(file) #输出文件内容
&_io.TextIOWrapper name='data' mode='r' encoding='utf-8'&
打开文件的模式有:
r ,只读模式【默认】
w,只写模式【不可读;不存在则创建;存在则清空内容;】
x, 只写模式【不可读;不存在则创建,存在则报错】
a, 追加模式【可读;
不存在则创建;存在则只追加内容;】
"+" 表示可以同时读写某个文件:
r+, 读写【可读,可写】
w+,写读【可读,可写】
x+ ,写读【可读,可写】
a+, 写读【可读,可写】
"b"表示以二进制的方式操作
以二进制的方式读写一个图片:
with open('sb.jpg','rb') as f,\
open('sb_alex.jpg','wb') as f1:
data=f.read()
f1.write(data)
注:以 b 方式打开时,读取到的内容是bytes类型,写入时也需要提供bytes类型。
2.2 with语句
为了避免打开文件后忘记关闭,可以通过管理上下文,即:
with open('log','r') as f:
print(f.read())
以此方式打开,当with代码块执行完毕时,内部会自动关闭并释放文件资源。
在Python2.7后,with又支持同时对多个文件的上下文进行管理,即:with open('log1') as obj1, open('log2') as obj2:
2.3 常用操作
read读取文件
with open('a.txt','r',encoding='utf-8') as f:
print(f.read(4))
#数字指的是读的是字符个数
write往文件中写入
with open('access.log','a',encoding='utf-8') as f:
f.write('\n我是一个好人')
seek/tellseek是将光标跳转到指定位置tell是显示当前光标所在位置
with open('a.txt','r',encoding='utf-8') as f:
#seek内指定的数字代表字节,默认情况,是以文件起始位置作为开始,往后移动3个bytes
print(f.tell()) # 打印当前光标所在的位置
以当前光标所在的位置为开始,移动光标
with open('b.txt','rb') as f:
f.seek(2,1)
# 1 代表以当前光标所在的位置为开始,往后移动2个 bytes
从文件末尾开始,移动光标
with open('b.txt','rb') as f:
f.seek(-3,2) # 2表示从文件末尾为开始,往前移动3个bytes
truncate截断,从指定位置截断,truncate中的数字是字节数
with open('a.txt','r+',encoding='utf-8') as f:
f.truncate(3)
#truncate中的数字是字节数,3个字节可以保留一个汉字,如果只截断1个字节,文件将出现乱码
模拟Linux命令tail -f access.log打开文件,并将光标移动到文件末尾,循环读取文件,这样每当文件中新增一条日志并保存后,就会被程序读取到并打印出来。
# tail -f access.log
import time
with open('access.log','r',encoding='utf-8') as f:
f.seek(0,2)
# 将光标移动末尾第一位
while True:
line=f.readline().strip()
print('新增一行日志',line)
time.sleep(0.5)
close关闭文件
f = open('test','w')
f.write('1\n')
closed判断文件是否关闭,关闭返回
f = open('test','w')
f.write('1\n')
print(f.closed)
readline一行一行的读取文件
with open('access.log','r') as f:
print(f.readline())
print(f.readline())
print(f.readline())
# 有空行是因print默认会打换行符,在print中加上end=''即可取消空行
readlines一次读取全部文件
with open('access.log','r') as f:
print(f.readlines())
['11111\n', '22222\n', '33333\n', '44444\n', '55555\n', '66666\n', '77777\n', '88888']
# 可以看出readlines将文件内容变成一个列表了
with open('access.log','r') as f:
for i in f.readlines():
print(i,end='')
注:以上两种方法都会将文件一次加载到内存中,如果文件特别大,比如打开一个20G的文件,而机器内存只有16G,通过这两种方法无疑会将机器内存撑爆。
想要一行一行读取文件内容,而每次读取一行内容将覆盖之前一行,这样就不会将一个文件中的所有内容全部加载到内存了。想要实现这点,可以采用下面这种方法:直接循环文件句柄
with open('access.log','r') as f:
for i in f:
print(i,end='')
模拟文件改名
# 模拟文件改名
f1 = open('c','r')
f2 = open('.c.swap','w')
for i in f1:
# print(i)
if i.startswith('5'):
# 修改以5开头的行
i = '11111\n'
f2.write(i)
f1.close()
f2.close()
os.remove('c')
# 先删除,再改名
os.rename('.c.swap','c')
readable判断文件是否以只读模式打开
with open('access.log','a',encoding='utf-8') as f:
print(f.readable())
# 因为是以a模式打开,所以返回False
writeable是否以可写的模式打开文件,与readable类似
writelines将列表写入文件,不过请注意,列表中的数据类型只能是字符串
with open('access.log','w',encoding='utf-8') as f:
f.writelines(['111\n','222\n','333\n'])
flush操作系统在将数据写入硬盘时会等内存中积累到一定量时,才会数据一次性写入硬盘,这样可以节省磁盘 I/O ,flush方法是将内存中的数据强制刷到硬盘
with open('access.log','w',encoding='utf-8') as f:
f.write('111\n222')
Python中函数的定义:函数是逻辑结构化和过程化的一种编程方法。函数的特性:
减少重复代码
使程序变的可扩展
使程序变得易维护
以我自己的理解,函数(function)--& 即功能,由一段代码实现的一个特定的功能,然后在程序中可以反复调用此功能,减少代码冗余。这是我简单的理解,不详之处还请大家多多指正。
在python中,函数分为两类:
内置函数,
自定义函数,请继续向下看
1. 函数的定义
# def 函数名(参数1,参数2,...):
"""文档注释"""
函数的定义主要有如下要点:
def:表示函数的关键字函数名:函数的名称,后续代码可根据函数名调用此函数函数体:函数中进行一系统的逻辑计算,如:发送邮件、计算出[11,22,38,55,2]中的最大数等。。。参数:为函数体提供数据返回值:当函数执行完毕后,可以给调用者返回数据。
1.1 定义函数的二种形式
1.1.1 无参函数
如果函数的功能仅仅只是执行一些操作而已,就定义成无参函数,无参函数通常都没有返回值
def print_star():
print('#'*6)
print_star()
# 调用函数
1.1.2 有参函数
函数的功能的执行依赖于外部传入的参数,有参函数通常都有返回值。
def my_max(x,y):
res=x if x &y else y
# 三元运算
print(res)
return res
my_max(5,8)
# 5和8是传给my_max函数的参数
2 函数调用
按照有参和无参,可以将函数调用分为两种,如下:
def foo():
print('from foo')
def bar(name):
print('bar===&',name)
# 定义时无参,调用时也无需传入参数
bar('egon') # 定义时有参,调用时也必须传入参数
按照函数的调用形式和出现的位置,分三种:
foo() # 调用函数的语句形式
调用函数的表达式形式def my_max(x,y):
res=x if x &y else y
return res
res=my_max(1,2)* #调用函数的表达式形式
print(res)
把函数调用当中另外一个函数的参数def my_max(x,y):
res=x if x &y else y
return res
res=my_max(my_max(10,20),30) #把函数调用当中另外一个函数的参数
print(res)
3. 函数的返回值
以下三种情况返回值都为None
没有return
return 什么都不写
return None
def foo():
print('from foo')
return None
print(res)
# res接收返回值None
多个返回值,将返回成一个元组
def foo():
print('from foo')
return 1,[2,3],(4,5),{}
print(res) #打印结果:(1,[2,3],(4,5),{})
a,b,c,d=foo()
# 可以直接用函数的返回值赋值给变量
输出结果:
(1, [2, 3], (4, 5), {})
4. 函数的参数
4.1 从大的角度去看,函数的参数分两种:
形参(可以理解为变量名)
实参(赋予变量的值)
形参与实参
def foo(x,y): #x=1,y=2
4.2 详细区分函数的参数
详细的区分函数的参数分为五种:
关键字参数
可变长参数( *args,**kwargs)
命名关键字参数
4.2.1 位置参数
实参必须与形参一一对应,实参的个数不能多也不能少。
def foo(x,y,z):#位置形参:必须被传值的参数
print(x,y,z)
foo(1,2,3) #位置实参数:与形参一一对应
4.2.2 关键字参数
Key = value
def foo(x,y,z):
print(x,y,z)
foo(z=3,x=1,y=2)
注:位置参数与关键字参数可以一起使用,但是需要注意以下问题:1.关键字实参必须在位置实参后面2.不能重复对一个形参传值
# foo(1,z=3,y=2) #正确
# foo(x=1,2,z=3) #错误
# foo(1,x=1,y=2,z=3) #错误
4.2.3 默认参数
在定义函数时,如果使用了默认参数;那在调用此函数时,如果对默认参数指定了新值,则取新值;如果没有对默认参数指定值,则使用默认参数定义的值。
def register(name,age,sex='male'): #形参:默认参数sex='male'
print(name,age,sex)
register('alex',age=40)
# 调用时没有给sex赋值
输出结果:
alex 40 male
给默认参数赋新值:
def register(name,age,sex='male'): #形参:默认参数
print(name,age,sex)
register('钢蛋',20,'female')
register('铁蛋',sex='female',age=19)
钢蛋 20 female
铁蛋 19 female
默认参数需要注意的问题:
默认参数必须跟在非默认参数后面 # def register(sex='male',name,age): #在定义阶段就会报错
print(name,age,sex)
默认参数在定义阶段就已经赋值了,而且只在定义阶段赋值一次 a=
def foo(x,y=a):
print(x,y)
# 输出结果:
注:由结果可以看出,后来给a赋值0后并没有生效
默认参数的值通常定义成不可变类型的数据
4.2.4 可变长参数
第一种:*args*会把溢出的按位置定义的实参都接收,以元组的形式赋值给args
def foo(x,y,*args): #*会把溢出的按位置定义的实参都接收,以元组的形式赋值给args
print(x,y)
print(args)
foo(1,2,3,4,5)
利用可变长参数计算任意长度的和
def add(*args):
for i in args:
return res
print(add(1,2,3,4))
print(add(1,2))
第二种:**kwargs **会把溢出的按关键字定义的实参都接收,以字典的形式赋值给kwargs
def foo(x, y, **kwargs):
# **会把溢出的按关键字定义的实参都接收,以字典的形式赋值给kwargs
print(x, y)
print(kwargs)
foo(1,2,a=1,name='egon',age=18)
{'age': 18, 'a': 1, 'name': 'egon'}
4.2.5 命名关键字参数(了解)
def foo(name,age,*,sex='male',height):
print(name,age)
print(sex)
print(height)
foo('egon',17,height='185')
*后定义的参数为命名关键字参数,这类参数,必须被传值,而且必须以关键字实参的形式去传值
4.2.6 各种形式的参数混合使用
def foo(name,age=10,*args,sex='male',height,**kwargs):
print(name)
print(age)
print(args)
print(sex)
print(height)
print(kwargs)
foo('alex',1,2,3,4,5,sex='female',height='150',a=1,b=2,c=3)
输出结果:
# 虽然age是默认参数,但是这里以位置参数的方式,将1传给age为值
(2, 3, 4, 5)
{'b': 2, 'c': 3, 'a': 1}
4.2.7 其他姿势
可变长参数的其他玩法:*args :
# def foo(*args):
print(args)
# foo(1,2,3,4) # 1,2,3,4 &=====&*(1,2,3,4)
#*['A','B','C','D'], 等同于'A','B','C','D'
# foo(*['A','B','C','D']) ===& foo('A','B','C','D')
# foo(['A','B','C','D']) #
# def foo(x,y,z):
print(x,y,z)
# # foo(*[1,2,3]) #相当于foo(1,2,3)
# foo(*[1,2]) #相当于foo(1,2)
**kwargs :
def foo(**kwargs):
print(kwargs)
foo(**{'y': 2, 'x': 1,'a':1}) #相当于foo(a=1,y=2,x=1)
{'y': 2, 'x': 1, 'a': 1}
*args 与 **kwargs混合使用:
def wrapper(*args,**kwargs):
print(args)
print(kwargs)
wrapper(1,2,3,a=1,b=2)
{'b': 2, 'a': 1}
def foo(x,y,z):
print('from foo',x,y,z)
def wrapper(*args,**kwargs):
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)
{'y': 3, 'z': 2}
from foo 1 3 2
注:函数定义阶段到底干了什么事情:只检测函数体的语法,并不会执行
努力过,不后悔!python新手,请问一下,为什么一调用函数,下面的for循环就不执行了
你好,想跟你请教个问题:还是你刚刚帮我回的问题,我解决了,能不能帮助我优化一下,如下:
#!c:\python27
#_*_ coding: utf-8 _*_
import os, sys
while True:
& & if len(sys.argv) &= 3:
& & & & print "usage:file_replace.py old_text &new_text &filename"
& & & & sys.exit()
& & & & break
old_text = sys.argv[1]
new_text = sys.argv[2]
filename = sys.argv[3]
f = file(filename, 'r+')
def create_bak_file ():
& & old_file = file(filename, 'r')
& & bak_file = file('%s.bak' % filename, 'w')
& & bak_file.write (old_file.read())
& & bak_file.close()
& & old_file.close()
& & return None
if not os.path.isfile('.%s.bak' % filename):
& & create_bak_file ()
& & override_y_n = raw_input ('the bak file is exited!,if you want to override the bak file?(y/n):')
& & if override_y_n == 'y':
& & & & create_bak_file ()
for line in f:
& & f.write(line.replace(old_text, new_text))
现在还有个问题,就是最后的for循环,成死循环了,出不来了,写的文件非常大,我打开看了看,都是nul,请问怎么解决,谢谢
1.为什么你的for不能迭代f, 原因就是,每个打开的文件对象, 都有个文件偏移位置指针, 当你执行create_bak_file里面的old_file.read()时, 老文件的文件指针已经指向文件尾部, 所以你再for也没用, 你可以重新调整文件指针的位置:
f.seek(0, 0)
for line in f:
& & f.write(line.replace(old_text, new_text))
---------------- 小建议 -------------
2.不知道你的while有什么用..直接判断 如果位置参数小于3个, 直接退出就好了, 为什么要加个while?
3.create_bak_file 如果没理解错的话, 应该是备份的意思,可以用下面的方式代替
import shutil
shutil.copy(SRC, DEST)
--- 共有 1 条评论 ---
代码混乱,不知道要做什么
感谢,我刚开始学,所以弱得很,这个代码是看了视频教程,然后根据需求自己想着写的!刚开始写while是想着如果输入的argv不够3个,就一直持续到输入够为止,后来没有办法实现,所以就先放那儿了,已经发现while 没用了,一点点的改错中!感谢给我帮助的朋友!
1.在使用之前open即可,不用开始就open file
2.加try catch
3.使用shutil拷贝备份
# _*_ coding: utf-8 _*_
import os, sys, shutil, traceback
if len(sys.argv) != 4:
& & print "please input three parameters"
& & sys.exit()
print "usage:file_replace.py old_text &new_text &filename"
old_text = sys.argv[1]
new_text = sys.argv[2]
filename = sys.argv[3]
if not os.path.isfile(filename):
& & "%s is not existed" % filename
& & sys.exit()
def create_bak_file():
& & old_file = file(filename, 'r')
& & bak_file = file('%s.bak' % filename, 'w')
& & bak_file.write(old_file.read())
& & bak_file.close()
& & old_file.close()
& & shutil.copy(filename, filename + '.bak')
& & return None
if not os.path.isfile('%s.bak' % filename):
& & create_bak_file()
& & override_y_n = raw_input('the bak file is existed!, if you want to override the bak file?(y/n):')
& & if override_y_n == 'y':
& & & & create_bak_file()
& & f = open(filename, 'r+')
& & for line in f.readlines():
& & & & f.write(line.replace(old_text, new_text))
& & traceback.print_exc()
& & f.close()
--- 共有 5 条评论 ---
: 文件编码改成UTF-8
回复 : SyntaxError: Non-ASCII character '\xa3' in file C:\Users\asus\workspace\Math1\src\Math1\__init__.py on line 1, but
: 提示什么错误
if override_y_n==‘y’
是为什么???
这篇文章主要介绍了讲解Python中for循环下的索引变量的作用域,是Python学习当中的基础知识,本文给出了Python3的示例帮助读者理解,需要的朋友可以参考下
我们从一个测试开始。下面这个函数的功能是什么?
def foo(lst):
&&for i in lst:
&&&&a += i
&&for t in lst:
&&&&b *= i
&&return a, b
如果你觉得它的功能是“计算lst中所有元素的和与积”,不要沮丧。通常很难发现这里的错误。如果在大堆真实的代码中发现了这个错误就非常厉害了。——当你不知道这是一个测试时,很难发现这个错误。
这里的错误是在第二个循环体中使用了i而不是t。等下,这到底是怎么工作的?i在第一个循环外应该是不可见的? [1]哦,不。事实上,Python正式声明过,为for循环目标(loop target)定义的名称(更严格的正式名称为“索引变量”)能泄露到外围函数范围。因此下面的代码:
for i in [1, 2, 3]:
这段代码是有效的,可以打印出3。在本文中,我想探讨一下为什么会这样,为什么它不太可能改变,以及将它作为一颗追踪子弹来挖掘CPython编辑器中一些有趣的部分。
顺便说一句,如果你不相信这种行为可能会导致真正的问题,考虑这个代码片断:
def foo():
&&lst = []
&&for i in range(4):
&&&&lst.append(lambda: i)
&&print([f() for f in lst])
如果你期待上面的代码能打印出[0,1,2,3],你的期望会落空的,它会打印出[3,3,3,3];因为在foo的作用域内只有一个i,这个i就是所有的lambda所捕获的。
Python参考文档中的for循环部分明确地记录了这种行为:
&&& for循环将变量赋值到目标列表中。……当循环结束时,赋值列表中的变量不会被删除,但如果序列是空的,它们将不会被赋值给所有的循环。
注意最后一句,让我们试试:
for i in []:
的确,上面的代码抛出NameError异常。稍后,我们将看到这是Python虚拟机执行字节码方式的必然结果。
为什么会是这样
其实我问过Guido van Rossum有关这个执行行为的原因,他很慷慨地告诉了我其中的一些历史背景(感谢Guido!)。这样执行代码的动机是保持Python获得变量和作用域的简单性,而不诉诸于hacks(例如在循环完成后,删除定义在该循环中的所有变量——想想它可能引发的异常)或更复杂的作用域规则。
Python的作用域规则非常简单、优雅:模块、类以及函数的代码块可引入作用域。在函数体内,变量从它们定义到代码块结束(包括嵌套的代码块如嵌套函数)都是可见的。当然,对于局部变量、全局变量(以及其他nonlocal变量)其规则略有不同。不过,这和我们的讨论没有太多关系。
这里最重要的一点是:最内层的可能作用域是一个函数体。不是一个for循环体。不是一个with代码块。Python与其他编程语言不同(例如C及其后代语言),在函数水平下没有嵌套词法作用域。
因此,如果你只是基于Python实现,你的代码可能会以这样的执行行为结束。下面是另一段令人启发的代码片段:
for i in range(4):
&&d = i * 2
变量d 在for循环结束后是可见及可访问的,你对这样的发现感到惊奇吗?不,这正是Python的工作方式。那么,为什么索引变量的作用域被区别对待呢?
顺便说一句,列表推导式(list comprehension)中的索引变量也泄露到其封闭作用域,或者更准确的说,在Python 3之前可以泄露。
Python 3包含许多重大更改,其中也修复了列表推导式中的变量泄露问题。毫无疑问,这样破坏了向后兼容中性。这就是我认为当前的执行行为不会被改变的原因。
此外,许多人仍然发现这是Python中的一个有用的功能。考虑一下下面的代码:
for i, item in enumerate(somegenerator()):
&&dostuffwith(i, item)
print('The loop executed {0} times!'.format(i+1))
如果不知道somegenerator返回项的数目,可以使用这种简洁的方式。否则,你就必须有一个独立的计数器。
这里有一个其他的例子:
for i in somegenerator():
&&if isinteresing(i):
dostuffwith(i)
这种模式可以有效的在循环中查找某一项并在随后使用该项。[2]
多年来,许多用户都想保留这种特性。但即使对于开发者认定的有害特性,也很难引入重大更改了。当许多人认为该特性很有用,而且在真实世界的代码中大量使用时,就更不会除去这项特性了。
Under the hood
现在是最有趣的部分。让我们来看看Python编译器和VM是如何协同工作,让这种代码执行行为成为可能的。在这种特殊的情况下,我认为呈现这些的最清晰方式是从字节码开始逆向分析。我希望通过这个例子来介绍如何挖掘Python内部[3]的信息(这是如此充满乐趣!)。
让我们来看本文开篇提出的函数的一部分:
def foo(lst):
&&for i in lst:
&&&&a += i
&&return a
产生的字节码是:
0 LOAD_CONST&&&&&&& 1 (0)
&3 STORE_FAST&&&&&&& 1 (a)
&6 SETUP_LOOP&&&&&& 24 (to 33)
&9 LOAD_FAST&&&&&&& 0 (lst)
12 GET_ITER
13 FOR_ITER&&&&&&& 16 (to 32)
16 STORE_FAST&&&&&&& 2 (i)
19 LOAD_FAST&&&&&&& 1 (a)
22 LOAD_FAST&&&&&&& 2 (i)
25 INPLACE_ADD
26 STORE_FAST&&&&&&& 1 (a)
29 JUMP_ABSOLUTE&&&&& 13
32 POP_BLOCK
33 LOAD_FAST&&&&&&& 1 (a)
36 RETURN_VALUE
作为提示,LOAD_FAST和STORE_FAST是字节码(opcode),Python用它来访问只在函数中使用的变量。由于Python编译器知道(编译时)在每个函数中有多少个这样的静态变量,它们可以通过静态数组偏移量而不是一个哈希表进行访问,这使得访问速度更快(因而是_FAST后缀)。我有些离题了。这里真正重要的是变量a和i被平等对待。它们都通过LOAD_FAST获取,并通过STORE_FAST修改。绝对没有任何理由认为它们的可见性是不同的。[4]
那么,这种执行现象是怎么发生的?为什么编译器认为变量i只是foo中的一个局部变量。这个逻辑在符号表中的代码中,当编译器执行到AST开始创建一个控制流图,随后会产生字节码。这个过程的更多细节在我有关符号表的文章中的介绍——所以我只在这里提及其中的重点。
符号表代码并不认为for语句很特别。在symtable_visit_stmt中有如下代码:
case For_kind:
&&VISIT(st, expr, s-&v.For.target);
&&VISIT(st, expr, s-&v.For.iter);
&&VISIT_SEQ(st, stmt, s-&v.For.body);
&&if (s-&v.For.orelse)
&&&&VISIT_SEQ(st, stmt, s-&v.For.orelse);
索引变量如任何其他表达式一样被访问。由于该代码访问了AST,这值得去看看for语句结点内部是怎样的:
For(target=Name(id='i', ctx=Store()),
&&iter=Name(id='lst', ctx=Load()),
&&body=[AugAssign(target=Name(id='a', ctx=Store()),
&&&&&&&&&&op=Add(),
&&&&&&&&&&value=Name(id='i', ctx=Load()))],
&&orelse=[])
所以i在一个名为Name的节点中。这些是由符号表代码通过symtable_visit_expr中以下语句来处理的:
case Name_kind:
&&if (!symtable_add_def(st, e-&v.Name.id,
&&&&&&&&&&&&&e-&v.Name.ctx == Load ? USE : DEF_LOCAL))
&&&&VISIT_QUIT(st, 0);
&&/* ... */
由于变量i被清楚地标记为DEF_LOCAL(因为* _FAST字节码是可访问的,但是这也很容易观察到,如果符号表是不能用的则使用symtable模块),上述明显的代码调用symtable_add_def与DEF_LOCAL 作为第三个参数。现在来浏览一下上面的AST,并注意到Name结点中i的ctx=Store部分。因此,它是在For结点的target部分存储着i的信息的AST。让我们看看这是如何实现的。
编译器中的AST构建部分越过了解析树(这是源代码中相当底层的表示——一些背景资料可以在这里获得),同时在其他事项中,在某些结点设置expr_context属性,其中最显著的是Name结点。想想看,这样一来,在下面的语句:
foo = bar + 1
for和bar这两个变量都将在Name结点中结束。但是bar只是被加载到这段代码中,而for实际上被存储到这段代码中。expr_context属性通过符号表代码被用来区分当前和未来使用[5] 。
回到我们for循环的索引变量。这些内容将在函数ast_for_for_stmt——for语句创建AST——中处理。下面是该函数的相关部分:
static stmt_ty
ast_for_for_stmt(struct compiling *c, const node *n)
&&asdl_seq *_target, *seq = NULL, *suite_
&&expr_ty target,
&&/* ... */
&&node_target = CHILD(n, 1);
&&_target = ast_for_exprlist(c, node_target, Store);
&&if (!_target)
&&&&return NULL;
&&/* Check the # of children rather than the length of _target, since
&&&&for x, in ... has 1 element in _target, but still requires a Tuple. */
&&first = (expr_ty)asdl_seq_GET(_target, 0);
&&if (NCH(node_target) == 1)
&&&&target =
&&&&target = Tuple(_target, Store, first-&lineno, first-&col_offset, c-&c_arena);
&&/* ... */
&&return For(target, expression, suite_seq, seq, LINENO(n), n-&n_col_offset,
&&&&&&&&c-&c_arena);
在调用函数ast_for_exprlist时创建了Store上下文,该函数为索引变量创建了一个结点(注意,for循环的索引变量还可能是一序列变量的元组,而不仅仅是一个变量)。
在介绍为什么for循环变量和循环中的其他变量一视同仁的过程中,这个函数是最后总要的一部分。在AST中进行标记之后,在符号表和虚拟机中用于处理循环变量的代码与处理其他变量的代码是相同的。
本文讨论了Python中可能被认为是“疑难杂症”的某些特定行为。我希望这篇文章确实解释了Python的变量和作用域的代码执行行为,说明了为什么这些行为是有用的而且永远不太可能改变,以及Python编译器的内部如何使其正常工作。感谢您的阅读!
[1] 在这里,我很想开个Microsoft Visual C ++ 6的玩笑,但事实让人有些不安,因为在2015年这个博客的大部分读者不会懂这个笑话(这反映了我的年龄,而不是我的读者的能力)。
[2] 你可能会说,在执行到break之前时,dowithstuff(i)可以进入if中。但是,这并不总是很方便。此外,根据Guido的解释,这里对我们关注的问题做了一个很好的分离——循环被用于并只用于搜索。在搜索结束后,循环中的变量会发生什么已经不是循环关注的事情。我觉得这是非常好的一点。
[3]: 通常我的文章中的代码是基于Python 3。具体而言,我期待Python库中将要完成的下一个版本(3.5)的default分支。但是对于这个特定的主题,在3.x系列中的任何版本的源代码都应该是可以工作的。
[4] 函数分解中另一件很明显的事是,如果循环不执行,为什么i仍然是不可见的,GET_ITER和FOR_ITER这对字节码将我们的循环当做一个迭代器,然后调用其__next__方法。如果这个调用最后以抛出StopIteration异常结束,虚拟机捕捉到这个异常然后结束循环。只有实际值被返回,虚拟机才会继续对i执行STORE_FAST,因此让这个值存在,让后续代码可以引用。
[5] 这是一个奇怪的设计,我怀疑这个设计的实质是为了使用相对干净的递归访问AST中的代码,如符号表代码和CFG生成器。
你如果还想提升下自己,想更好的学习好,我整理了很多资料还有免费的课程学习欢迎加群
--- 共有 2 条评论 ---
如果要用while,可以用如下方式:
inputcontent = raw_input("please input old_text
filename: ")
inputs = str.split(inputcontent)
while len(inputs) != 3:
print "please input three parameters or input quit to exit this program"
inputcontent = raw_input()
inputs = str.split(inputcontent)
if inputcontent == 'quit':
sys.exit()
print "usage:file_replace.py old_text
old_text = inputs[0]
new_text = inputs[1]
filename = inputs[2]
--- 共有 1 条评论 ---

我要回帖

更多关于 上下文不允许函数定义 的文章

 

随机推荐