黑马python培训班 费用用open打开文件读写,“w”会替换,“a”多次调用函数会重复

您所在的位置: &
Python学习(九)IO 编程 ―― 文件读写
时间: 编辑:feesland 来源:本站整理
Python&文件读写
  Python内置了读写文件的函数,用法和C是兼容的。本节介绍内容大致有:文件的打开/关闭、文件对象、文件的读写等。
  open()&& close()&&&& with open(...) as ...
    看以下示例就能了解 Python&的 open()&及&close()&函数。这边调用 read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示,具体使用参见下文。
    在 E 盘 python_file 文件夹下新建一 a.txt,输入随意,如下:
      
    Python 操作 打开及关闭方式 如下:
      
    注意 open() 之后 一定要 close()。但由于文件读写时都可能产生IOError,为了保证无论是否出错都能正确地关闭文件,我们用 try ... finally 来实现:
      
    python 简化了改写法,即用 with open(...) as ...& ; 建议之后文件读写都用该写法:
    上面,你肯定注意到了参数 "r";该参数决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。
以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
打开一个文件用于读写。文件指针将会放在文件的开头。
以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
  File 对象
    file 为一对象,它有一些内置属性,如下
      
文件对象属性描 述
file.closed
表示文件已经被关闭,否则为False
Access文件打开时使用的访问模式
file.encoding
文件所使用的编码
file.newlines
未读取到行分隔符时为None,只有一种行分隔符时为一个字符串,当文件有多种类型的行结束符时,则为一个包含所有当前所遇到的行结束的列表
file.softspace
为0表示在输出一数据后,要加上一个空格符,1表示不加。这个属性一般程序员用不着,由程序内部使用
  read()&&& read(size)&& readline()&& readlines()
    之前的例子已经接触到了 read() 函数,该函数会会一次性读取文件的全部内容,如果能确保文件的大小,自然可以。但若文件过大,内存就爆了,所以,可以反复调用read(size)方法,每次最多读取size个字节的内容;也可调用 readline() 每次读取一行内容;而调用readlines()可以一次读取所有内容并按行返回list。总之,根据需求来。仅以 txt 文件为例,其他的文件读取需要特殊处理;另外,文件的格式编码方式也需要注意;这边仅介绍读取方法,其他的会出专题来学习。
    在D:\python_file 下新建 poet.txt;示例如下,由于一个中文会占多个字节,故read(size) 部分会乱码,如:
  write()
    写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件;'a' 对应的表示追加等。
    如下示例,由于 write.txt 文件不存在,创建该文件并写入:
    在上例基础上,继续,该文件被重写:
    继续,这次使用追加,会在文件结尾追加:
