唐抉的个人博客

python学习笔记(九)

字数统计: 4.2k阅读时长: 18 min
2022/10/12

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 StringIO
f=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 StringIO
f=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 BytesIO
f=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 BytesIO
f=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 os
print(os.name)#获取操作系统类型
#若输出nt,说明系统是Windows系统,若输出posix,说明是Linux、Unix或Moc OS X系统

print(os.uname)#获取详细的操作系统信息
#uname函数在Windows上不提供

环境变量

系统定义的环境变量保存在os.environ中。

获取某个环境变量key的值:os.environ.get('key')

操作文件和目录

查看、创建和删除目录语句如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
print(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 os
import 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 isfile
import os
import os.path

def 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 pickle
d=dict(name='Bob',age=20,score=99)
print(pickle.dumps(d))#把任意对象序列化成一个bytes
f=open('1.txt','wb')
pickle.dump(d,f)#直接把对象序列化后写入一个file-like Object
f.close()
#运行结果如下:
#文件1.txt中的结果
€? }??name攲Bob攲age擪?score擪cu.

写入的1.txt文件中的内容便是python保存的对象内容。

反序列化刚才保存的对象:

1
2
3
4
5
6
7
import pickle
f=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内置的数据类型对象如下:

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 json
d=dict(name='Bob',age=20,score=99)
print(json.dumps(d))#返回一个str,内容是标准的JSON
#运行结果如下:
{"name": "Bob", "age": 20, "score": 99}

把JSON反序列化为Python对象:

1
2
3
4
5
import json
json_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 json
class 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 json
class 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 json
obj = dict(name='小明', age=20)
s = json.dumps(obj, ensure_ascii=True)
#当ensure_ascii的值为True时,name以ascii字符码输出
#当ensure_ascii的值为False时,name以中文输出
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 os
print('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 Process
import os
def 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 Pool
import os,time,random
def 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)#设置最多同时执行4个进程,pool默认大小是cpu的核数
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print('Waiting for all subprocesses done...')
p.close()#停止继续添加新进程
p.join()#等待所有子进程执行完毕,调用前必须先调用close()
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 subprocess
print('$ 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 subprocess
print('$ 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')#对unicode编码的output进行反编码
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模块包装了底层的机制,提供了QueuePips等多种方式来交换数据,以实现进程间的通信。

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,Queue
import os,time,random
#写数据进程所执行的代码
def 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()#父进程创建Queue,并传给各个子进程
pw=Process(target=write,args=(q,))
pr=Process(target=read,args=(q,))
pw.start()#启动子进程pw,写入
pr.start()#启动子进程pw,读取
pw.join()#等待pw结束
pr.join()#pr进程里是 死循环,无法等待其结束,只能强行终止
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序列化再传到子进程去。

CATALOG
  1. 1. IO编程
    1. 1.1. 文件读写
      1. 1.1.1. 读文件
      2. 1.1.2. file-like Object
      3. 1.1.3. 二级制文件
      4. 1.1.4. 字符编码
      5. 1.1.5. 写文件
      6. 1.1.6. 练习题
    2. 1.2. StringIO和BytesIO()
      1. 1.2.1. StringIO
      2. 1.2.2. BytesIO
    3. 1.3. 操作文件和目录
      1. 1.3.1. 环境变量
      2. 1.3.2. 操作文件和目录
      3. 1.3.3. 练习题
    4. 1.4. 序列化
      1. 1.4.1. JSON
      2. 1.4.2. JSON进阶
      3. 1.4.3. 练习题
  2. 2. 进程和线程
    1. 2.1. 多进程
      1. 2.1.1. multiprocessing
      2. 2.1.2. Pool
      3. 2.1.3. 子进程
      4. 2.1.4. 进程间通信