【练习题】第十四章--文件(Think Python)

2.读写文件

要写入一个文件,就必须要在打开它的时候用『w』作为第二个参数(译者注:w 就是 wirte 的意思了):

>>> fout = open('output.txt', 'w')

如果文件已经存在了,这样用写入的模式来打开,会把旧的文件都清除掉,然后重新写入文件,所以一定要小心!如果文件不存在,程序就会创建一个新的。

open 函数会返回一个文件对象,文件对象会提供各种方法来处理文件。write 这个方法就把数据写入到文件中了。

>>> line1 = "This here's the wattle,\n"
>>> fout.write(line1)
24

返回值是已写入字符的数量。文件对象会记录所在位置,所以如果你再次调用write方法,会从文件结尾的地方继续添加新的内容。

>>> line2 = "the emblem of our land.\n"
>>> fout.write(line2)
24

写完文件之后,你需要用 close 方法来关闭文件。

>>> fout.close()

如果不 close 这个文件,就要等你的程序运行结束退出的时候,它自己才关闭了。

3.格式运算符

>>> camels = 42
>>> '%d' % camels
'42'

如果格式化序列有一个以上了,那么第二个参数就必须是一个元组了。每个格式序列对应元组当中的一个元素,次序相同。

下面的例子中,用了'%d'来格式化输出整型值,用'%g'来格式化浮点数,'%s'就是给字符串用的了。

>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'

这就要注意力,如果字符串中格式化序列有多个,那个数一定要和后面的元组中元素数量相等才行。另外格式化序列与元组中元素的类型也必须一样。

4.文件名与路径

>>> import os
>>> cwd = os.getcwd()
>>> cwd
'/home/dinsdale'
>>> os.path.abspath('memo.txt')
 '/home/dinsdale/memo.txt'
>>> os.path.exists('memo.txt')
True
>>> os.path.isdir('memo.txt')
False
>>> os.path.isdir('/home/dinsdale')
True
>>> os.listdir(cwd)
['music', 'photos', 'memo.txt']

为了展示一下这些函数的用法,下面这个例子中,walks 这个函数就遍历了一个目录,然后输出了所有该目录下的文件的名字,并且在该目录下的所有子目录中递归调用自身。

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)

 5.捕获异常

try:
    fin = open('bad_file')
except:
    print('Something went wrong.')

Python 会先执行 try 后面的语句。如果运行正常,就会跳过 except 语句,然后继续运行。如果除了异常,就会跳出 try 语句,然后运行 except 语句中的代码。

这种用 try 语句来处理异常的方法,就叫异常捕获。上面的例子中,except 语句中的输出信息并没有什么用。一般情况,得到异常之后,你可以选择解决掉这个问题或者再重试一下,或者就以正常状态退出程序了。

6.数据库

数据库是一个用来管理已存储数据的文件。很多数据库都以类似字典的形式来管理数据,就是从键到键值成对映射。数据库和字典的最大区别就在于数据库是存储在磁盘(或者其他永久性存储设备中),所以程序运行结束退出后,数据库依然存在。

(译者注:这里作者为了便于理解,对数据库的概念进行了极度的简化,实际上数据库的类型、模式、功能等等都与字典有很大不同,比如有关系型数据库和非关系型数据库,还有分布式的和单一文件式的等等。如果有兴趣对数据库进行进一步了解,译者推荐一本书:SQLite Python Tutorial。)

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.'

上面的代码返回的结果是一个二进制对象,这也就是开头有个 b 的原因了。二进制对象就跟字符串在很多方面都挺像的。以后对 Python 的学习深入了之后,这种区别就变得很重要了,不过现在还不要紧,咱们就忽略掉。

如果对一个已经存在值的键进行赋值,dbm 就会把旧的值替换成新的值:

>>> 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])

然后就同其他文件一样,用完了之后你得用 close 方法关闭数据库:

>>> db.close()

 7.Pickle模块

dbm 的局限就在于键和键值必须是字符串或者二进制。如果用其他类型数据,就得到错误了。

这时候就可以用 pickle 模块了。该模块可以把几乎所有类型的对象翻译成字符串模式,以便存储在数据库中,然后用的时候还可以把字符串再翻译回来。

pickle.dumps 接收一个对象做参数,然后返回一个字符串形式的内容翻译(dumps 就是『dump string』的缩写):

>>> import pickle
>>> t = [1, 2, 3]
>>> pickle.dumps(t)
b'\x80\x03]q\x00(K\x01K\x02K\x03e.'

这种格式让人读起来挺复杂;这种设计能让 pickle 模块解译起来比较容易。pickle.lods("load string")就又会把原来的对象解译出来:

>>> t1 = [1, 2, 3]
>>> s = pickle.dumps(t1)
>>> t2 = pickle.loads(s)
>>> t2
[1, 2, 3]

这里要注意了,新的对象与旧的有一样的值,但(通常)并不是同一个对象:

>>> t1 == t2
True
>>> t1 is t2
False

换句话说,就是说 pickle 解译的过程就如同复制了原有对象一样。

有 pickle了,就可以把非字符串的数据也存到数据库里面了。实际上这种结合方式特别普遍,已经封装到一个叫shelve的模块中了。

8.管道

大多数操作系统都提供了一个命令行接口,也被称作『shell』。Shell 通常提供了很多基础的命令,能够来搜索文件系统,以及启动应用软件。比如,在 Unix 下面,就可以通过 cd 命令来切换目录,用 ls 命令来显示一个目录下的内容,如果装了火狐浏览器,就可以输入 fireforx 来启动浏览器了。