热门关键字7. 输入和输出 & Python tutorial 3.6.3 documentation使用 Python 进行稳定可靠的文件操作 - 文章 - 伯乐在线
& 使用 Python 进行稳定可靠的文件操作
程序需要更新文件。虽然大部分程序员知道在执行I/O的时候会发生不可预期的事情,但是我经常看到一些异常幼稚的代码。在本文中,我想要分享一些如何在Python代码中改善I/O可靠性的见解。
考虑下述Python代码片段。对文件中的数据进行某些操作,然后将结果保存回文件中:
with open(filename) as f:
input = f.read()
output = do_something(input)
with open(filename, 'w') as f:
f.write(output)
with open(filename) as f:&&&&input = f.read()output = do_something(input)with open(filename, 'w') as f:&&&&f.write(output)
看起来很简单吧?可能看起来并不像乍一看这么简单。我在产品服务器中调试应用,经常会出现奇怪的行为。
这是我看过的失效模式的例子:
失控的服务器进程溢出大量日志,磁盘被填满。write()在截断文件之后抛出异常,文件将会变成空的。
应用的几个实例并行执行。在各个实例结束之后,因为混合了多个实例的输出,文件内容最终变成了天书。
在完成了写操作之后,应用会触发一些后续操作。几秒钟后断电。在我们重启了服务器之后,我们再一次看到了旧的文件内容。已经传递给其它应用的数据与我们在文件中看到的不再一致。
下面没有什么新的内容。本文的目的是为在系统编程方面缺少经验的Python开发者提供常见的方法和技术。我将会提供代码例子,使得开发者可以很容易的将这些方法应用到自己的代码中。
“可靠性”意味着什么?
广义的讲,可靠性意味着在所有规定的条件下操作都能执行它所需的函数。至于文件的操作,这个函数就是创建,替换或者追加文件的内容的问题。这里可以从数据库理论上获得灵感。经典的事务模型的ACID性质作为指导来提高可靠性。
开始之前,让我们先看看我们的例子怎样和ACID4个性质扯上关系:
原子性(Atomicity)要求这个事务要么完全成功,要么完全失败。在上面的实例中,磁盘满了可能导致部分内容写入文件。另外,如果正当在写入内容时其它程序又在读取文件,它们可能获得是部分完成的版本,甚至会导致写错误
一致性(Consistency)?表示操作必须从系统的一个状态到另一个状态。一致性可以分为两部分:内部和外部一致性。内部一致性是指文件的数据结构是一致的。外部一致性是指文件的内容与它相关的数据是相符合的。在这个例子中,因为我们不了解这个应用,所以很难推断是否符合一致性。但是因为一致性需要原子性,我们至少可以说没有保证内部一致性。
隔离性(Isolation)如果在并发的执行事务中,多个相同的事务导致了不同的结果,就违反了隔离性。很明显上面的代码对操作失败或者其它都没有保护。
持久性(Durability)意味着改变是持久不变的。在我们告诉用户成功之前,我们必须确保我们的数据存储是可靠的并且不只是一个写缓存。上面的代码已经成功写入数据的前提是假设我们调用write()函数,磁盘I/O就立即执行。但是POSIX标准是不保证这个假设的。
尽可能使用数据库系统
如果我们能够获得ACID 四个性质,那么我们增加可靠性方面取得了长远发展。但是这需要很大的编码功劳。为什么重复发明轮子?大多数数据库系统已经有ACID事务了。
可靠性数据存储已经是一个已解决的问题。如果你需要可靠性存储,请使用数据库。很可能,没有几十年的功夫,你自己解决这方面的能力没有那些已经专注这方面好些年的人好。如果你不想安装一个大数据库服务器,那么你可以使用,它具有ACID事务,很小,免费的,而且它包含在Python的中。
文章本该在这里就结束的,但是还有一些有根有据的原因,就是不使用数据。它们通常是文件格式或者文件位置约束。这两个在数据库系统中都不好控制。理由如下:
我们必须处理其它应用产生的固定格式或者在固定位置的文件,
我们必须为了其它应用的消耗而写文件(和应用了同样的限制条件)
我们的文件必须方便人阅读或者修改。
…等等。你懂的。
如果我们自己动手实现可靠的文件更新,那么这里有一些编程技术供参考。下面我将展示四种常见的操作文件更新模式。在那之后,我会讨论采取哪些步骤在每个文件更新模式下满足ACID性质。
文件更新模式
文件可以以多种方式更新,但是我认为至少有四种常见的模式。这四种模式将做为本文剩余部分的基础。
这可能是最基本的模式。在下述例子中,假设的域模型代码读数据,执行一些计算,然后以写模式重新打开存在的文件:
with open(filename, 'r') as f:
model.read(f)
model.process()
with open(filename, 'w') as f:
model.write(f)
with open(filename, 'r') as f:&& model.read(f)model.process()with open(filename, 'w') as f:&& model.write(f)
此模式的一个变种以读写模式打开文件(Python中的“加”模式),寻找到开始的位置,显式调用truncate(),重写文件内容。
with open(filename, 'a+') as f:
model.input(f.read())
model.compute()
f.truncate()
f.write(model.output())
with open(filename, 'a+') as f:&& f.seek(0)&& model.input(f.read())&& model.compute()&& f.seek(0)&& f.truncate()&& f.write(model.output())
该变种的优势是只打开文件一次,始终保持文件打开。举例来说,这样可以简化加锁。
另外一种广泛使用的模式是将新内容写到临时文件,之后替换原始文件:
with tempfile.NamedTemporaryFile(
'w', dir=os.path.dirname(filename), delete=False) as tf:
tf.write(model.output())
tempname = tf.name
os.rename(tempname, filename)
with tempfile.NamedTemporaryFile(&&&&&&'w', dir=os.path.dirname(filename), delete=False) as tf:&& tf.write(model.output())&& tempname = tf.nameos.rename(tempname, filename)
该方法与截断-写方法相比对错误更具有鲁棒性。请看下面对原子性和一致性的讨论。很多应用使用该方法。
这两个模式很常见,以至于linux内核中的ext4文件系统甚至可以自动检测到这些模式,自动修复一些可靠性缺陷。但是不要依赖这一特性:你并不是总是使用ext4,而且管理员可能会关掉这一特性。
第三种模式就是追加新数据到已存在的文件:
with open(filename, 'a') as f:
f.write(model.output())
with open(filename, 'a') as f:&& f.write(model.output())
这个模式用来写日志文件和其它累积处理数据的任务。从技术上讲,它的显著特点就是极其简单。一个有趣的扩展应用就是常规操作中只通过追加操作更新,然后定期重新整理文件,使之更紧凑。
这里我们将目录做为逻辑数据存储,为每条记录创建新的唯一命名的文件:
with open(unique_filename(), 'w') as f:
f.write(model.output())
with open(unique_filename(), 'w') as f:&& f.write(model.output())
该模式与附加模式一样具有累积的特点。一个巨大的优势是我们可以在文件名中放入少量元数据。举例来说,这可以用于传达处理状态的信息。spooldir模式的一个特别巧妙的实现是maildir格式。maildirs使用附加子目录的命名方案,以可靠的、无锁的方式执行更新操作。md和gocept.filestore库为maildir操作提供了方便的封装。
如果你的文件名生成不能保证唯一的结果,甚至有可能要求文件必须实际上是新的。那么调用具有合适标志的低等级os.open():
fd = os.open(filename, os.O_WRONLY | os.O_CREAT| os.O_EXCL, 0o666)
with os.fdopen(fd, 'w') as f:
f.write(...)
fd = os.open(filename, os.O_WRONLY | os.O_CREAT| os.O_EXCL, 0o666)with os.fdopen(fd, 'w') as f:&& f.write(...)
在以O_EXCL方式打开文件后,我们用os.fdopen将原始的文件描述符转化为普通的Python文件对象。
应用ACID属性到文件更新
下面,我将尝试加强文件更新模式。反过来让我们看看可以做些什么来满足ACID属性。我将会尽可能保持简单,因为我们并不是要写一个完整的数据库系统。请注意本节的材料并不彻底,但是可以为你自己的实验提供一个好的起点.
写-替换模式提供了原子性,因为底层的os.rename()。这意味着在任意给定时间点,进程或者看到旧的文件,或者看到新的文件。该模式对写错误具有天然的鲁棒性:如果写操作触发异常,重命名操作就不会被执行,所有就没有用损坏的新文件覆盖正确的旧文件的风险。
附加模式并不是原子性的,因为有附加不完整记录的风险。但是有个技巧可以使更新具有原子性:为每个写操作标注校验和。之后读日志的时候,忽略所有没有有效校验和的记录。以这种方式,只有完整的记录才会被处理。在下面的例子中,应用做周期性的测量,每次在日志中附加一行JSON记录。我们计算记录的字节表示形式的CRC32校验和,然后附加到同一行:
with open(logfile, 'ab') as f:
for i in range(3):
measure = {'timestamp': time.time(), 'value': random.random()}
record = json.dumps(measure).encode()
checksum = '{:8x}'.format(zlib.crc32(record)).encode()
f.write(record + b' ' + checksum + b'\n')
with open(logfile, 'ab') as f:&&&&for i in range(3):&&&&&&&&measure = {'timestamp': time.time(), 'value': random.random()}&&&&&&&&record = json.dumps(measure).encode()&&&&&&&&checksum = '{:8x}'.format(zlib.crc32(record)).encode()&&&&&&&&f.write(record + b' ' + checksum + b'\n')
该例子代码通过每次创建随机值模拟测量。
{"timestamp": .258189, "value": 0.b87a
{"timestamp": .25825, "value": 0.9afc22
{"timestamp": .258291, "value": 0.939} d229d937
$ cat log{"timestamp": .258189, "value": 0.7828} 9495b87a{"timestamp": .25825, "value": 0.99424} 149afc22{"timestamp": .258291, "value": 0.939} d229d937
想要处理这个日志文件,我们每次读一行记录,分离校验和,与读到的记录比较。
with open(logfile, 'rb') as f:
for line in f:
record, checksum = line.strip().rsplit(b' ', 1)
if checksum.decode() == '{:8x}'.format(zlib.crc32(record)):
print('read measure: {}'.format(json.loads(record.decode())))
print('checksum error for record {}'.format(record))
with open(logfile, 'rb') as f:&&&&for line in f:&&&&&&&&record, checksum = line.strip().rsplit(b' ', 1)&&&&&&&&if checksum.decode() == '{:8x}'.format(zlib.crc32(record)):&&&&&&&&&&&&print('read measure: {}'.format(json.loads(record.decode())))&&&&&&&&else:&&&&&&&&&&&&print('checksum error for record {}'.format(record))
现在我们通过截断最后一行模拟被截断的写操作:
{"timestamp": .258189, "value": 0.b87a
{"timestamp": .25825, "value": 0.9afc22
{"timestamp": .258291, "value": 0.23202
$ cat log{"timestamp": .258189, "value": 0.7828} 9495b87a{"timestamp": .25825, "value": 0.99424} 149afc22{"timestamp": .258291, "value": 0.23202
当读日志的时候,最后不完整的一行被拒绝:
$ read_checksummed_log.py log
read measure: {'timestamp': .258189, 'value': 0.7828}
read measure: {'timestamp': .25825, 'value': 0.99424}
checksum error for record b'{"timestamp": .258291, "value":'
$ read_checksummed_log.py logread measure: {'timestamp': .258189, 'value': 0.7828}read measure: {'timestamp': .25825, 'value': 0.99424}checksum error for record b'{"timestamp": .258291, "value":'
添加校验和到日志记录的方法被用于大量应用,包括很多数据库系统。
spooldir中的单个文件也可以在每个文件中添加校验和。另外一个可能更简单的方法是借用写-替换模式:首先将文件写到一边,然后移到最终的位置。设计一个保护正在被消费者处理的文件的命名方案。在下面的例子中,所有以.tmp结尾的文件都会被读取程序忽略,因此在写操作的时候可以安全的使用。
newfile = generate_id()
with open(newfile + '.tmp', 'w') as f:
f.write(model.output())
os.rename(newfile + '.tmp', newfile)
newfile = generate_id()with open(newfile + '.tmp', 'w') as f:&& f.write(model.output())os.rename(newfile + '.tmp', newfile)
最后,截断-写是非原子性的。很遗憾我不能提供满足原子性的变种。在执行完截取操作后,文件是空的,还没有新内容写入。如果并发的程序现在读文件或者有异常发生,程序中止,我们既看不久的版本也看不到新的版本。
我谈论的关于原子性的大部分内容也可以应用到一致性。实际上,原子性更新是内部一致性的前提条件。外部一致性意味着同步更新几个文件。这不容易做到,锁文件可以用来确保读写访问互不干涉。考虑某目录下的文件需要互相保持一致。常用的模式是指定锁文件,用来控制对整个目录的访问。
写程序的例子:
with open(os.path.join(dirname, '.lock'), 'a+') as lockfile:
fcntl.flock(lockfile, fcntl.LOCK_EX)
model.update(dirname)
with open(os.path.join(dirname, '.lock'), 'a+') as lockfile:&& fcntl.flock(lockfile, fcntl.LOCK_EX)&& model.update(dirname)
读程序的例子:
with open(os.path.join(dirname, '.lock'), 'a+') as lockfile:
fcntl.flock(lockfile, fcntl.LOCK_SH)
model.readall(dirname)
with open(os.path.join(dirname, '.lock'), 'a+') as lockfile:&& fcntl.flock(lockfile, fcntl.LOCK_SH)&& model.readall(dirname)
该方法只有控制所有读程序才生效。因为每次只有一个写程序活动(独占锁阻塞所有共享锁),所有该方法的可扩展性有限。
更进一步,我们可以对整个目录应用写-替换模式。这涉及为每次更新创建新的目录,更新完成后改变符合链接。举例来说,镜像应用维护一个包含压缩包和列出了文件名、文件大小和校验和的索引文件的目录。当上流的镜像更新,仅仅隔离地对压缩包和索引文件进项原子性更新是不够的。相反,我们需要同时提供压缩包和索引文件以免校验和不匹配。为了解决这个问题,我们为每次生成维护一个子目录,然后改变符号链接激活该次生成。
`-- index.json
`-- index.json
`-- current -& 483
1234567891011
mirror|-- 483|&& |-- a.tgz|&& |-- b.tgz|&& `-- index.json|-- 484|&& |-- a.tgz|&& |-- b.tgz|&& |-- c.tgz|&& `-- index.json`-- current -& 483
新的生成484正在被更新的过程中。当所有压缩包准备好,索引文件更新后,我们可以用一次原子调用os.symlink()来切换current符号链接。其它应用总是或者看到完全旧的或者完全新的生成。读程序需要使用os.chdir()进入current目录,很重要的是不要用完整路径名指定文件。否在当读程序打开current/index.json,然后打开current/a.tgz,但是同时符号链接已经改变时就会出现竞争条件。
隔离性意味着对同一文件的并发更新是可串行化的——存在一个串行调度使得实际执行的并行调度返回相同的结果。“真实的”数据库系统使用像这种高级技术维护可串行性,同时允许高等级的可并行性。回到我们的场景,我们最后使用加锁来串行文件更新。
对截断-写更新进行加锁是容易的。仅仅在所有文件操作前获取一个独占锁就可以。下面的例子代码从文件中读取一个整数,然后递增,最后更新文件:
def update():
with open(filename, 'r+') as f:
fcntl.flock(f, fcntl.LOCK_EX)
n = int(f.read())
f.truncate()
f.write('{}\n'.format(n))
def update(): with open(filename, 'r+') as f: fcntl.flock(f, fcntl.LOCK_EX) n = int(f.read()) n += 1 f.seek(0) f.truncate()f.write('{}\n'.format(n))
使用写-替换模式加锁更新就有点儿麻烦啦。像?截断-写那样使用锁可能导致更新冲突。某个幼稚的实现可能看起来像这样:
def update():
with open(filename) as f:
fcntl.flock(f, fcntl.LOCK_EX)
n = int(f.read())
with tempfile.NamedTemporaryFile(
'w', dir=os.path.dirname(filename), delete=False) as tf:
tf.write('{}\n'.format(n))
tempname = tf.name
os.rename(tempname, filename)
12345678910
def update():&& with open(filename) as f:&&&&&&fcntl.flock(f, fcntl.LOCK_EX)&&&&&&n = int(f.read())&&&&&&n += 1&&&&&&with tempfile.NamedTemporaryFile(&&&&&&&&&&&&'w', dir=os.path.dirname(filename), delete=False) as tf:&&&&&&&& tf.write('{}\n'.format(n))&&&&&&&& tempname = tf.name&&&&&&os.rename(tempname, filename)
这段代码有什么问题呢?设想两个进程竞争更新某个文件。第一个进程运行在前面,但是第二个进程阻塞在fcntl.flock()调用。当第一个进程替换了文件,释放了锁,现在在第二个进程中打开的文件描述符指向了一个包含旧内容的“幽灵”文件(任意路径名都不可达)。想要避免这个冲突,我们必须检查打开的文件是否与fcntl.flock()返回的相同。所以我写了一个新的LockedOpen上下文管理器来替换内建的open上下文。来确保我们实际打开了正确的文件:
class LockedOpen(object):
def __init__(self, filename, *args, **kwargs):
self.filename = filename
self.open_args = args
self.open_kwargs = kwargs
self.fileobj = None
def __enter__(self):
f = open(self.filename, *self.open_args, **self.open_kwargs)
while True:
fcntl.flock(f, fcntl.LOCK_EX)
fnew = open(self.filename, *self.open_args, **self.open_kwargs)
if os.path.sameopenfile(f.fileno(), fnew.fileno()):
fnew.close()
self.fileobj = f
def __exit__(self, _exc_type, _exc_value, _traceback):
self.fileobj.close()
123456789101112131415161718192021222324
class LockedOpen(object):&&&&&def __init__(self, filename, *args, **kwargs):&&&&&&&&self.filename = filename&&&&&&&&self.open_args = args&&&&&&&&self.open_kwargs = kwargs&&&&&&&&self.fileobj = None&&&&&def __enter__(self):&&&&&&&&f = open(self.filename, *self.open_args, **self.open_kwargs)&&&&&&&&while True:&&&&&&&&&&&&fcntl.flock(f, fcntl.LOCK_EX)&&&&&&&&&&&&fnew = open(self.filename, *self.open_args, **self.open_kwargs)&&&&&&&&&&&&if os.path.sameopenfile(f.fileno(), fnew.fileno()):&&&&&&&&&&&&&&&&fnew.close()&&&&&&&&&&&&&&&&break&&&&&&&&&&&&else:&&&&&&&&&&&&&&&&f.close()&&&&&&&&&&&&&&&&f = fnew&&&&&&&&self.fileobj = f&&&&&&&&return f&&&&&def __exit__(self, _exc_type, _exc_value, _traceback):&&&&&&&&self.fileobj.close()
def update(self):
with LockedOpen(filename, 'r+') as f:
n = int(f.read())
with tempfile.NamedTemporaryFile(
'w', dir=os.path.dirname(filename), delete=False) as tf:
tf.write('{}\n'.format(n))
tempname = tf.name
os.rename(tempname, filename)
def update(self):&&&&with LockedOpen(filename, 'r+') as f:&&&&&&&&n = int(f.read())&&&&&&&&n += 1&&&&&&&&with tempfile.NamedTemporaryFile(&&&&&&&&&&&&&&&&'w', dir=os.path.dirname(filename), delete=False) as tf:&&&&&&&&&&&&tf.write('{}\n'.format(n))&&&&&&&&&&&&tempname = tf.name&&&&&&&&os.rename(tempname, filename)
给追加更新上锁如同给截断-写更新上锁一样简单:需要一个排他锁,然后追加就完成了。需要长期运行的会将文件长久的打开的进程,可以在更新时释放锁,让其它进入。
spooldir模式有个很优美的性质就是它不需要任何锁。此外,你建立在使用灵活的命名模式和一个健壮的文件名分代。就是一个spooldir模式的好例子。它可以很容易的适应其它情况,不仅仅是处理邮件。
持久性有点特殊,因为它不仅依赖于应用,也与OS和硬件配置有关。理论上来说,我们可以假定,如果数据没有到达持久存储,os.fsync()或os.fdatasync()调用就没有返回结果。在实际情况中,我们有可能会遇到几个问题:我们可能会面对不完整的fsync实现,或者糟糕的磁盘控制器配置,它们都无法提供任何持久化的保证。有一个来自??的讨论对哪里会发生错误进行了详尽的讨论。有些像PostgreSQL 之类的数据库系统,甚至提供了?,以便管理员在运行时刻选择最佳的一个。然而不走运的人只能使用os.fsync(),并期待它可以被正确的实现。
通过截断-写模式,在结束写操作以后关闭文件以前,我们需要发送一个同步信号。注意通常这还牵涉到另一个层次的写缓存。glibc?缓存?甚至会在写操作传递到内核以前,在进程内部拦住它。同样为了得到空的glibc缓存,我们需要在同步以前对它flush():
with open(filename, 'w') as f:
model.write(f)
os.fdatasync(f)
with open(filename, 'w') as f:&& model.write(f)&& f.flush()&& os.fdatasync(f)
要不,你也可以带参数-u调用Python,以此为所有的文件I/O获得未缓冲的写。
大多数时候相较os.fsync()我更喜欢os.fdatasync(),以此避免同步元数据的更新(所有权、大小、mtime…)。元数据的更新可最终导致磁盘I/O搜索操作,这会使整个过程慢不少。
对写-替换风格更新使用同样的技巧只是成功了一半。我们得确保在代替旧文件之前,新写入文件的内容已经写入了非易失性存储器上了,但是替换操作怎么办?我们不能保证那个目录更新是否执行的刚刚好。在网络上有很多关于的长篇大论。但是在我们这种情况,旧文件和新文件都在同一个目录下,我们可以使用简单的解决方案来逃避这个这题。
os.rename(tempname, filename)
dirfd = os.open(os.path.dirname(filename), os.O_DIRECTORY)
os.fsync(dirfd)
os.close(dirfd)
os.rename(tempname, filename)dirfd = os.open(os.path.dirname(filename), os.O_DIRECTORY)os.fsync(dirfd)os.close(dirfd)
我们调用底层的os.open()来打开目录(Python自带的open()方法不支持打开目录),然后在目录文件描述符上执行os.fsync()。
对待追加更新和我以及说过的截断-写是相似的。
spooldir模式与写-替换模式同样的目录同步问题。幸运地是,可以使用同样的解决方案:第一步同步文件,然后同步目录。
这使可靠的更新文件成为可能。我已经演示了满足ACID的四大性质。这些展示的实例代码充当一个工具箱。掌握这编程技术最大的满足你的需求。有时,你并不需要满足所有的ACID性质,可能仅仅需要一到两个。我希望这篇文章可以帮助你去做已充分了解的决定,什么该去实现以及什么该舍弃。
本文翻译者:Lesus, super0555, 赵亮-本人, Andrew_Xue, 姜鹏飞

我要回帖

更多关于 黑马python培训班 费用 的文章

 

随机推荐