Python学习笔记(2)

Python的IO编程

操作IO的能力都是有操作系统决定的,因此,使用高级语言来操作都是调用的系统所提供的低级C接口。

本文探讨的IO都是同步的

非二进制文件读写

向其他脚本语言相似,Python提供了以下非二进制文件IO模式:

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。
r+ 打开一个文件用于读写。文件指针将会放在文件的开头。
w 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
w+ 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。

总结一下就是:

凡是有+的都可以读写,只有r模式不会创建新文件(抛出IOError的异常),wa都会在没有找到文件时创建,只有a的初始指针在文件尾。

基本API:

使用open()来打开一个文件,从

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

可看出,默认的模式为 r ,必要参数只有一个,那就是file,这里,file是一个 path-like-object ,除了我们认为的文件,还能打开内存的字节流,网络流,自定义流等等。

这样我们便得到了一个file对象,有用的属性有:

属性 描述
file.closed 返回文件是否已关闭
file.name 返回文件的名字
file.mode 返回文件的打开模式

使用read()来读取一个文件的全部内容,Python会把内容全部塞到内存中,用一个str,来表示。

使用write()来向一个文件写入内容(注意:操作系统往往不会在我们调用write()时就立即写入数据,而是先暂存到内存中,空闲时在慢慢写入(这个可以使用交互式的Shell来试一下:执行写入操作后先不要关闭句柄,到资源管理器中看一下,会发现文件大小不变)),而当我们调用close()方法时,OS才会去确保把数据全部写入。

因此,必须在文件使用完毕后调用close()方法(否则可能会有数据滞留内存中)。

如果想要立即写入数据,可以调用flush()方法来刷新文件内部缓冲,相当于是主动写入。

如果想要知道当前文件指针的位置,则可以直接调用tell()方法,返回文件指针的位置(字节数)

使用seek(offset[,from])方法来调整指针位置,参数是支持负数的(也就是向前偏移)

在做IO时一般都要考虑IO异常的产生,因此要进行异常处理:

1
2
3
4
5
6
7
8
try:
f = open("sample", "r")
print(f.read())
except IOError as e:
print("IO异常!Error:",e)
finally:
if f:
f.close()

这样写比较繁琐,所以Python提供了with语法糖:

1
2
with open("sample", "r") as f:
print(f.read())

这样就使得代码变得更加简洁,而且不需要再调用close方法。

回到前面说的,read方法会把文件全部读取到内存中,这对于小文件是可行的,如果文件大小超过了8G,内存可能就爆了。

所以,最好使用反复调用read(size)这样来读取,或是使用readlines()来读取,这种读取方法非常适合读取配置文件或单行文本。

1
2
for line in f.readlines():
print(line.strip())

二进制IO和FS操作

对于二进制的文件操作基本与之前的文件读写操作类似,不同的是访问模式:(也就是在之前的访问模式后加b = _binary_)

模式 描述
rb 以只读方式打开一个二进制文件。文件的指针将会放在文件的开头。
rb+ 打开一个二进制文件用于读写。文件指针将会放在文件的开头。
wb 打开一个二进制文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb+ 打开一个二进制文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
ab 打开一个二进制文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab+ 打开一个二进制文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。

对于文件系统的操作,主要使用一个在Python中非常重要的模块 – os.

先来看一下os的一些基本属性吧。

os中有很多API在Windows下是不可用的,如果想让os发挥最大作用,也许你需要一台高贵的Mac或者一只免费的penguin

使用os.name,获取系统类型 : 如windowsntLinux/Unix/Mac OS Xposix

使用os.uname(),获取详细的系统信息, 不支持Windows

os还提供了一系列的方法来进行文件系统的操作,就好像在跑命令行一样。

