os.walk的复杂理解 - 表达能力的极限?

时间:2017-06-07 09:11:27

标签: python

我想完全控制基于os.walk的列表理解(后者将变成生成器表达式),但找不到方法。我是否达到了Python表达能力的极限,还是我缺乏的技巧词汇? : - )

这就是我所拥有的,甚至为此我不得不破解(短路逻辑表达式并强制为True),因为我可以找到一种方法来使用赋值运算符注入任何东西。无论我多么努力埋葬它,解析器都会找到我:-)也无法在任何地方注入打印(作为调试辅助工具)。

[( s, re.sub(r"^.*?FStar\.(.*)\.fs(.*)", dest_dir + r"Fstar.\1.fst\2", s) )
     for s in ( x[0].replace('\\', '/') + "/" + f
        for x in os.walk(".")
            if (skip_dir in x[1] and x[1].remove(skip_dir) or True)
            for f in x[2] if fnmatch(f ,patt))
]

我想要的是完全控制dirs:

x[1][:]=[d for d in x[1] if d not in skip_list]

这让我想到了Python的表现力的极限。

对于这个特定案例的任何想法都表示赞赏,但是如果有人知道/有一个构造的例子提供比os.walk更多的潜在控制,只是面对理解嵌套/组合的限制。

在写这篇文章时,我也开始希望使用管道或其他形式的发电机组合器。

理解结构(通常在句法结构中)表达能力的最终限制是一个更大的问题。

对于那些要求澄清的人 - 第一个问题是如何在理解中实现对os.walk的控制,能够删除多个项目(来自skip_list)而不仅仅是一个,第二个是限制是什么Pythons表达能力 - 还有什么,可以在嵌套的理解结构中完成。例如,作业。

1 个答案:

答案 0 :(得分:1)

所有 Python 用户都应该熟悉Zen

在这种情况下,我可以看到违反下一条规则

  •   

    可读性计数。

    给出的例子太难以理解了。

  •   

    简单比复杂更好。

         

    稀疏优于密集。

    在一次理解中有太多陈述。

  •   

    Flat比嵌套好。

    我们在这里有嵌套的理解。

概述

  • 理解中的副作用不是一个好主意

    x[1].remove(skip_dir)
    

    参见讨论here

  • 而不是字符串连接

    x[0].replace('\\', '/') + "/" + f
    

    我们使用os.path.join

    os.path.join(x[0].replace('\\', '/'), f)
    
  • 关于声明

    for x in os.walk(".") 
    

    os.walk产生具有根目录,子目录和文件名称的元组,因此最好解包tuple而不是按索引访问坐标

    for root, directories_names, files_names in os.walk('.')
    
  • 如果我们多次使用相同的正则表达式,那么在使用之前它应该被编译,所以

    ... re.sub(r"^.*?FStar\.(.*)\.fs(.*)", dest_dir + r"Fstar.\1.fst\2", s) ...
    

    可分为

    TARGET_FILES_NAMES_RE = re.compile(r"^.*?FStar\.(.*)\.fs(.*)")
    
    ... TARGET_FILES_NAMES_RE.sub(dest_dir + r"Fstar.\1.fst\2", s) ...
    

    也应该忽略dest_dir + r"Fstar.\1.fst\2"应该做什么:我相信它应该将dest_dir加入到简化的文件名中。

主要想法

当理解变得复杂(或特别复杂)时,将其重写为生成函数是个好主意。

对于给定的例子,它可能像

TARGET_FILES_NAMES_RE = re.compile(r"^.*?FStar\.(.*)\.fs(.*)")


def modify_paths(top, dest_dir, skip_dir, pattern):
    replacement = os.path.join(dest_dir, r"Fstar.\1.fst\2")
    for root, directories_names, files_names in os.walk(top):
        try:
            # we are removing `skip_dir` from all subdirectories,
            # is it a desired behavior?
            directories_names.remove(skip_dir)
        except ValueError:
            # not in list
            pass

        for file_name in files_names:
            if not fnmatch(file_name, pattern):
                continue
            s = os.path.join(root.replace('\\', '/'), file_name)
            yield (s,
                   TARGET_FILES_NAMES_RE.sub(replacement, s))

但它仍然是原始的,应该重构。

测试

我已经创建了目录

/
    doc.txt
    FStar.anotherfs
    FStar.other.fS
    FStar.some.fs90
    FStar.text..fs
    test.py

    skip_me/
            FStar.file.fs
            FStar.sample.fs
            FStar.skipped.fs

其中test.py内容:

import os
import re
from fnmatch import fnmatch

TARGET_FILES_NAMES_RE = re.compile(r"^.*?FStar\.(.*)\.fs(.*)")


def modify_paths(top, dest_dir, skip_dir, pattern):
    replacement = os.path.join(dest_dir, r"Fstar.\1.fst\2")
    for root, directories_names, files_names in os.walk(top):
        try:
            # we are removing `skip_dir` from all subdirectories,
            # is it a desired behavior?
            directories_names.remove(skip_dir)
        except ValueError:
            # not in list
            pass

        for file_name in files_names:
            if not fnmatch(file_name, pattern):
                continue
            s = os.path.join(root.replace('\\', '/'), file_name)
            yield (s,
                   TARGET_FILES_NAMES_RE.sub(replacement, s))


if __name__ == '__main__':
    top = '.'
    skip_dir = 'skip_me'
    patt = '*'
    # slash is required for version with list comprehension
    dest_dir = 'dest_dir/'
    before = [
        (s,
         re.sub(r"^.*?FStar\.(.*)\.fs(.*)", dest_dir + r"Fstar.\1.fst\2", s))
        for s in (os.path.join(x[0].replace('\\', '/'), f)
                  for x in os.walk(top)
                  if (skip_dir in x[1] and
                      x[1].remove(skip_dir) or True)
                  for f in x[2] if fnmatch(f, patt))]
    after = list(
        modify_paths(top=top, dest_dir=dest_dir, skip_dir=skip_dir,
                     pattern=patt))
    assert after == before

并断言成功。

P上。 S上。

跳过异常并不是一个好主意,但如果你知道会发生什么,它可以成为一个强大的工具。

也可以使用

中的contextlib.suppress上下文管理器重写它
try:
    directories_names.remove(skip_dir)
except ValueError:
    pass

with suppress(ValueError):
    directories_names.remove(skip_dir)