IO编程

本文详细介绍了Python中文件的基本操作,包括文件的打开、读写、关闭等方法,并讲解了文件迭代、内存中读取数据、数据存储等内容。此外,还介绍了Python中文件和目录的操作方法及异步IO的应用。

一、读写文件

通常文件的操作流程为:打开文件->读、写文件->关闭文件

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.34int或float
true / falseTrue / False
nullNone

        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服务。

转载于:https://my.oschina.net/u/3767248/blog/1605816

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值