来看一个综合实例:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#! /usr/bin/env python3
# -*- utf-8 -*-
import os,sys,stat
print("We are now at "+os.getcwd())
os.chdir("./io")
print("We are now at",os.getcwd())
content = os.listdir(os.getcwd())
for n in content:
print(n)
print("Now let us create a new dir")
try:
os.mkdir("./temp")
except FileExistsError as e:
print("[+] Error:File exists")
content = os.listdir(os.getcwd())
for n in content:
print(n)
os.chdir("./temp")
try:
f = open("temp.file", "w")
text = "Now let us write data in this empty file"
f.write(text)
f.flush()
print("Stream Position:",f.tell(),"And length of this file is ",len(text))
f.seek(0,0)
f.write("Rewrite")
f.flush()
print("Let us see some info of this new file:",os.stat("./temp.file"))
print("Now let change the mode of this file as Read-Only:")
except PermissionError as e:
print("[+] Error:",e)
finally:
f.close()
os.chmod("./temp.file", stat.S_IREAD)
print("Now let us change the name of this file")
try:
os.rename("./temp.file", "./temp.file.new")
except (PermissionError,FileExistsError) as e:
print("[+] Error",e)
print("Is the change successfully?")
c = os.listdir("./")
print(c)
print("Alright~Our test is nearly end.\nNow Let us remove the creations.")
if c[0] == "temp.file.new":
os.chmod("./temp.file.new", stat.S_IWRITE)
os.remove("./temp.file.new")
elif c[0] == "temp.file":
os.chmod("./temp.file", stat.S_IWRITE)
os.remove("./temp.file")
print(os.getcwd())
os.chdir("..")
try:
os.remove("./temp")
except OSError as e:
print("[+] Error:",e)
print("Oops..It seems that remove cannot delete a dir")
print("Now we use rmdir method")
os.rmdir("./temp")

也许你会奇怪,为什么os模块中并没有封装剪切、复制这样的常用方法。(虽然我们已经可以使用上面的IO解决)

铛铛铛! Python为我们封装了shutil模块。shutil提供了各种剪切复制方法(文件,对象,信息,模式…都可以)

事实上他的复制也就是我们所想的那样:

删除了部分注释

特意又去看了2.7的源码…基本没有改动(3.6增加了对文件链接的处理、以及3.3/3.4对异常的更改)

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
def copyfile(src, dst, *, follow_symlinks=True):
if _samefile(src, dst):
raise SameFileError("{!r} and {!r} are the same file".format(src, dst))
for fn in [src, dst]:
try:
st = os.stat(fn)
except OSError:
pass
else:
if stat.S_ISFIFO(st.st_mode):
raise SpecialFileError("`%s` is a named pipe" % fn)
if not follow_symlinks and os.path.islink(src):
os.symlink(os.readlink(src), dst)
else:
with open(src, 'rb') as fsrc: # 文件输入流
with open(dst, 'wb') as fdst: # 文件写入流
copyfileobj(fsrc, fdst)
return dst

def copyfileobj(fsrc, fdst, length=16*1024):
while 1:
buf = fsrc.read(length) # 读数据
if not buf:
break
fdst.write(buf) # 写数据

序列化

Python支持bytes序列化和流行的JSON序列化。

本文仅谈一下JSON的部分。

JSON中允许的数据类型有:

  • {}
  • []
  • “string”
  • 1234.56
  • true/false
  • null

与之对应的Python数据类型为:

  • dict
  • list
  • str
  • int/float
  • True/False
  • None

使用起来也很容易,使用dumps()来进行序列化,使用loads()来反序列化

1
2
3
4
5
6
7
import json
d = dict(name='Bob', age=20, score=88)
json.dumps(d)
<return> '{"age": 20, "score": 88, "name": "Bob"}'
json_str = '{"age": 20, "score": 88, "name": "Bob"}'
json.loads(json_str)
<return> {'age': 20, 'score': 88, 'name': 'Bob'}

既然使用起来这么方便,那么我们可否直接序列化一个类的实例呢?

答案是:可以

比方说我们定义一个Student类,有name,age,score参数,我们便可以定义这样的方法:

1
2
3
4
5
6
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}

接着,在序列化该实例时,调用:

1
2
s = Student("Justin", 19, 100)
json.dumps(student, default=student2dict)

就可以按照我们的想法来序列化了。