一、读写文件
通常文件的操作流程为:打开文件->读、写文件->关闭文件
1、函数
1.1、打开文件:open()
定义:open(filename, access_mode = 'r', buffering = -1)
filename: 文件名,字符串,可以是文件的绝对路径,也可以是相对路径
access_mode:可选变量,字符串,文件打开的模式。
通用的文件打开模式:‘r’(读),‘w’(写),‘a’(追加),‘U’(代表通用换行符支持)
‘r’或‘U’模式打开的文件必须已经存在。
‘w’模式打开的文件若存在则首先清空,然后(重新)创建。
‘a’模式打开的文件,表示所有写入的数据都追加到文件的末尾。若文件不存在,则自动创建。
‘b’表示二进制模式访问
‘+’表示可读可写。
访问模式组合:
r:只读模式打开
rU或U:读模式打开,同时提供通用换行符支持。
w:以写模式打开( 必要时清空)
a:以追加模式打开(从EOF开始,必要时创建新文件)
r+或w+:以读写模式打开
a+:以读和追加写模式打开
rb:二进制读模式打开
wb:二进制写模式打开
ab:二进制追加模式打开
rb+或wb+:二进制读写模式打开
ab+:二进制读和追加写模式打开
buffering:指访问文件所采用的缓冲方式。
0:不缓冲;
大于0:表示使用给定值作为缓冲区大小;
不提供参数或者给定负值:表示使用系统默认的缓冲机制。
示例:f = open('test', 'r')
1.2、读文件:read()\readline()\readlines()
read():直接读取指定数目的字节到字符串,若没有给定可选参数size或size的值为负数,文件将被读取直至末尾。
readline():读取打开文件的一行,然后整行包括行结束符,作为字符串返回。和read相同,若提供可选参数size,则返回超过size个字节后不完整的行。
readlines():读取所有剩余的行,并把它们作为一个字符串列表返回。可选参数size表示返回的最大字节,若大于0,表示返回的所有行应该大约有size个字节(可能稍微大于这个数字,因为需要凑齐缓冲区大小)。
调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便。
示例:f.read()
f.readline()
f.readlines()
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
1.3、写文件:write()/writelines()
write():把含有文本数据或二进制数据块的字符串写入到文件中去。
writelines():它接收一个字符串列表作为参数,将他们写入文件。行结束符并不会被自动加入,所以如有必要,需在调用writelines()前给每行结尾加上行结束符。
示例:f.write()
f.writelines()
1.4、关闭文件:close()
close():通过关闭文件来结束对它的访问。若不显式的关闭文件,那么你可能丢失输出缓冲区的数据。
示例: f.close()
1.5、其他方法:
seek():可以在文件中移动文件指针到不同的位置。
offset:代表相对于某个位置的偏移量。位置默认值为0,代表从文件开头算起;1,代表从当前位置算起;2,代表从文件末尾算起。
text():它告诉你当前文件指针在文件中的位置——从文件起始算起,单位为字节。
fileno():返回打开文件的描述符,这是一个整型。
flush():会直接把内部缓冲区中的数据立刻写入文件,而不是被动的等待输出缓冲区被写入。
isatty():是一个布尔内建函数,当文件是一个类tty设备时返回True,否则返回False。
truncate():将文件截取到当前文件指针位置或者到给定size,以字节为单位。
1.6、注意
1.6.1、 当使用输入方法read()或者readlines()从文件中读取行时,python不会删除行结束符,这个操作被留给了程序员。类似的,输出方法write()或writelines()也不会自动加入行结束符,应该在写入数据前自己完成。
示例: data = [line.strip() for line in f.readlines()]
1.6.2、一般在读写文件时可能报错,然后f.close()函数就不会调用,导致文件句柄没有被关掉,占用操作系统的资源。并且在调用写write()向文件写入数据时,操作系统不会把数据立刻写入磁盘文件中,而是等到空闲时慢慢写入,只有调用 close()函数才会将剩下的数据全部写入磁盘文件,若没有调用close()函数可能导致只向磁盘中写入了部分数据,其他数据可能丢失,因此需要保证close()函数被调用。
可以使用try.....finally...来实现:
try:
f = open('test', 'rw')
print(f.read())
f.write("hello world!")
finally:
if f:
f.close()
但是太繁琐,可以使用with来自动调用close()函数:
1.6.3、file-like Object
open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
1.6.4、字符编码
要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
f.read()
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
1.7、文件迭代:
for eachline in f:
1.8、示例:
filename = input('Enter file name:')
f = open(filename, 'r')
for eachline in f:
print(eachline)
f.close()
二、操作文件和目录
python操作文件和目录主要依赖于os和os.path模块。
2.1、os模块的文件/目录访问函数
mkfifo()/mknod():创建命名管道/创建文件系统节点
remove()/unlink():删除文件
rename()/renames():重命名文件
symlink():创建符号链接
utime():更新时间戳
tmpfile():创建并打开('w'+'b')一个新的临时文件
listdir():列出指定目录的文件
mkdir()/makedirs():创建目录/创建多层目录
rmdir()/removedirs():删除目录/删除多层目录
access():检验权限模式
chmod():改变权限模式
2.2、os.path模块中的路径名访问函数
basename():去掉路径名,返回文件名
dirname():去掉文件名,返回目录路径
join():将分离的各部分组合成一个路径名
split():返回(dirname(),basename())元组
splitdrive():返回(drivename, pathname)元组
splitext():返回(filename,extension)元组
getatime():返回最近访问时间
getctime():返回文件创建的时间
getmtime():返回最近文件修改时间
getsize():返回文件大小(以字节为单位)
exists():指定路径(文件或目录)是否存在
isabs():指定路径是否为绝对路径
isdir():指定路径是否存在且为一个目录
isfile():指定路径是否存在且为一个文件
islink():指定路径是否存在且为一个符号链接
ismount():指定路径是否存在且为一个挂载点
samefile():两个路径名是否指向同一个文件
注意:把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。同样的道理,要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名.os.path.splitext()可以直接让你得到文件扩展名.这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。
另外,python中两个常用的列出目录中所有文件的语句:
[x for x in os.listdir('.') if os.path.isdir(x)] #列出当前目录中的所有目录
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] #列出当前目录中所有的python文件
三、内存中读取数据:
StringIO、BytesIO的操作基本和文件操作是一样的:创建、读写、关闭。
3.1、 StringIO:内存中读取str
getvalue()方法用于获得写入后的str。
使用方法示例:
#!/usr/bin/env python3
#-*-coding:utf-8-*-
from io import StringIO
f = StringIO()
f.write('hello')
f.write(' world!')
print(f.getvalue())
f.close()
f = StringIO('Hello !\nHi!\nGoodbye!')
while True:
s = f.readline()
if s == '':
break
print(s.strip())
f.close()
3.2、 BytesIO:内存中读取二进制数据
使用方法示例:
#!/usr/bin/env python3
#-*-coding:utf-8-*-
from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())
f.close()
f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
print(f.read())
f.close()
四、数据存储
数据存储主要依赖:pickle,json,shelve
序列化:把变量从内存中变成可存储或传输的对象
反序列化:把变量内容从序列化的对象重新读到内存里
主要依赖两个函数:dump()和load()。
dump():函数接受一个文件句柄和一个数据对象作为参数,把数据对象以特定的格式保存到给定的文件里。
load():从文件中取出已保存的对象,并恢复到它们本来的格式。
4.1、pickle
pickle.dumps():首先把一个对象序列化为一个bytes,然后再把这个bytes写入到文件中。
pickle.dump():直接把对象序列化后写入一个file-like Object。
pickle.loads():先把对象内容从磁盘文件中读到一个bytes中,然后通过pickle.loads()反序列化出对象。
pickle.load():从一个file-like Object中直接反序列化出对象。
示例:
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import pickle
f = open('dump.txt','wb')
d = dict(name = 'Bob', age = 20, score = 88)
print(pickle.dumps(d))
f.write(pickle.dumps(d))
pickle.dump(d, f)
f.close()
print("dump over, load start")
f = open('dump.txt', 'rb')
d = pickle.load(f)
print(d)
f.close()
4.2、JSON
不同语言之间传递对象,就必须把对象序列化为标准格式,比如xml,json。json表示出来就是一个字符串,可以被所有语言读取,也可以方便的存储到磁盘或者通过网络传输。Json不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
json表示的对象就是标准的javascript语言的对象,json和python内置的数据类型对应如下:
JSON类型 | PYthon类型 |
{ } | dict |
[] | list |
'string' | str |
12 , 12.34 | int或float |
true / false | True / False |
null | None |
json.dumps():将对象内容转换为json格式的字符串作为函数返回结果 。
json.dump():可以直接把对象转换为json格式的数据并写入file-like Object中。
json.loads():把json格式的字符串反序列化为一个python对象。
json.load():直接从file-like Object中反序列化出一个python对象。
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import json
d = dict(name = 'Bob', age = 20, score = 88)
print(d)
print(json.dumps(d))#把dict转换为字符串
print(json.loads(json.dumps(d)))
json_str = '{"age":20, "score":88, "name":"Bob"}'
print(json_str)
print(json.loads(json_str)) #把字符串转换为dict
python的dict对象可以直接序列化为json的{},但是类class不能直接序列化为json,需要通过dump的可选参数default把任意一个对象可序列化为一个json对象。
把类对象序列化为json对象的方法:首先将类实例转换为一个dict,然后序列化为json。
把json对象反序列化为类对象时:首先将序列化的json对象反序列化为dict,然后在利用相应的函数将dict转换为类实例。
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import json
d = dict(name = 'Bob', age = 20, score = 88)
print(d)
print(json.dumps(d))#把dict转换为字符串
print(json.loads(json.dumps(d)))
json_str = '{"age":20, "score":88, "name":"Bob"}'
print(json_str)
print(json.loads(json_str)) #把字符串转换为dict
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
def __str__(self):
return 'Student object (%s, %s, %s)'%(self.name, self.age, self.score)
__repr__ = __str__
s = Student('Bob', 20 ,99)
#方法一
print("method first")
#把student对象转换为dict
def student2dict(std):
return{
'name':std.name,
'age':std.age,
'score':std.score
}
#利用default参数和student2dict把student实例序列化为json对象
print(json.dumps(s,default = student2dict))
#把dict转换为student实例
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
#利用object_hook参数和dict2student把json字符串转换为student实例
json_str = '{"age":20, "name":"Bob", "score":88}'
print(json.loads(json_str, object_hook = dict2student))
#方法二
print("method second")
#利用匿名函数lambda和__dict__函数将类实例序列化为json对象。
student_data = json.dumps(s, default = lambda obj:obj.__dict__)
print('Dump Student:', student_data)
#利用lambda函数和Student实例化将json对象反序列化为类对象。
rebuild = json.loads(student_data, object_hook = lambda d:Student(d['name'],d['age'], d['score']))
print(rebuild)
#总结:通常采用方法2,代码简单,可复用性强
五、其他文件的操作
压缩:gzip,zlib,zipfile,tarfile
高级文件访问:shutil
临时文件:tempfile
网络文件:socket,urllib
六、异步IO
6.1、协程
协程比起线程的优势:
1、最大的优势就是协程极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
2、第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,所以需要多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
python对协程的支持是通过generator实现的。在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
但是python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
#!/usr/bin/env python3
#-*-coding:utf-8-*-
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[Consumer] consuming %s....'%n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[Producer] producing %s...'%n)
r = c.send(n)
print('[Producer] consumer return:%s'%r)
c.close()
c = consumer()
produce(c)
注意到consumer函数是一个generator,把一个consumer传入produce后:
首先调用c.send(None)启动生成器;
然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
consumer通过yield拿到消息,处理,又通过yield把结果传回;
produce拿到consumer处理的结果,继续生产下一条消息;
produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
最后套用Donald Knuth的一句话总结协程的特点:“子程序就是协程的一种特例。”
6.2、asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
用asyncio实现Hello world代码如下:
#!/ust/bin/env python3
#-*-coding:utf-8-*-
import asyncio
@asyncio.coroutine
def hello():
print('Hello World!')
#异步调用asyncio.sleep(1)
r = yield from asyncio.sleep(1)
print('Hello again!')
#获取EventLoop
loop = asyncio.get_event_loop()
#执行coroutine
loop.run_until_complete(hello())
loop.close()
@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。
hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。
把asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。
我们用Task封装两个coroutine试试:
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import threading
import asyncio
@asyncio.coroutine
def hello():
print('Hello world! (%s)'%threading.currentThread())
yield from asyncio.sleep(1)
print('Hello again! (%s)'%threading.currentThread())
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。
如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。
我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页:
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import asyncio
@asyncio.coroutine
def wget(host):
print('wget %s...'%host)
connect = asyncio.open_connection(host, 80)
reader, writer = yield from connect
header = 'GET / HTTP/1.0\r\nHost:%s\r\n\r\n'%host
writer.write(header.encode('utf-8'))
yield from writer.drain()
while True:
line = yield from reader.readline()
if line == b'\r\n':
break
print('%s header > %s'%(host, line.decode('utf-8').rstrip()))
#Ignore the body, close the socket
writer.close()
loop = asyncio.get_event_loop()
tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com','www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
可见3个连接由一个线程通过coroutine并发完成。
asyncio提供了完善的异步IO支持;异步操作需要在coroutine中通过yield from完成;多个coroutine可以封装成一组Task然后并发执行。
6.3、async/await
用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
请注意,async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
把@asyncio.coroutine替换为async;
把yield from替换为await。
让我们对比一下上一节的代码:
@asyncio.coroutine
def hello():
print('Hello World!')
#异步调用asyncio.sleep(1)
r = yield from asyncio.sleep(1)
print('Hello again!')
新语法:
async def hello():
print('Hello world!')
r = await asyncio.sleep(1)
print('Hello again!')
Python从3.5版本开始为asyncio提供了async和await的新语法;
注意新语法只能用在Python 3.5以及后续版本,如果使用3.4版本,则仍需使用上一节的方案。
6.4、aiohttp
asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。
安装aiohttp: pip install aiohttp
#!/usr/bin/env python3
#-*-coding:utf-8-*-
import asyncio
from aiohttp import web
async def index(request):
await asyncio.sleep(0.5)
return web.Response(body = b'<h1>Index</h1>')
async def hello(request):
await asyncio.sleep(0.5)
text = '<h1>hello, %s!</h1>'%request.match_info['name']
return web.Response(body = text.encode('utf-8'))
async def init(loop):
app = web.Application(loop = loop)
app.router.add_route('GET', '/', index)
app.router.add_route('GET', '/hello/{name}', hello)
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
print('Server started at http://127.0.0.1:8000....')
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
注意aiohttp的初始化函数init()也是一个coroutine,loop.create_server()则利用asyncio创建TCP服务。