python-10 文件

2020-04-13  本文已影响0人  巴巴11

读取和写入

要写入一个文件,你必须在打开文件时设置第二个参数来为 'w' 模式:
>>> 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

完成文件写入后,你应该关闭文件。
>>> fout.close()
如果你不关闭这个文件,程序结束时它才会关闭。

格式化运算符
write的参数必须是字符串,所以如果想要在文件中写入其它值, 我们需要先将它们转换为字符串。最简单的法是使用 str :

>>> x = 52
>>> fout.write(str(x))

另一个方法是使用 格式化运算符(format operator) ,即 %。 作用于整数时,% 是取模运算符,而当第一个运算数是字符串时,% 则是格式化运算符。

第一个运算数是 格式化字符串(format string) ,它包含一个或多个 格式化序列(format sequence) 。格式化序列指定了第二个运算数是如何格式化的。运算结果是一个字符串。

例如,格式化序列 '%d' 意味着第二个运算数应该被格式化为一个十进制整数:
>>> camels = 42
>>> '%d' % camels
'42'
结果是字符串 '42' ,需要和整数值 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
在第一个例子中,元组中没有足够的元素;在第二个例子中,元素的类型错误。

文件名和路径

文件以 **目录(directory)** (也称为“文件夹(folder)”)的形式组织起来。 每个正在运行的程序都有一个“当前目录(current directory)”作为大多数操作的默认目录。 例如,当你打开一个文件来读取时,Python 会在当前目录下寻找这个文件。

`os`模块提供了操作文件和目录的函数(“os”代表“operating system”)。`os.getcwd` 返回当前目录的名称:
>>> import os
>>> cwd = os.getcwd()
>>> cwd
'/home/dinsdale'
</pre>

`cwd`代表“current working directory”,即“当前工作目录”。 在本例中,返回的结果是 `/home/dinsdale` ,即用户名为 `dinsdale` 的主目录。

类似 `'/home/dinsdale'` 这样的字符串指明一个文件或者目录,叫做 **路径(path)** 。

一个简单的文件名,如 `memo.txt` ,同样被看做是一个路径,只不过是 **相对路径(relative path)** ,因为它是相对于当前目录而言的。如果当前目录是 `/home/dinsdale` ,那么文件名 `memo.txt` 就代表 `/home/dinsdale/memo.txt` 。

一个以 `/` 开头的路径和当前目录无关,叫做 **绝对路径(absolute path)**。要获得一个文件的绝对路径,你可以使用 `os.path.abspath` :
>>> os.path.abspath('memo.txt')
'/home/dinsdale/memo.txt'


`os.path`还提供了其它函数来对文件名和路径进行操作。例如,`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模块提供了一个叫做 `walk` 的函数,和我们上面写的类似,但是功能更加更富。

## 捕获异常
试图读写文件时,很多地方可能会发生错误。如果你试图打开一个不存在的文件夹, 会得到一个输入输出错误(IOError):

>>> fin = open('bad_file')
IOError: [Errno 2] No such file or directory: 'bad_file'

如果你没有权限访问一个文件:
>>> fout = open('/etc/passwd', 'w')
PermissionError: [Errno 13] Permission denied: '/etc/passwd'


如果你试图打开一个目录来读取,你会得到:
>>> fin = open('/home')
IsADirectoryError: [Errno 21] Is a directory: '/home'

为了避免这些错误,你可以使用类似 `os.path.exists` 和 `os.path.isfile` 的函数来检查,但这将会耗费大量的时间和代码去检查所有的可能性(从“Errno 21”这个错误信息来看,至少有21种可能出错的情况)。

更好的办法是在问题出现的时候才去处理,而这正是 `try` 语句做的事情。 它的语法类似 `if...else` 语句:

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

Python 从 `try` 子句(clause)开始执行。 如果一切正常,那么 `except` 子句将被跳过。 如果发生异常,则跳出 `try` 子句,执行 `except` 子句。

使用 `try` 语句处理异常被称为是 **捕获(catching)** 异常。 在本例中,`except` 子句打印出一个并非很有帮助的错误信息。 一般来说,捕获异常后你可以选择是否解决这个问题,或者继续尝试运行,又或者至少优雅地结束程序。

数据库
数据库是一个用来存储数据的文件。 大多数的数据库采用类似字典的形式,即将键映射到值。 数据库和字典的最大区别是,数据库是存储在硬盘上(或者其他永久存储中), 所以即使程序结束,它们依然存在。

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 开头。 一个字节对象在很多方面都和一个字符串很像。但是当你深入了解 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])

与其它文件一样,当你完成操作后需要关闭文件:
>>> db.close()

序列化

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.loads (“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 将非字符串对象存储在数据库中。 事实上,这个组合非常常用,已经被封装进了模块 shelve 中。

管道

任何可以在shell中启动的程序,也可以在 Python 中通过使用 管道对象(pipe object) 来启动。一个管道代表着一个正在运行的程序。

例如,Unix 命令 ls -l 将以详细格式显示当前目录下的内容。 你可以使用 os.popen 来启动 ls :

>>> cmd = 'ls -l'
>>> fp = os.popen(cmd)
实参是一个包含shell命令的字符串。返回值是一个行为类似已打开文件的对象。 你可以使用 readline 来每次从 ls 进程的输出中读取一行,或者使用 read 来一次读取所有内容:

>>> res = fp.read()
当你完成操作后,像关闭一个文件一样关闭管道:

>>> stat = fp.close()
>>> print(stat)
None
返回值是 ls 进程的最终状态。None 表示正常结束(没有出现错误)。

例如,大多数 Unix 系统提供了一个叫做 md5sum 的命令,来读取一个文件的内容并计算出一个“校验和(checksum)”。

你可以使用一个管道来从 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

编写模块
任何包含 Python 代码的文件,都可以作为模块被导入。

作为模块的程序通常写成以下结构:

if __name__ == '__main__':
    print(linecount('wc.py'))
__name__是一个在程序开始时设置好的内建变量。 如果程序以脚本的形式运行,__name__ 的值为 __main__ ,这时其中的代码将被执行。否则当被作为模块导入时,其中的代码将被跳过。

我们做个练习,将例子输入到文件 wc.py 中,然后以脚本形式运行它。 接着,打开 Python 解释器并导入 wc 。当模块被导入后, __name__ 的值是什么?

警示:如果你导入一个已经被导入了的模块,Python 将不会做任何事情。它并不会重新读取文件,即使文件的内容已经发生了改变。

如果你要重载一个模块,可以使用内建函数 reload ,但它可能会出错。因此最安全的方法是重启解释器,然后重新导入模块。

内置变量
name

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

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

wc1.py
import wc

wc.lineCount('wc1.py')
print("hello world\n")


上一篇下一篇

猜你喜欢

热点阅读