python循环优化 - 迭代dirs 3级并删除

时间:2012-12-20 21:09:59

标签: python

您好我有以下程序,

问题: - 如何使它更优雅,更可读,更紧凑。 - 我该怎么做才能将常见循环提取到另一个方法。

假设:

从给定的rootDir开始,dirs的组织方式如下所示。

proc的作用:

如果输入为200,则删除所有OLDER超过200天的DIRS。不是基于修改时间,而是基于dir结构和dir名称[我稍后会在每个老版本的dd上用暴力“rm -Rf”删除]

例如dir结构:

-2009(year dirs) [will force delete dirs e.g "rm -Rf" later]
-2010
  -01...(month dirs)
  -05 ..
      -01.. (day dirs)
         -many files. [I won't check mtime at file level - takes more time]
      -31
  -12
-2011
-2012 ...

我的代码:

def get_dirs_to_remove(dir_path, olderThanDays):
    today = datetime.datetime.now();
    oldestDayToKeep = today + datetime.timedelta(days= -olderThanDays) 
    oldKeepYear = int(oldestDayToKeep.year)
    oldKeepMonth =int(oldestDayToKeep.month);
    oldKeepDay = int(oldestDayToKeep.day);
    for yearDir in os.listdir(dirRoot):
        #iterate year dir
        yrPath = os.path.join(dirRoot, yearDir);
        if(is_int(yearDir) == False):
            problemList.append(yrPath); # can't convery year to an int, store and report later 
            continue

        if(int(yearDir) < oldKeepYear):
                print "old Yr dir: " + yrPath
                #deleteList.append(yrPath); # to be bruteforce deleted e.g "rm -Rf"
                yield yrPath;
                continue
        elif(int(yearDir) == oldKeepYear):
            # iterate month dir
            print "process Yr dir: " + yrPath
            for monthDir in os.listdir(yrPath):
                monthPath = os.path.join(yrPath, monthDir)
                if(is_int(monthDir) == False):
                    problemList.append(monthPath);
                    continue
                if(int(monthDir) < oldKeepMonth):
                        print "old month dir: " + monthPath
                        #deleteList.append(monthPath);
                        yield monthPath;
                        continue
                elif (int(monthDir) == oldKeepMonth):
                    # iterate Day dir
                    print "process Month dir: " + monthPath
                    for dayDir in os.listdir(monthPath):
                        dayPath = os.path.join(monthPath, dayDir)
                        if(is_int(dayDir) == False):
                            problemList.append(dayPath);
                            continue
                        if(int(dayDir) < oldKeepDay):
                            print "old day dir: " + dayPath
                            #deleteList.append(dayPath);
                            yield dayPath
                            continue
print [ x for x in get_dirs_to_remove(dirRoot, olderThanDays)]
print "probList" %  problemList # how can I get this list also from the same proc?

2 个答案:

答案 0 :(得分:1)

这实际上看起来很不错,除了这篇评论中提到的一件大事:

print "probList" %  problemList # how can I get this list also from the same proc?

听起来你正在将problemList存储在全局变量或其他内容中,而你想解决这个问题。以下是一些方法:

  • 产生删除文件和问题文件 - 例如,产生tuple,其中第一个成员说明它是哪种,第二个成员用它做什么。
  • problemList作为参数。请记住,list是可变的,因此调用者可以看到附加到参数。
  • yield最后的problemList - 这意味着您需要重新构建使用生成器的方式,因为它不再仅仅是一个简单的迭代器。
  • 将生成器编码为类而不是函数,并将problemList存储为成员变量。
  • 查看内部生成器信息并在那里填写problemList,以便调用者可以检索它。

同时,有几种方法可以使代码更紧凑和可读。

最简单:

print [ x for x in get_dirs_to_remove(dirRoot, olderThanDays)]

此列表理解与原始迭代完全相同,您可以更简单地编写为:

print list(get_dirs_to_remove(dirRoot, olderThanDays))

对于算法本身,您可以对listdir进行分区,然后只使用分区list。你可以懒洋洋地做到这一点:

yearDirs = os.listdir(dirRoot):
problemList.extend(yearDir for yearDir in yearDirs if not is_int(yearDir))
yield from (yearDir for yearDir in yearDirs if int(yearDir) < oldKeepYear)
for year in (yearDir for yearDir in yearDirs if int(yearDir) == oldKeepYear):
    # next level down

或严格地说:

yearDirs = os.listdir(dirRoot)
problems, older, eq, newer = partitionDirs(yearDirs, oldKeepYear)
problemList.extend(problems)
yield from older
for year in eq:
    # next level down

后者可能更有意义,特别是考虑到yearDirs已经是一个列表,并且不太可能那么大。

当然你需要编写partitionDirs函数 - 但好的是,你可以在几个月和几天的时间里再次使用它。这很简单。实际上,我实际上可能通过排序来进行分区,因为它使逻辑变得如此明显,即使它更冗长:

def partitionDirs(dirs, keyvalue):
    problems = [dir for dir in dirs if not is_int(dir)]
    values = sorted(dir for dir in dirs if is_int(dir), key=int)
    older, eq, newer = partitionSortedListAt(values, keyvalue, key=int)

如果你环顾四周(也许搜索“python分区排序列表”?),你可以找到很多方法来实现partitionSortedListAt功能,但这里有一些我觉得很容易理解的东西的草图谁没有想到这个问题:

    i = bisect.bisect_right(vals, keyvalue)
    if vals[i] == keyvalue:
        return problems, vals[:i], [vals[i]], vals[i+1:]
    else:
        return problems, vals[:i], [], vals[i:]

如果您搜索“python split predicate”,您还可以找到其他方法来实现初始拆分 - 但请记住,大多数人都要关注能够对任意迭代进行分区(这里不需要)或者,无论是否正确,都担心效率(这里你也不关心)。所以,不要寻找有人说“最好”的答案;看看所有的答案,然后选择一个看起来最具可读性的答案。

最后,你可能会注意到你最终得到的三个级别几乎相同:

yearDirs = os.listdir(dirRoot)
problems, older, eq, newer = partitionDirs(yearDirs, oldKeepYear)
problemList.extend(problems)
yield from older
for year in eq:
    monthDirs = os.listdir(os.path.join(dirRoot, str(year)))
    problems, older, eq, newer = partitionDirs(monthDirs, oldKeepMonth)
    problemList.extend(problems)
    yield from older
    for month in eq:
        dayDirs = os.listdir(os.path.join(dirRoot, str(year), str(month)))
        problems, older, eq, newer = partitionDirs(dayDirs, oldKeepDay)
        problemList.extend(problems)
        yield from older
        yield from eq

你可以通过递归 - 向下传递到目前为止的路径以及要检查的更多级别的列表来进一步简化这一点,并且你可以将这18行转换为9.这是否更具可读性取决于你的管理能力如何编码要传递的信息和适当的yield from。这是这个想法的草图:

def doLevel(pathSoFar, dateComponentsLeft):
    if not dateComponentsLeft:
        return
    dirs = os.listdir(pathSoFar)
    problems, older, eq, newer = partitionDirs(dirs, dateComponentsLeft[0])
    problemList.extend(problems)
    yield from older
    if eq:
        yield from doLevel(os.path.join(pathSoFar, eq[0]), dateComponentsLeft[1:]))