在 shell 下能够启动的所有程序,也都可以在 Python 中启动,这要用到一个 pipe 对象,这个直接翻译意思为管道的对象可以理解为 Python 到操作系统的 Shell 进行通信的途径,一个 pipe 对象就代表了一个运行的程序。

举个例子吧,Unix 的 ls -l 命令通常会用长文件名格式来显示当前目录的内容。在 Python 中就可以用 os.open 来启动它:

>>> cmd = 'ls -l'
>>> fp = os.popen(cmd)

参数 cmd 是包含了 shell 命令的一个字符串。返回的结果是一个对象,用起来就像是一个打开了的文件一样。

可以读取ls 进程的输出,用 readline 的话每次读取一行,用 read 的话就一次性全部读取:

>>> res = fp.read()

用完之后要关闭,这点也跟文件一样:

>>> stat = fp.close()
>>> print(stat)
None

返回值是 ls 这个进程的最终状态;None 的意思就是正常退出(没有错误)。

举个例子,大多数 Unix 系统都提供了一个教唆 md5sum 的函数,会读取一个文件的内容,然后计算一个『checksum』(校验值)。你可以点击这里阅读更多相关内容。

这个命令可以很有效地检查两个文件是否有相同内容。两个不同内容产生同样的校验值的可能性是很小的(实际上在宇宙坍塌之前都没戏)。

你就可以用一个 pipe 来从 Python 启动运行 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

9.编写模块

任何包含 Python 代码的文件都可以作为模块被导入使用。举个例子,假设你有一个名字叫 wc.py 的文件,里面代码如下:

def linecount(filename):
    count = 0
    for line in open(filename):
        count += 1
    return count
print(linecount('wc.py'))

如果运行这个程序,程序就会读取自己本身,然后输出文件中的行数,也就是7行了。你还可以导入这个模块,如下所示:

>>> import wc
7

现在你就有一个模块对象 wc 了:

>>> wc
<module 'wc' from 'wc.py'>

该模块提供了数行数的函数linecount:

>>> wc.linecount('wc.py')
7

你看,你就可以这样来为 Python 写模块了。

当然这个例子中有个小问题,就是导入模块的时候,模块内代码在最后一行对自身进行了测试。

一般情况你导入一个模块,模块只是定义了新的函数,但不会去主动运行自己内部的函数。

以模块方式导入使用的程序一般用下面这样的惯用形式:

if __name__ == '__main__':
    print(linecount('wc.py'))

name 是一个内置变量,当程序开始运行的时候被设置。如果程序是作为脚本来运行的,name 的值就是'main';这样的话,if条件满足,测试代码就会运行。而如果该代码被用作模块导入了,if 条件不满足,测试的代码就不会运行了。

10.调试

读写文件的时候,你可能会碰到空格导致的问题。这些问题很难解决,因为空格、跳表以及换行,平常就难以用眼睛看出来:

>>> s = '1 2\t 3\n 4'
>>> print(s)
1 2  3
4

这时候就可以用内置函数 repr 来帮忙。它接收任意对象作为参数,然后返回一个该对象的字符串表示。对于字符串,该函数可以把空格字符转成反斜杠序列:

>>> print(repr(s))
'1 2\t 3\n 4'

该函数的功能对调试来说很有帮助。

另外一个问题就是不同操作系统可能用不同字符表示行尾。

有的用一个换行符,也就是\n。有的用一个返回字符,也就是\r。有的两个都亏。如果你把文件在不同操作系统只见移动,这种不兼容性就可能导致问题了。

对大多数操作系统,都有一些应用软件来进行格式转换。你可以在https://en.wikipedia.org/wiki/Newline查找一下(并且阅读关于该问题的更多细节)。当然,你也可以自己写一个转换工具了。

练习1:

写一个函数,名为 sed,接收一个目标字符串,一个替换字符串,然后两个文件名;读取第一个文件,然后把内容写入到第二个文件中,如果第二个文件不存在,就创建一个。如果目标字符串在文件中出现了,就用替换字符串把它替换掉。

如果在打开、读取、写入或者关闭文件的时候发生了错误了,你的程序应该要捕获异常,然后输出错误信息,然后再退出。

def sed(s1,s2,f1,f2):
    try:       
        fin=open(f1,'r')
        fout=open(f2,'w')
    except:
        print('openError!')
    for s in fin:
        try:
            if(s.strip()==s1):
                fout.write(s2+'\r\n')
            else:
                fout.write(s)
        except:
            print('writeError!')
    fin.close()
    fout.close()

def main():
    s1='aa'
    s2='bbb'
    f1='words.txt'
    f2='words2.txt'
    sed(s1,s2,f1,f2)

if __name__=='__main__':
    main()

练习2:

如果你从 这里下载了我的样例代码,你会发现该程序创建了一个字典,建立了从一个有序字母字符串到一个单词列表的映射,列表中的单词可以由这些字母拼成。例如'opst'就映射到了列表 [’opts’, ’post’, ’pots’, ’spot’, ’stop’, ’tops’].

写一个模块,导入 anagram_sets 然后提供两个函数:store_anagrams 可以把相同字母异序词词典存储到一个『shelf』;read_anagrams 可以查找一个词,返回一个由其 相同字母异序词 组成的列表。

猜你喜欢

转载自blog.csdn.net/qq_29567851/article/details/83510728