【问题标题】:Is this the best way to get unique version of filename w/ Python?这是使用 Python 获得唯一版本的文件名的最佳方法吗?
【发布时间】:2008-10-08 15:50:21
【问题描述】:

仍在“潜入”Python,并想确保我没有忽略某些东西。我编写了一个脚本,从几个 zip 文件中提取文件,并将提取的文件一起保存在一个目录中。为了防止重复的文件名被覆盖,我写了这个小函数——我只是想知道是否有更好的方法来做到这一点? 谢谢!

def unique_filename(file_name):
counter = 1
file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
while os.path.isfile(file_name): 
    file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
    counter += 1
return file_name

我真的确实要求文件位于单个目录中,并且在我的情况下对重复编号绝对是可以接受的,所以我不是在寻找更强大的方法(尽管我想任何欢迎指点),但只是为了确保以正确的方式完成。

【问题讨论】:

    标签: python filenames


    【解决方案1】:

    一个问题是您的上述代码中存在竞争条件,因为在测试存在性和创建文件之间存在差距。这可能存在安全隐患(想想有人恶意将符号链接插入到他们无法覆盖的敏感文件,但您的程序以更高的权限运行可以)像这样的攻击就是为什么像 os.tempnam( ) 已弃用。

    要解决这个问题,最好的方法是实际尝试以这样一种方式创建文件,如果它失败,你会得到一个异常,并在成功时返回实际打开的文件对象。这可以通过传递 os.O_CREAT 和 os.O_EXCL 标志使用较低级别的 os.open 函数来完成。打开后,返回您创建的实际文件(以及可选的文件名)。例如,这里是你的代码修改为使用这种方法(返回一个(文件,文件名)元组):

    def unique_file(file_name):
        counter = 1
        file_name_parts = os.path.splitext(file_name) # returns ('/path/file', '.ext')
        while 1:
            try:
                fd = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_RDRW)
                return os.fdopen(fd), file_name
            except OSError:
                pass
            file_name = file_name_parts[0] + '_' + str(counter) + file_name_parts[1]
            counter += 1
    

    [编辑] 实际上,为您处理上述问题的更好方法可能是使用 tempfile 模块,尽管您可能会失去对命名的一些控制。这是一个使用它的例子(保持类似的界面):

    def unique_file(file_name):
        dirname, filename = os.path.split(file_name)
        prefix, suffix = os.path.splitext(filename)
    
        fd, filename = tempfile.mkstemp(suffix, prefix+"_", dirname)
        return os.fdopen(fd), filename
    
    >>> f, filename=unique_file('/home/some_dir/foo.txt')
    >>> print filename
    /home/some_dir/foo_z8f_2Z.txt
    

    这种方法的唯一缺点是您总是会得到一个包含一些随机字符的文件名,因为没有尝试首先创建未修改的文件 (/home/some_dir/foo.txt)。 您可能还想查看 tempfile.TemporaryFile 和 NamedTemporaryFile,它们将执行上述操作并在关闭时自动从磁盘中删除。

    【讨论】:

    • 是的,这是正确的方法。我希望我可以修改自己并将您的答案放在首位!
    • 小错字:应该是os.O_RDWR 而不是os.O_RDRW
    【解决方案2】:

    是的,对于可读但唯一的文件名,这是一个很好的策略。

    一个重要的变化:您应该将os.path.isfile 替换为os.path.lexists!正如现在所写的那样,如果有一个名为 /foo/bar.baz 的目录,您的程序将尝试用新文件覆盖它(这将不起作用)......因为isfile 只检查文件和不是目录。 lexists 检查目录、符号链接等...基本上是否有任何原因无法创建文件名。

    编辑:@Brian 给出了更好的答案,这在竞争条件方面更加安全和强大。

    【讨论】:

      【解决方案3】:

      两个小改动...

      base_name, ext = os.path.splitext(file_name) 
      

      你得到两个含义不同的结果,给他们不同的名字。

      file_name = "%s_%d%s" % (base_name, str(counter), ext)
      

      它并没有更快或更短。但是,当您想更改文件名模式时,该模式位于一个位置,并且更易于使用。

      【讨论】:

        【解决方案4】:

        如果您想要可读的名​​称,这看起来是一个不错的解决方案。
        有一些例程可以返回唯一的文件名,例如。临时文件,但它们会产生看起来很长的随机名称。

        【讨论】:

          【解决方案5】:

          如果您不关心可读性,uuid.uuid4() 是您的朋友。

          import uuid
          
          def unique_filename(prefix=None, suffix=None):
              fn = []
              if prefix: fn.extend([prefix, '-'])
              fn.append(str(uuid.uuid4()))
              if suffix: fn.extend(['.', suffix.lstrip('.')])
              return ''.join(fn)
          

          【讨论】:

            【解决方案6】:

            怎么样

            def ensure_unique_filename(orig_file_path):    
                from time import time
                import os
            
                if os.path.lexists(orig_file_path):
                    name, ext = os.path.splitext(orig_file_path)
                    orig_file_path = name + str(time()).replace('.', '') + ext
            
                return orig_file_path
            

            time() 以毫秒为单位返回当前时间。结合原始文件名,即使在复杂的多线程情况下也相当独特。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-01-29
              • 1970-01-01
              • 2012-12-16
              • 2014-01-30
              • 1970-01-01
              • 2018-07-25
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多