(一)持久化
- 之前的程序都是临时的(transient):只运行一段时间并输出结果,当程序运行结束以后,数据就消失了;再次运行程序,将以全新的状态开始;
- 还有持久的(persistent),可以长时间运行(或者一直运行),程序重新启动之后吗,将从上次中断的地方开始;
- 程序保存数据的一个简单的办法是读写文件;
- 另外一个办法是使用数据库;
(二)读取和写入
- 要写入文件,将
open
函数的第二个参数写为w
:
>>> fout = open('output')
如果该文件已经存在,那么用写入模式打开它将会清空原来的数据并从新开始,所以要小心! 如果文件不存在,那么将创建一个新的文件;
open
会返回一个文件对象,这个对象提供了操作我呢间的方法;write
方法将数据写入文件:
>>> line = "This here's the wattle,\n"
>>> fout.write(line1)
24
返回值是被写入字符的格式;
- 文件对象将跟踪自身的位置,所以下次调用
write
的时候,会在文件末尾添加新的数据:
>>> line2 = "the emblem of our land.\n"
>>> fout.write(line2)
24
- 完成文件的写入之后,需要关闭这个文件,否则会在程序结束的时候关闭:
>>> fout.close()
(三)格式化运算符
-
write
的参数必须是字符串,所以可以使用str
将值转化为字符串格式 -
另一种方法是格式化运算符(format operator),即
%
:- 当作用于整数的时候,
%
是取模运算符; - 当第一个运算数是字符串的时候,
%
则是格式化运算符;
- 当作用于整数的时候,
-
第一个运算数是格式化字符串(format string),包含一个或者多个格式化序列(format sequence);格式化序列指定了第二个运算数是如何格式化的,运算的结果是一个字符串:
# 格式化序列 '%d' 意味着第二个运算数应该被格式化为一个十进制整数
>>> camels = 42
>>> '%d' % camels
'42'
- 一个格式化序列可以出现在字符串中的任何位置,所以可以将一个值嵌入到一个语句中:
>>> 'I have spotted %d camels.' % camels
'I have spotted 42 camels.'
- 如果字符串中有多个格式化序列,则第二个参数必须是一个元组;每个格式化序列按照顺序与元组中的元素进行对应:
# 使用 '%d' 来格式化一个整数, '%g' 来格式化一个浮点数,以及 '%s' 来格式化一个字符串:
>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'
- 元组中元素的个数必须等于字符串中格式化序列的个数。 同时,元素的类型也必须符合对应的格式化序列:
>>> '%d %d %d' % (1, 2)
TypeError: not enough arguments for format string
>>> '%d' % 'dollars'
TypeError: %d format: a number is required, not str
-
格式化输出的另一种办法是使用**str.format()**方法:
- 这个方法使用占位符,占位符中是序号:
>>> "The sum of 1 + 2 is {0}".format(1+2) "The sum of 1 + 2 is 3"
(四)文件名和路径
文件是以**目录(directory)的形式组起来的,每个正在运行的程序都有一个当前目录(current directory)**作为大多数操作的默认目录;
os
模块提供了操作文件和目录的函数;os.getcwd
返回当前目录的名称:
>>> import os
>>> cwd = os.getcwd()
>>> cwd
'/home/dinsdale'
- 一个简单的文件名
memo.txt
同样是一个路径,只不过是相对路径; - 一个以
/
开头的路径和当前目录无关,叫做绝对路径(absolute Path);可以使用下面的方法获取一个文件的绝对路径:
>>> os.path.abspath('memo.txt')
'/home/dinsdale/memo.txt'
- 使用
os.path.exists
检查一个文件或者路径是不是存在:
>>> os.path.exists('memo.txt')
True
- 使用
os.path.isdir
检查它是不是一个目录:
>>> os.path.isdir('memo.txt')
False
>>> os.path.isdir('/home/dinsdale')
True
-
使用
os.path.isfile
检查是不是一个文件: -
使用
os.listdir
返回给定目录下的文件列表(以及其他目录):
>>> os.listdir(cwd)
['music', 'photos', 'memo.txt']
- 示例:
def walk(dirname):
for name in os.listdir(dirname):
path = os.path.join(dirname, name)
if os.path.isfile(path):
print(path)
else:
walk(path)
os.path.join
接受一个目录和一个文件名,并将它们合并成一个完成的路径
(五)异常捕获
- 当试图读写文件的时候,会很容易发生错误;如访问不存在的文件、没有权限的文件等;
- 为了避免这样的错误,当然可以使用类似于
os.path.exists
和os.path.isfile
的函数进行检查,但是这样会耗费大量的时间和代码检查所有的可能性;- 更加好的办法是在问题出现的时候才进行处理,这个是
try
语句做的事情:
try:
fin = open('bad_file')
except:
print('Something went wrong.')
- 一般来说,捕获异常之后,可以选择是不是解决这个问题,或者继续常识运行,或者结束程序;
(六)数据库
- 数据库是用来存储信息的文件;
- 大多数的数据库采用类似字典的形式,即将键映射到值;但是数据库和字典的最大的区别是:数据库是存储在硬盘上的,所以当程序结束运行之后,依然是存在的;
dbm
模块提供了创建和更新数据库文件的接口:
>>> import dbm
>>> db = dbm.open('captions', 'c')
# 'c'代表如果数据库不存在则创建该数据库;
#这个操作返回一个数据库对象,可以像使用字典一样使用它(对于多数的操作)
- 当创建一个新的项目时,
dbm
将会更新数据库:
>>> db['cleese.png'] = 'Photo of John Cleese'
- 当访问某个项时,
dbm
将会读取文件:
>>> db['cleese.png']
b'Photo of John Cleese.'
返回的结果是一个字节对象(bytes object),所以是以b
开头;一个字节对象在很多方面和一个字符串很像,在目前的阶段可以忽略他们之间的不同,但是他们之间的不同是非常重要的;
- 当对已经存在的值进行重新赋值,将会替换原有的值:
>>> db['cleese.png'] = 'Photo of John Cleese doing a silly walk.'
>>> db['cleese.png']
b'Photo of John Cleese doing a silly walk.'
keys
和items
等字典方法并不适合于数据库对象,但是for
循环是可以的:
for key in db:
print(key, db[key])
- 当操作完毕之后需要关闭文件:
>>> db.close()
(七)序列化
dbm
模块的键和值必须是字符串或者字节;pickle
可以将几乎所有类型的对象转化为适合在数据库中存储的字符串;也可以将字符串还原为原来的对象
pickle.dumps
读取一个对象作为参数,并返回一个字符串:
>>> import pickle
>>> t = [1, 2, 3]
>>> pickle.dumps(t)
b'\x80\x03]q\x00(K\x01K\x02K\x03e.'
pickle.loads
可以重建对象:
>>> t1 = [1, 2, 3]
>>> s = pickle.dumps(t1)
>>> t2 = pickle.loads(s)
>>> t2
[1, 2, 3]
- 虽然新的对象和旧的对象是有相同的值,但是并不是相同的对象:
>>> t1 == t2
True
>>> t1 is t2
False
序列化,之后反序列化,等效于复制一个对象;
(八)管道
- 大多数操作系统提供了命令行接口,称之为
shell
;- 任何在
shell
中可以启动的程序,都可以在python中通过**管道对象(pipe object)**启动;- 一个管道代表一个正在运行的程序;
# Unix命令'ls -l'以详细格式显示当前目录下的内容
>>> cmd = 'ls -l'
>>> fp = os.popen(cmd)
# 返回值是一个行为类似已打开文件的对象
>>> res = fp.read()
# 关闭管道
>>> stat = fp.close()
>>> print(stat)
None
md5sum
可以读取一个文件的内容,计算一个校验和:
>>> filename = 'book.tex'
>>> cmd = 'md5sum ' + filename
>>> fp = os.popen(cmd)
>>> res = fp.read()
>>> stat = fp.close()
>>> print(res)
1e0033f0ed0656636de0d75144ba32e0 book.tex
>>> print(stat)
None
(九)编写模块
任何包含python代码的文件,都可以作为模块导入
- 有一个
wc.py
文件:
def linecount(filename):
count = 0
for line in open(filename):
count += 1
return count
print(linecount('wc.py'))
- 现在有一个模块
wc
:
>>> wc
<module 'wc' from 'wc.py'>
- 这个模块对象提供了
linecount
函数:
>>> wc.linecount('wc.py')
7
- 现在这个模块的问题在于,当导入这个模块之后,将自动运行到后面的测试代码;通常当导入一个模块的时候,将定义一些新的函数,但是并不运行它们;
- 作为模块的程序通常是:
if __name__ == '__main__':
print(linecount('wc.py'))
__name__
是一个在程序开始时设置到内建变量;- 如果程序以脚本的形式运行,
__name__
的值为__main__
,这时候其中的代码被执行; - 否则,当作为模块导入的时候,其中的代码将会被跳过;
- 如果程序以脚本的形式运行,
- 如果你导入一个已经被导入了的模块,Python 将不会做任何事情。它并不会重新读取文件,即使文件的内容已经发生了改变;
- 如果你要重载一个模块,可以使用内建函数
reload
,但它可能会出错。因此最安全的方法是重启解释器,然后重新导入模块。
- 如果你要重载一个模块,可以使用内建函数
(十)调试
读写文件的时候,可能会遇到空格带来的问题;这个问题难以解决是因为这些空格、制表符、换行符是不可见的;
- 使用内建函数
repr
可以解决这个问题:
>>> s = '1 2\t 3\n 4'
>>> print(s)
1 2 3
4
>>> print(repr(s))
'1 2\t 3\n 4'