【问题标题】:os.link() vs. os.rename() vs. os.replace() for writing atomic write files. What is the best approach?os.link() vs. os.rename() vs. os.replace() 用于编写原子写入文件。最好的方法是什么?
【发布时间】:2020-02-24 03:48:18
【问题描述】:

您好,我正在尝试编写一个像这样的原子写入函数...

with tempfile.NamedTemporaryFile(mode= "w", dir= target_directory) as f: 
     #perform file writing operation  
     os.replace(f.name, target_file_name) 

我正在努力找出第 3 行中的最佳操作是什么。我应该使用 os.replace()、os.rename() 还是应该使用 os.关联()?

os.link() 是否使用更多内存?每个的好处是什么,它们都是原子的?

【问题讨论】:

  • 这不仅是特定于平台的,还取决于底层文件系统。我怀疑python会在这里给出任何硬性保证。有些配置根本不允许原子移动(例如 FAT32 上的 Windows,尽管我怀疑是否还有很多这样的语句)

标签: python


【解决方案1】:

os.rename / os.replaceboth implemented 使用 this function

唯一的区别是os.replace使用is_replace=1,它对posix没有影响,但在windows上设置MOVEFILE_REPLACE_EXISTING标志:

如果存在名为 lpNewFileName 的文件,则该函数将其内容替换为 lpExistingFileName 文件的内容,前提是满足有关访问控制列表 (ACL) 的安全要求。有关详细信息,请参阅本主题的备注部分。

如果 lpNewFileName 或 lpExistingFileName 命名一个目录并且 lpExistingFileName 存在,则报错。

os.link 不太适合这个功能除非你可以保证目标文件不存在(os.link 会报错):

$ touch a b
$ link a b
link: cannot create link 'b' to 'a': File exists
$ python3 -c 'import os; os.link("a", "b")'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
FileExistsError: [Errno 17] File exists: 'a' -> 'b'

【讨论】:

    【解决方案2】:

    我不确定在这种情况下您所说的“原子”是什么意思。但主要区别是:

    replace 和 rename 相似,但 replace 更跨平台(因此,如果您知道要使用哪个系统,我认为 rename 会更好)。

    os.link 将使用相同数量的磁盘空间,但您将创建一个硬链接,这可能不是您想要的。

    【讨论】:

    • 原子的意思是移动是..原子的。这意味着其他应用程序要么看到整个文件,要么什么都不看到,而不是可能的部分文件。
    【解决方案3】:

    首先,os.link 创建hard link,这意味着 src-link 与 dst-link 共享相同的空间。不执行复制写入操作 - 因此没有内存开销。遗憾的是,硬链接并非与所有文件系统都兼容。例如,NTFS 支持硬链接,而 FAT 和 ReFS 不支持。

    其次,os.replace 应该优先于 os.rename,因为它更跨平台(仅适用于 Python 3.3+)。另一个重要的事情是源和目标路径必须在同一个物理磁盘上。至于原子性,os.replace 在 Unix\Windows 上的所有可能情况下似乎都有很高的概率(但不是 100%)是原子的。相关链接123。无论如何,这是推荐的approach 以避免race conditions/TOCTOU-bags。至于我,我从未遇到或能够重现调用os.replace 最终导致 src 或 dst 数据损坏的情况。但是,只要官方文档中不要求这种行为,os.replace 不应被视为原子调用(尤其是对于 Windows)

    根据定义,您的示例代码绝对不是原子的 - 任何时候任何相关过程都可能破坏临时文件的数据完整性;在非 Windows 系统上突然关闭执行过程甚至可以将您的临时文件永远留在指定的目录中。要解决这些问题,您可能需要一些同步原语、锁,而您的代码逻辑必须假设最不可能发生的数据中断或损坏情况。

    以下是应从现有文件中提取或以其他方式在此类文件中创建某些数据的常见情况示例:

    import time
    filename = 'data' # file with data
    temp_filename = 'data.temp' # temp used to create 'data'
    
    def get_data():
        while True:
            try:
                os.remove(temp_filename)
            except FileNotFoundError: # if no data.temp
                try: # check if data already exists:
                    with open(filename, 'rt', encoding='utf8') as f:
                        return f.read() # return data here
                except FileNotFoundError:
                    pass # create data
            except PermissionError: # if another process/thread is creating data.temp right now
                time.sleep(0.1) # wait for it
                continue
            else:
                pass # something went wrong and it's better to create all again
    
            # data creation:
            excl_access = 'xt' # raises error if file exists - used as a primitive lock
            try:
                with open(temp_filename, excl_access, encoding='utf8') as f:
                    # process can be interrupted here 
                    f.write('Hello ') # or here
                    f.write('world!') # or here
            except FileExistsError: # another one is creating it now
                time.sleep(0.1) # wait for it
                continue
            except Exception: # something went wrong
                continue
            try:
                os.replace(temp_filename, filename) # not sure this would be atomic in 100%
            except FileNotFoundError:
                continue # try again
    

    Here's 一个相关问题,其中包含一些建议一些外部库来处理原子文件创建的答案

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-10
      • 1970-01-01
      • 1970-01-01
      • 2014-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-01
      相关资源
      最近更新 更多