yield from doLevel(rootPath, [oldKeepYear, oldKeepMonth, oldKeepDay])

如果你使用的是没有yield from的较旧的Python版本,那么早期的东西几乎是微不足道的。写入的递归版本将更加丑陋且更加痛苦。但是在处理递归生成器时实际上没有办法避免这种情况,因为子生成器不能“通过”调用生成器。

答案 1 :(得分:1)

我建议不要使用发电机,除非你绝对确定需要它们。在这种情况下,您不需要它们。

在下面,并不严格需要newer_list。虽然categorizeSubdirs可以递归,但我不认为复杂性的增加值得重复节省(但这只是个人风格问题;当不清楚需要多少级别的递归时,我只使用递归或数字固定但很大;三个不够IMO。


def categorizeSubdirs(keep_int, base_path):
    older_list = []
    equal_list = []
    newer_list = []
    problem_list = []

    for subdir_str in os.listdir(base_path):
        subdir_path = os.path.join(base_path, subdir_str))
        try:
            subdir_int = int(subdir_path)
        except ValueError:
            problem_list.append(subdir_path)
        else:
            if subdir_int  keep_int:
                newer_list.append(subdir_path)
            else:
                equal_list.append(subdir_path)

    # Note that for your case, you don't need newer_list, 
    # and it's not clear if you need problem_list
    return older_list, equal_list, newer_list, problem_list

def get_dirs_to_remove(dir_path, olderThanDays):
    oldest_dt = datetime.datetime.now() datetime.timedelta(days= -olderThanDays) 
    remove_list = []
    problem_list = []

    olderYear_list, equalYear_list, newerYear_list, problemYear_list = categorizeSubdirs(oldest_dt.year, dir_path))
    remove_list.extend(olderYear_list)
    problem_list.extend(problemYear_list)

    for equalYear_path in equalYear_list:
        olderMonth_list, equalMonth_list, newerMonth_list, problemMonth_list = categorizeSubdirs(oldest_dt.month, equalYear_path))
        remove_list.extend(olderMonth_list)
        problem_list.extend(problemMonth_list)

        for equalMonth_path in equalMonth_list:
            olderDay_list, equalDay_list, newerDay_list, problemDay_list = categorizeSubdirs(oldest_dt.day, equalMonth_path))
            remove_list.extend(olderDay_list)
            problem_list.extend(problemDay_list)

    return remove_list, problem_list

最终的三个嵌套循环可以减少重复性,代价是代码复杂性。我不认为这是值得的,尽管合理的人可以不同意。在其他条件相同的情况下,我更喜欢更简单的代码,而不是更聪明的代码;正如他们所说,阅读代码比编写代码更难,所以如果你能编写最聪明的代码,那么你就不会聪明地阅读它。 :/