【问题标题】:Flatten complex directory structure in Python在 Python 中展平复杂的目录结构
【发布时间】:2013-07-07 00:05:00
【问题描述】:

我想将文件从复杂的目录结构移动到一个地方。例如,我有这么深的层次结构:

foo/
    foo2/
        1.jpg
    2.jpg
    ...

我希望它是:

1.jpg
2.jpg
...

我目前的解决方案:

def move(destination):
    for_removal = os.path.join(destination, '\\')
    is_in_parent = lambda x: x.find(for_removal) > -1
    with directory(destination):
        files_to_move = filter(is_in_parent,
                               glob_recursive(path='.'))
    for file in files_to_move:
        shutil.move(file, destination)

定义:directoryglob_recursive。请注意,我的代码仅将文件移动到它们的公共父目录,而不是任意目的地。

如何简洁优雅地将所有文件从复杂的层次结构移动到一个地方?

【问题讨论】:

    标签: python directory-structure flatten file-move


    【解决方案1】:

    我不喜欢测试要移动的文件的名称,看看我们是否已经在目标目录中。相反,此解决方案仅扫描目标的子目录

    import os
    import itertools
    import shutil
    
    
    def move(destination):
        all_files = []
        for root, _dirs, files in itertools.islice(os.walk(destination), 1, None):
            for filename in files:
                all_files.append(os.path.join(root, filename))
        for filename in all_files:
            shutil.move(filename, destination)
    

    解释:os.walk 以“自上而下”的方式递归地遍历目的地。整个文件名是用 os.path.join(root, filename) 调用构造的。现在,为了防止扫描目标顶部的文件,我们只需要忽略 os.walk 迭代的第一个元素。为此,我使用 islice(iterator, 1, None)。另一种更明确的方法是这样做:

    def move(destination):
        all_files = []
        first_loop_pass = True
        for root, _dirs, files in os.walk(destination):
            if first_loop_pass:
                first_loop_pass = False
                continue
            for filename in files:
                all_files.append(os.path.join(root, filename))
        for filename in all_files:
            shutil.move(filename, destination)
    

    【讨论】:

    • 您不处理要移动的文件名与目标目录中的文件名相同的情况。例如,如果源子目录只包含名为 hello.txt 的文件怎么办? move() 会抛出错误。
    • 这个解决方案也会留下空的子目录。解决方法:将 _dirs 存储在将 first_loop_pass 设置为 False 的位置旁边,然后在执行文件移动后最后循环遍历 dirs 并调用 shutil.rmtree()
    【解决方案2】:

    这样可以,如果文件发生冲突,它还会重命名文件(我注释掉了实际的移动并替换为副本):

    import os
    import sys
    import string
    import shutil
    
    #Generate the file paths to traverse, or a single path if a file name was given
    def getfiles(path):
        if os.path.isdir(path):
            for root, dirs, files in os.walk(path):
                for name in files:
                    yield os.path.join(root, name)
        else:
            yield path
    
    destination = "./newdir/"
    fromdir = "./test/"
    for f in getfiles(fromdir):
        filename = string.split(f, '/')[-1]
        if os.path.isfile(destination+filename):
            filename = f.replace(fromdir,"",1).replace("/","_")
        #os.rename(f, destination+filename)
        shutil.copy(f, destination+filename)
    

    【讨论】:

      【解决方案3】:

      通过目录递归运行,移动文件并为目录启动move

      import shutil
      import os
      
      def move(destination, depth=None):
          if not depth:
              depth = []
          for file_or_dir in os.listdir(os.path.join([destination] + depth, os.sep)):
              if os.path.isfile(file_or_dir):
                  shutil.move(file_or_dir, destination)
              else:
                  move(destination, os.path.join(depth + [file_or_dir], os.sep))
      

      【讨论】:

      • 绝对路径版本 ``` import shutil import os def move(destination, depth=''): current_depth = os.path.join(destination, depth) for file_or_dir in os.listdir(current_depth) : file_or_dir = os.path.join(current_depth, file_or_dir) if os.path.isfile(file_or_dir): if depth: shutil.move(file_or_dir, destination) else: move(destination, os.path.join(depth, file_or_dir) ) move(r'F:\sw\PycharmProjects\yappy-dev\lectures') ```
      • 这将无法正常工作。 os.path.join 不带列表,您需要使用 splat 将列表放入单独的参数中: os.path.join(*([destination] + depth))
      【解决方案4】:
      import os.path, shutil
      
      def move(src, dest):
          not_in_dest = lambda x: os.path.samefile(x, dest)
          files_to_move = filter(not_in_dest,
                                 glob_recursive(path=src))
      
          for f in files_to_move:
              shutil.move(f, dest)
      

      Sourceglob_recursive。如果它们发生冲突,则不会更改文件名。

      samefile 是一种比较路径的安全方法。但它在 Windows 上不起作用,请查看How to emulate os.path.samefile behaviour on Windows and Python 2.7?

      【讨论】:

        【解决方案5】:
        def splitPath(p):
            a,b = os.path.split(p)
            return (splitPath(a) if len(a) and len(b) else []) + [b]
        
        def safeprint(s):
            try:
                print(s)
            except UnicodeEncodeError:
                if sys.version_info >= (3,):
                    print(s.encode('utf8').decode(sys.stdout.encoding))
                else:
                    print(s.encode('utf8'))
        
        def flatten(root, doit):
            
            SEP  = "¦"
            REPL = "?"
        
            folderCount = 0
            fileCount = 0
        
            if not doit:
                print("Simulating:")
        
            for path, dirs, files in os.walk(root, topdown=False):
        
                if path != root:
        
                    for f in files:
        
                        sp = splitPath(path)
        
                        np = ""
        
                        for element in sp[1:]:
                            e2 = element.replace(SEP, REPL)
                            np += e2 + SEP
        
                        f2 = f.replace(SEP, REPL)
                        newName = np + f2
        
                        safeprint("Moved:   "+ newName )
                        if doit:
                            shutil.move(os.path.join(path, f), os.path.join(root, f))
                            # Uncomment, if you want filenames to be based on folder hierarchy.
                            #shutil.move(os.path.join(path, f), os.path.join(root, newName))
                        fileCount += 1
        
                    safeprint("Removed: "+ path)
                    if doit:
                        os.rmdir(path)
                    folderCount += 1
        
            if doit:
                print("Done.")        
            else:
                print("Simulation complete.")
        
        
            print("Moved files:", fileCount)
            print("Removed folders:", folderCount)
        
        
        directory_path = r"C:\Users\jd\Documents\myFtpData"
        flatten(directory_path, True)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-12-23
          • 2022-06-23
          • 2021-06-25
          • 2015-03-25
          • 2011-03-16
          • 1970-01-01
          • 2019-11-27
          相关资源
          最近更新 更多