IO编程
往外发数据的操作叫做Output,往外接收数据的操作叫做Intput。因此程序完成IO操作会有两个Intput和Output数据流。
在IO编程中存在速度不匹配的问题,其解决方法有两种:
第一种是同步IO ,即让cpu等着,程序暂停执行后续的代码,等当前读写操作完成后再往下执行。
第二种是异步IO ,即cpu不等待,让其慢慢读写,后续代码可以立刻执行,其复杂度远高于同步IO。
文件读写
读写文件就是请求操作系统打开一个文件对象,然后通过操作系统提供的接口从该文件对象中读取数据或把数据写入这个文件对象。
读文件
使用open()
函数读文件:f=open('路径','r')
,其中'r'
表示读取。
文件打开后,一次读取文件全部内容:f.read()
关闭文件:f.close()
为保证无论是否出错都能正确关闭文件,可引入with
语句:
1 2 with open ('路径' ,'r' ) as f: print (f.read())
每次最多读取size个字节的内容:f.read(size)
每次读取一行内容:f.readline()
一次读取所有内容并按行返回list:f.readlines()
若文件很小,使用read()
一次性读取最方便,若不能确定文件大小,反复调用read(size)
比较好,若是配置文件,调用readlines()
最方便。
file-like Object
像open()
函数返回的有个read()
方法的对象,统称为file-like Object。file-like Object不要求从特定类中继承,只要写read()
方法就行。除了file外,还可以是内存的字节流、网络流、自定义流等。
二级制文件
open()
函数默认读取的是UTF-8编码的文本文件,若要读取二进制编码的文件,open()
函数得写成:f=open('路径','rb')
。
字符编码
若要读取非UTF-8编码的文件,需要给open()
函数传入encoding
参数。
如读取GDK编码的文件:f=open('路径',encoding='gdk')
。
写文件
写文件时也是用open()
函数来打开文件,此时的open()
函数得写成:f=open('路径','w')
,其中'w'
表示写文本文件或写二进制文件。
往文件中写入内容:f.write(内容)
写完内容后要用f.close()
语句来关闭文件。为保证无论是否出错都能正确关闭文件,可引入with
语句:
1 2 with open ('路径' ,'w' ) as f: f.write('123' )
若要写入特定文本文件,需要给open()
函数传入encoding
参数,如f=open('路径','w',encoding='gdk')
若写入文件时文件 已存在,会直接覆盖原文件 ,若需要将内容追加到文件末尾 ,在open()语句中传入'a'
来写入。其形式为:open('路径','a')
练习题
请将本地一个文本文件读为一个str并打印出来:
1 2 3 4 5 6 fpath = '/etc/timezone' with open (fpath, 'r' ) as f: s = f.read() print (s)
代码修改如下:
1 2 3 4 5 6 fpath = './1.txt' with open (fpath, 'r' ) as f: s = f.read() print (s) helloworld!
StringIO和BytesIO()
StringIO
StringIO就是在内存中读写str,其只能操作str数据。
要把str写入 StringIO,需要先创建一个StringIO,然后在像写文件一样写入:
1 2 3 4 5 6 from io import StringIOf=StringIO() f.write('hello world!' ) print (f.getvalue())hello world!
获得写入后的str: getvalue()
要读取 StringIO,需要先用一个str初始化StringIO,然后在像读文件一样读取:
1 2 3 4 5 6 7 8 9 from io import StringIOf=StringIO('hello world!' ) while True : s=f.readline() if s=='' : break print (s.strip()) hello world!
BytesIO
StringIO就是在内存中读写bytes,其只能操作二进制数据。
使用BytesIO写入二进制数据如下:
1 2 3 4 5 6 from io import BytesIOf=BytesIO() f.write('中文' .encode('utf-8' )) print (f.getvalue())b'\xe4\xb8\xad\xe6\x96\x87'
使用BytesIO读取二进制数据如下:
1 2 3 4 5 from io import BytesIOf=BytesIO(b'\xe4\xb8\xad\xe6\x96\x87' ) print (f.read())b'\xe4\xb8\xad\xe6\x96\x87'
操作文件和目录
使用os
模块可以直接调用操作系统提供的接口函数。
os模块的基本功能如下所示:
1 2 3 4 5 6 import osprint (os.name)print (os.uname)
环境变量
系统定义的环境变量保存在os.environ
中。
获取某个环境变量key的值:os.environ.get('key')
操作文件和目录
查看、创建和删除目录语句如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import osprint (os.path.abspath('.' ))os.path.join('/某个目录路径' ,'新文件名称' ) os.mkdir('/新文件完整路径' ) os.rmkdir('/新文件完整路径' ) os.path.split('/新文件完整路径' ) os.path.splitext('/新文件完整路径' ) os.rename('文件原名' ,'文件新名' ) os.remove('文件名' )
os
模块中不存在复制文件的函数。
导入os
模块后,列出当前目录下所有目录: print([x for x in os.listdir('.') if os.path.isdir(x)])
导入os
模块后,列出当前目录下所有.py
文件: print([x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'])
练习题
1.利用os
模块编写一个能实现dir -l
输出的程序。
这里的dir-l
命令指的是在linux系统下运行的命令,其与ls -l
命令类似,作用皆为查看当前目录所有的文件和目录的详细信息。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import osimport time current=os.getcwd() filenum,filesize,dirnum=0 ,0 ,0 for name in os.listdir(current): if os.path.isfile(name): print ('%s\t\t%s\t%s' % (time.strftime('%Y-%m-%d %H:%M:%S' ,time.localtime(os.path.getmtime(name))),os.path.getsize(name),name)) filenum+=1 filesize+=os.path.getsize(name) if os.path.isdir(name): print ('%s\t\t%s\t%s' % (time.strftime('%Y-%m-%d %H:%M:%S' ,time .localtime(os.path.getmtime(name))),os.path.getsize(name),name)) dirnum+=1 print ('\t\t\t\t%d个文件,\t\t%d 字节' % (filenum,filesize)) print ('\t\t\t\t%d个目录' % (dirnum))
2.编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from genericpath import isfileimport osimport os.pathdef find_str (current,str ): for name in os.listdir(current): new_path=os.path.join(current,name) if os.path.isfile(new_path): if str in name: print ('name=%s , dir=%s' % (name,('../' +name))) else : find_str(new_path,str ) if __name__=='__main__' : find_str(os.path.abspath("." ),'.py' )
序列化
把变量从内存中变成可存储或传输的过程,称之为序列化 (picking)。序列化后可以把序列化后的内容写入磁盘,或通过网络传输到别的机器上。把变量从序列化的对象重新读到内存里的过程,称之为反序列化 (unpicking)。可以通过pickle
模块来实现序列化。
将对象序列化并写入文件中:
1 2 3 4 5 6 7 8 9 import pickled=dict (name='Bob' ,age=20 ,score=99 ) print (pickle.dumps(d))f=open ('1.txt' ,'wb' ) pickle.dump(d,f) f.close() €? }??name攲Bob攲age擪?score擪cu.
写入的1.txt
文件中的内容便是python保存的对象内容。
反序列化刚才保存的对象:
1 2 3 4 5 6 7 import picklef=open ('1.txt' ,'rb' ) d=pickle.load(f) f.close() print (d){'name' : 'Bob' , 'age' : 20 , 'score' : 99 }
Pickle只能用于python,且可能不同版本的python彼此不兼容,因此只能用Pickle保存那些不重要的数据,
JSON
若要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML、JSON。最好是序列化为JSON,因为其表示出来的就一个字符串,可以被所有语言读取,也更方便存储到磁盘或通过网络传输。
JSON表示的对象是标准的JavaScript语言的对象,JSON和Python内置的数据类型对象如下:
{}
dict
[]
list
"string"
str
1234.56
int或float
true/false
True/False
null
None
通过json模块可以把python对象变成一个JSON:
1 2 3 4 5 import jsond=dict (name='Bob' ,age=20 ,score=99 ) print (json.dumps(d)){"name" : "Bob" , "age" : 20 , "score" : 99 }
把JSON反序列化为Python对象:
1 2 3 4 5 import jsonjson_str='{"age":22,"score":93,"name":"Lisi"}' print (json.loads(json_str)){'age' : 22 , 'score' : 93 , 'name' : 'Lisi' }
JSON进阶
默认情况下,class实例不是一个可序列化为JSON的对象。若要序列化该class实例,需要先写一个转换函数把实例转换成dict,再序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import jsonclass Student (object ): def __init__ (self,name,age,score ): self.name=name self.age=age self.score=score def student2dict (std ): return { 'name' :std.name, 'age' :std.age, 'score' :std.score } s=Student('Bob' ,22 ,99 ) print (json.dumps(s,default=student2dict)){"name" : "Bob" , "age" : 22 , "score" : 99 }
不过以上代码只能应用于Student类,若要将任一class实例变为dict
可将student2dict()
函数以及print()
语句精简为以下语句:
print(json.dumps(s,default=lambda obj:obj.__dict__))
若要把JSON反序列化为一个class对象实例,可以先用loads()
方法转化出一个dict
对象,然后传入object_hook
函数把dict
转换为Student
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import jsonclass Student (object ): def __init__ (self,name,age,score ): self.name=name self.age=age self.score=score def dict2student (d ): return Student(d['name' ],d['age' ],d['score' ]) s=Student('Bob' ,22 ,99 ) json_str='{"age":22,"score":99,"name":"Bob"}' print (json.loads(json_str,object_hook=dict2student)) <__main__.Student object at 0x0000019E34217B80 >
练习题
对中文进行JSON序列化时,json.dumps()
提供了一个ensure_ascii
参数,观察该参数对结果的影响:
1 2 3 4 5 6 7 8 import jsonobj = dict (name='小明' , age=20 ) s = json.dumps(obj, ensure_ascii=True ) print (s){"name" : "\u5c0f\u660e" , "age" : 20 }
进程和线程
对于操作系统来说,一个任务就是一个进程。线程是最小的执行单元 ,由于每个进程至少要干一件事,因此一个进程至少有一个线程 ,多个线程可以同时执行。python既支持多进程又支持多线程。
多任务的实现 有3种方式:
多进程模式: 启动多个进程,每个进程只有1个线程,但多个进程可以一起执行多个任务。
多线程模式: 启动一个进程,在进程内启动多个线程,让多个线程一起执行多个任务。
多进程+多线程模式: 启动多个进程,每个进程再启动多个线程,同时执行多个任务。
多进程
fork()是一个系统调用函数,调用一次,返回两次。其中,当前进程(父进程)复制出来了一份子进程,父进程返回子进程的ID,子进程永远返回0。子进程可以通过getppid()
获取父进程的ID。
在Mac系统 中,python的os模块中封装了常见的系统调用函数,使其可以在程序中创建子进程:
1 2 3 4 5 6 7 8 9 10 11 import osprint ('Process (%s) start...' % os.getpid())pid=os.fork() if pid==0 : print ('I am child process (%s) and my parent is %s.' % (os.getpid(),os.getppid())) else : print ('I (%s) just created a child process(%s).' %(os.getpid(),pid)) Process (42 ) start... I (42 ) just created a child process(46 ). I am child process (46 ) and my parent is 42.
在Windows系统中 没有fork
调用,若要运行上面的代码,可以在onlinegdb在线编译器 里选择python3
语言后,编译运行。
有了fork调用,一个进程在接到新任务时就可以复制出一个子进程出来,父进程复制监听端口,子进程负责处理任务。
multiprocessing
若要在Windows上用python编写多进程程序,可以使用multiprocessing
模块,其是一个跨平台版本的多进程模块,提供了一个process类来代表一个进程对象。
运用multiprocessing
模块启动一个子进程并等待其结束:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from multiprocessing import Processimport osdef run_proc (name ): print ('Run child process %s (%s)...' %(name,os.getpid())) if __name__=='__main__' : print ('Parent process %s.' % os.getpid()) p=Process(target=run_proc,args=('test' ,)) print ('Child process will start.' ) p.start() p.join() print ('Child process end.' ) Parent process 12664. Child process will start. Run child process test (9516 )... Child process end.
Pool
若要启动大量的子进程,可以用进程池(pool)的方式批量创建子进程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from multiprocessing import Poolimport os,time,randomdef long_time_task (name ): print ('Run task %s (%s)...' % (name,os.getpid())) start=time.time() time.sleep(random.random()*3 ) end=time.time() print ('Task %s runs %0.2f seconds.' % (name,(end-start))) if __name__=='__main__' : print ('Parent process %s.' % os.getpid()) p=Pool(4 ) for i in range (5 ): p.apply_async(long_time_task,args=(i,)) print ('Waiting for all subprocesses done...' ) p.close() p.join() print ('All subprocesses done' ) Parent process 1228. Waiting for all subprocesses done... Run task 1 (1233 )... Run task 2 (1234 )... Run task 3 (1235 )... Run task 0 (1232 )... Task 2 runs 0.86 seconds. Run task 4 (1234 )... Task 1 runs 1.77 seconds. Task 3 runs 1.83 seconds. Task 4 runs 1.04 seconds. Task 0 runs 2.62 seconds. All subprocesses done
子进程
subprocess
模块可以启动一个子进程,然后控制其输入和输出。
如用subprocess
模块在python代码中运行命令nslookup www.python.org
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import subprocessprint ('$ nslookup www.python.org' )r=subprocess.call(['nslookup' ,'www.python.org' ]) print ('Exit code:' ,r)$ nslookup www.python.org 服务器: gdad07.gd.ctc.com Address: 10.157 .160 .89 非权威应答: 名称: dualstack.python.map .fastly.net Addresses: 2a04:4e42 :8c::223 151.101 .108 .223 Aliases: www.python.org Exit code: 0
若子进程需要输入,可以调用communicate()
方法来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import subprocessprint ('$ nslookup' )p = subprocess.Popen(['nslookup' ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err = p.communicate(b'set q=mx\npython.org\nexit\n' ) a=output.decode('unicode_escape' ) print (a)print ('Exit code:' , p.returncode)$ nslookup ĬÈÏ·þÎñÆ÷: gdad07.gd.ctc.com Address: 10.157 .160 .89 > > ·þÎñÆ÷: gdad07.gd.ctc.com Address: 10.157 .160 .89 python.org MX preference = 50 , mail exchanger = mail.python.org mail.python.org internet address = 188.166 .95 .178 mail.python.org AAAA IPv6 address = 2a03:b0c0:2 :d0::71 :1 > Exit code: 0
进程间通信
multiiprocessing
模块包装了底层的机制,提供了Queue
、Pips
等多种方式来交换数据,以实现进程间的通信。
以Queue
为例,在父进程中创建两个子进程,一个往Queue
里写数据,另一个从Queue
里读数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from multiprocessing import Process,Queueimport os,time,randomdef write (q ): print ('Process to write: %s' %os.getpid()) for value in ['A' ,'B' ,'C' ]: print ('Put %s to queue...' % value) q.put(value) time.sleep(random.random()) def read (q ): print ('Process to read: %s' %os.getpid()) while True : value=q.get(True ) print ('Get %s from queue.' %value) if __name__=='__main__' : q=Queue() pw=Process(target=write,args=(q,)) pr=Process(target=read,args=(q,)) pw.start() pr.start() pw.join() pr.join() pr.terminate() Process to write: 4684Process to read: 3796 Put A to queue... Get A from queue. Put B to queue... Get B from queue. Put C to queue... Get C from queue.
在Unix/Linix下,multiprocessing
模块封装了fork()
调用,由于Windows没有fork
调用,所以父进程所有python对象都必须通过pickle
序列化再传到子进程去。