【问题标题】:Create file but if name exists add number创建文件但如果名称存在添加数字
【发布时间】:2012-11-30 22:06:18
【问题描述】:

如果文件名已经存在,Python 是否有任何内置功能可以将数字添加到文件名中?

我的想法是它会像某些操作系统的工作方式一样工作 - 如果一个文件被输出到一个已经存在同名文件的目录,它会附加一个数字或增加它。

即:如果“file.pdf”存在,它将创建“file2.pdf”,下一次创建“file3.pdf”。

【问题讨论】:

标签: python file-io operating-system


【解决方案1】:

我最终为此编写了自己的简单函数。原始,但可以完成工作:

def uniquify(path):
    filename, extension = os.path.splitext(path)
    counter = 1

    while os.path.exists(path):
        path = filename + " (" + str(counter) + ")" + extension
        counter += 1

    return path

【讨论】:

    【解决方案2】:

    在某种程度上,Python 将此功能内置于 tempfile 模块中。不幸的是,您必须使用私有全局变量tempfile._name_sequence。这意味着正式地,tempfile 不保证在未来的版本中_name_sequence 甚至存在——它是一个实现细节。 但是,如果您无论如何都可以使用它,这将显示您如何在指定目录(例如 /tmp)中创建格式为 file#.pdf 的唯一命名文件:

    import tempfile
    import itertools as IT
    import os
    
    def uniquify(path, sep = ''):
        def name_sequence():
            count = IT.count()
            yield ''
            while True:
                yield '{s}{n:d}'.format(s = sep, n = next(count))
        orig = tempfile._name_sequence 
        with tempfile._once_lock:
            tempfile._name_sequence = name_sequence()
            path = os.path.normpath(path)
            dirname, basename = os.path.split(path)
            filename, ext = os.path.splitext(basename)
            fd, filename = tempfile.mkstemp(dir = dirname, prefix = filename, suffix = ext)
            tempfile._name_sequence = orig
        return filename
    
    print(uniquify('/tmp/file.pdf'))
    

    【讨论】:

    • 感谢您的回答!从文档中找出棘手的东西;)我选择了我自己的,更简单的方法,但是这个答案清楚地回答了我想知道的问题
    • 是的,如果您不需要tempfile 的特殊功能,这可能是一个明智的选择。 tempfile 模块努力避免某些竞争条件、安全性和拒绝服务攻击。使用顺序编号使上述代码容易受到拒绝服务攻击。而且我也不完全确定上述内容是否可以避免竞争条件或其他安全风险。
    【解决方案3】:

    我试图在我的项目中实现同样的事情,但@unutbu 的回答对我的需求来说似乎太“重”了,所以我最终想出了以下代码:

    import os
    index = ''
    while True:
        try:
            os.makedirs('../hi'+index)
            break
        except WindowsError:
            if index:
                index = '('+str(int(index[1:-1])+1)+')' # Append 1 to number in brackets
            else:
                index = '(1)'
            pass # Go and try create file again
    

    以防万一有人偶然发现并需要更简单的东西。

    【讨论】:

    • 非常好用,我用os.path.isfile(fname)逻辑效果很好
    【解决方案4】:

    如果所有文件都被编号没有问题,并且您事先知道要写入的文件的名称,您可以简单地这样做:

    import os
    
    counter = 0
    filename = "file{}.pdf"
    while os.path.isfile(filename.format(counter)):
        counter += 1
    filename = filename.format(counter)
    

    【讨论】:

    • 最优雅的!
    【解决方案5】:

    最近我遇到了同样的事情,这是我的方法:

    import os
    
    file_name = "file_name.txt"
    if os.path.isfile(file_name):
        expand = 1
        while True:
            expand += 1
            new_file_name = file_name.split(".txt")[0] + str(expand) + ".txt"
            if os.path.isfile(new_file_name):
                continue
            else:
                file_name = new_file_name
                break
    

    【讨论】:

      【解决方案6】:

      由于 tempfile hack A) 是一种 hack,而 B) 仍然需要大量代码,因此我采用了手动实现。你基本上需要:

      1. Safely create a file if and only if it does not exist 的一种方式(这是 tempfile hack 为我们提供的)。
      2. 文件名生成器。
      3. 隐藏混乱的包装函数。

      我定义了一个可以像 open 一样使用的 safe_open:

      def iter_incrementing_file_names(path):
          """
          Iterate incrementing file names. Start with path and add " (n)" before the
          extension, where n starts at 1 and increases.
      
          :param path: Some path
          :return: An iterator.
          """
          yield path
          prefix, ext = os.path.splitext(path)
          for i in itertools.count(start=1, step=1):
              yield prefix + ' ({0})'.format(i) + ext
      
      
      def safe_open(path, mode):
          """
          Open path, but if it already exists, add " (n)" before the extension,
          where n is the first number found such that the file does not already
          exist.
      
          Returns an open file handle. Make sure to close!
      
          :param path: Some file name.
      
          :return: Open file handle... be sure to close!
          """
          flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
      
          if 'b' in mode and platform.system() == 'Windows':
              flags |= os.O_BINARY
      
          for filename in iter_incrementing_file_names(path):
              try:
                  file_handle = os.open(filename, flags)
              except OSError as e:
                  if e.errno == errno.EEXIST:
                      pass
                  else:
                      raise
              else:
                  return os.fdopen(file_handle, mode)
      
      # Example
      with safe_open("some_file.txt", "w") as fh:
          print("Hello", file=fh)
      

      【讨论】:

      • 谢谢!你不需要做一些特别的事情来让它被用作上下文吗?
      • 该函数只需要返回一个文件句柄——这就是内置的 open() 函数所做的一切。另请参阅stackoverflow.com/questions/3774328/…
      【解决方案7】:

      假设我已经有了这些文件:

      此函数生成下一个可用的非现有文件名,必要时在扩展名前添加 _1、_2、_3 等后缀:

      import os
      
      def nextnonexistent(f):
          fnew = f
          root, ext = os.path.splitext(f)
          i = 0
          while os.path.exists(fnew):
              i += 1
              fnew = '%s_%i%s' % (root, i, ext)
          return fnew
      
      print(nextnonexistent('foo.txt'))  # foo_3.txt
      print(nextnonexistent('bar.txt'))  # bar_1.txt
      print(nextnonexistent('baz.txt'))  # baz.txt
      

      【讨论】:

        【解决方案8】:

        我还没有对此进行测试,但它应该可以工作,迭代可能的文件名,直到有问题的文件不存在,此时它会中断。

        def increment_filename(fn):
            fn, extension = os.path.splitext(path)
        
            n = 1
            yield fn + extension
            for n in itertools.count(start=1, step=1)
                yield '%s%d.%s' % (fn, n, extension)
        
        for filename in increment_filename(original_filename):
            if not os.isfile(filename):
                break
        

        【讨论】:

          【解决方案9】:

          这对我有用。 初始文件名为0.yml,如果存在则添加一个直到满足要求

          import os
          import itertools
          
          def increment_filename(file_name):
              fid, extension = os.path.splitext(file_name)
          
              yield fid + extension
              for n in itertools.count(start=1, step=1):
                  new_id = int(fid) + n
                  yield "%s%s" % (new_id, extension)
          
          
          def get_file_path():
              target_file_path = None
              for file_name in increment_filename("0.yml"):
                  file_path = os.path.join('/tmp', file_name)
                  if not os.path.isfile(file_path):
                      target_file_path = file_path
                      break
              return target_file_path
          

          【讨论】:

            【解决方案10】:
            import os
            
            class Renamer():
                def __init__(self, name):
                    self.extension = name.split('.')[-1]
                    self.name = name[:-len(self.extension)-1]
                    self.filename = self.name
                def rename(self):
                    i = 1
                    if os.path.exists(self.filename+'.'+self.extension):
                        while os.path.exists(self.filename+'.'+self.extension):
                            self.filename = '{} ({})'.format(self.name,i)
                            i += 1
                    return self.filename+'.'+self.extension
            

            【讨论】:

              【解决方案11】:

              我发现 os.path.exists() 条件函数做了我需要的。我以字典到 csv 的保存为例,但相同的逻辑适用于任何文件类型:

              import os 
              
              def smart_save(filename, dict):
                  od = filename + '_' # added underscore before number for clarity
              
                  for i in np.arange(0,500,1): # I set an arbitrary upper limit of 500
                      d = od + str(i)
              
                      if os.path.exists(d + '.csv'):
                          pass
              
                      else:
                          with open(d + '.csv', 'w') as f: #or any saving operation you need
                              for key in dict.keys():
                                  f.write("%s,%s\n"%(key, dictionary[key]))
                          break
              

              注意:默认情况下,这会在文件名后面附加一个数字(从 0 开始),但很容易改变它。

              【讨论】:

                【解决方案12】:

                我用pathlib 实现了一个类似的解决方案:

                创建与模式path/<file-name>-\d\d.ext 匹配的文件名。也许这个解决方案可以帮助...

                import pathlib
                from toolz import itertoolz as itz
                
                def file_exists_add_number(path_file_name, digits=2):
                
                    pfn = pathlib.Path(path_file_name)
                    parent = pfn.parent     # parent-dir of file
                    stem = pfn.stem         # file-name w/o extension
                    suffix = pfn.suffix     # NOTE: extension starts with '.' (dot)!
                
                    try:
                        # search for files ending with '-\d\d.ext'
                        last_file = itz.last(parent.glob(f"{stem}-{digits * '?'}{suffix}"))
                    except:
                        curr_no = 1
                    else:
                        curr_no = int(last_file.stem[-digits:]) + 1
                
                    # int to string and add leading zeros
                    curr_no = str(last_no).zfill(digits)
                    path_file_name = parent / f"{stem}-{curr_no}{suffix}"
                
                    return str(path_file_name)
                

                请注意:该解决方案从01 开始,并且只会找到包含-\d\d 的文件模式!

                【讨论】:

                  【解决方案13】:

                  稍晚一点,但仍然有这样的东西应该可以正常工作,mb它对某人有用。

                  您可以使用内置的迭代器来执行此操作(图像下载器作为您的示例):

                  def image_downloader():
                  
                          image_url = 'some_image_url'
                  
                          for count in range(10):
                              image_data = requests.get(image_url).content
                  
                              with open(f'image_{count}.jpg', 'wb') as handler:
                                  handler.write(image_data)
                  

                  文件将正确增加。结果是:

                  image.jpg
                  image_0.jpg
                  image_1.jpg
                  image_2.jpg
                  image_3.jpg
                  image_4.jpg
                  image_5.jpg
                  image_6.jpg
                  image_7.jpg
                  image_8.jpg
                  image_9.jpg
                  

                  【讨论】:

                  • 它似乎没有回答原始问题。问题是关于处理与预先存在的文件的冲突,而不是如何创建一些不相互冲突的文件名。
                  【解决方案14】:

                  Easy way for create new file if this name in your folder

                  if 'sample.xlsx' in os.listdir('testdir/'):
                  
                      i = 2
                      
                      while os.path.exists(f'testdir/sample ({i}).xlsx'):
                          i += 1
                      
                      wb.save(filename=f"testdir/sample ({i}).xlsx")
                  else:
                      wb.save(filename=f"testdir/sample.xlsx")
                  
                  

                  【讨论】:

                    猜你喜欢
                    • 2012-08-24
                    • 1970-01-01
                    • 2010-11-18
                    • 2020-07-18
                    • 2021-07-06
                    • 2020-07-18
                    • 2018-10-18
                    • 1970-01-01
                    相关资源
                    最近更新 更多