【问题标题】:List comprehension optimization列表理解优化
【发布时间】:2012-10-27 18:26:09
【问题描述】:

我设法将 8 行代码转换为 2 行代码。

第一个列表理解让我得到文件夹,第二个让我得到特定过滤器的文件:

hideTheseFolders=[".thumb",".mayaSwatches","RECYCLER","$AVG"]
fileFilters=["ma","jpg","png","mb",'iff','tga','tif']
newLst=[]
import os
locationTxt="E:\box\scripts"
[newLst.append(each) for each in os.listdir(locationTxt)  if os.path.isdir(os.path.join(locationTxt,each)) and each not in hideTheseFolders]
[newLst.append(os.path.basename(os.path.join(locationTxt,each))) for nfile in fileFilters for each in os.listdir(locationTxt) if each.endswith(nfile)]

现在在上面的代码中,最后两行从locationTxt 查找同一目录,这意味着我可能有一种方法可以合并最后两行。有什么建议吗?

【问题讨论】:

  • 你为什么要附加到列表理解中的列表?
  • 我建议两行就可以了。它们中的每一个都已经发生了很多事情(无论如何都不适合标准的行长),并且每个都做了一些不同的事情。大概你可以对每个理解的结果做newLst.extend(),而不是在理解中附加到newLst
  • 你为什么要把它合并成两行?
  • 另外,你知道os.path.basename(os.path.join(d, f))总是等于os.path.basename(f)吗?

标签: python list python-2.7 list-comprehension


【解决方案1】:

列表推导不是一种优化技术。当 Python 编译器看到一个列表推导时,它会将其分解为一个 for 循环。查看字节码 13 (FOR_ITER):

In [1]: from dis import dis

In [2]: code = "[i for i in xrange(100)]"

In [3]: dis(compile(code, '', 'single'))
  1           0 BUILD_LIST               0
              3 LOAD_NAME                0 (xrange)
              6 LOAD_CONST               0 (100)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                12 (to 28)
             16 STORE_NAME               1 (i)
             19 LOAD_NAME                1 (i)
             22 LIST_APPEND              2
             25 JUMP_ABSOLUTE           13
        >>   28 POP_TOP             
             29 LOAD_CONST               1 (None)
             32 RETURN_VALUE      

列表推导式与for循环相同这一事实也可以通过计时来看出。在这种情况下,for 循环实际上运行得稍微快一些(但不明显):

In [4]: %timeit l = [i for i in xrange(100)]
100000 loops, best of 3: 13.6 us per loop

In [5]: %%timeit l = []; app = l.append  # optimise out the attribute lookup for a fairer test
   ...: for i in xrange(100):
   ...:     app(i)
   ...: 
100000 loops, best of 3: 11.9 us per loop  #  insignificant difference. Run it yourself and you might get it the other way around

因此,您可以将任何给定的列表推导式编写为 for 循环,而对性能的影响最小(实际上,由于属性查找,通常会有很小的差异),并且通常具有显着的可读性优势。特别是,具有副作用的循环应该写成列表推导式。您也不应该使用包含两个以上 for 关键字的列表推导,或者使一行长度超过 70 个字符左右的列表推导。这些不是一成不变的规则,只是编写可读代码的启发式方法。

不要误会我的意思,列表推导式非常有用,而且通常比等效的 for-loop-and-append 更清晰、更简单、更简洁。但他们不能以这种方式被滥用。

【讨论】:

    【解决方案2】:

    我会坚持使用更具可读性的代码并避免列表理解,或者如果我必须进行列表理解,我会保留对可读的备份引用。

    到目前为止,我对列表理解的学习我会把它放在上面,以便每个人都可以跟随。

    理解的主要用途是:

    • 将迭代器(可能带有过滤器)的结果抓取到永久列表中:files = [f for f in list_files() if f.endswth("mb")]
    • 在可迭代类型之间转换:example = "abcde"; letters = [x for x in example] # this is handy for data packed into strings!
    • 简单列表处理:strings = [str(x) for x in list_of_numbers]
    • 使用 lambda 进行更复杂的列表处理以提高可读性: filter_func = lambda p, q: p > q larger_than_last = [val for val in list_of_numbers if filter_func(val, 5)]

    感谢大家的投入和关注。

    更新:我的研究和故障排除找到了确切的答案。

    filters = [[".thumb", ".mayaSwatches", "RECYCLER", "$AVG"], ["ma", "jpg", "png", "mb", 'iff', 'tga', 'tif']]
    locationTxt = r"E:\box\scripts"
    newLst = [each for each in os.listdir(locationTxt) if os.path.isdir(os.path.join(locationTxt, each)) and each not in filters[0]] + [each for each in os.listdir(locationTxt) if os.path.isfile(os.path.join(locationTxt, each)) and os.path.splitext(each)[-1][1:] in filters[1]]
    

    然而,正如我所提到的,坚持可读的代码逻辑才是正确的方法!!!

    【讨论】:

    • 你似乎掌握了它!请注意,letters = [x for x in example] 也可以呈现为 letters = list(x)
    • 此外,如果您将比较放在列表理解中,您使用lambda 的示例读起来会更好:[val for val in list_of_numbers if val > 5]。如果您的过滤器函数比这更复杂,您可能应该使用def 语句,并给它一个比filter_func 更好的名称。
    【解决方案3】:

    首先,您滥用列表推导来隐藏循环,方法是在其中添加;您实际上是在丢弃列表理解的结果。其次,没有必要以牺牲可读性为代价尽可能地塞进一行。

    如果你想使用列表推导,这在通过循环和过滤构建列表时实际上是一个非常好的主意,那么考虑这个版本:

    ignore_dirs = set([".thumb",".mayaSwatches","RECYCLER","$AVG"])
    extensions = ["ma", "jpg", "png", "mb", 'iff', 'tga', 'tif']
    location = "E:\\box\\scripts"
    
    filelist = [fname for fname in os.listdir(location)
                      if fname not in ignore_dirs
                      if os.path.isdir(os.path.join(location, fname))]
    filelist += [os.path.basename(fname)
                 for fname in os.listdir(location)
                 if any(fname.endswith(ext) for ext in extensions)]
    

    请注意,仍然有两种理解,因为您似乎正在构建一个逻辑上包含两种项目的列表。无需尝试在单个表达式中执行此操作,尽管您可以使用两个推导式,在它们之间使用 + 而不是 += 语句。

    (我冒昧地重命名了变量以反映它们所代表的内容。)

    【讨论】:

      【解决方案4】:

      主要的建议是买一本像样的 Python 书并好好阅读。从您的代码来看,您不知道列表解析是如何工作的,但您仍然设法将 8 行可读的代码塞进了 2 行过长且难以理解的代码中。

      你应该编写易于阅读的程序:

      • 换行符是你的朋友,使用它们
      • 空间也是你的朋友
      • 行应该适合屏幕(
      • imports放在文件开头
      • 读一本python书

      如果您想知道,您的代码应该如下所示:

      import os
      
      path = 'e:/box/scripts'
      
      newLst = list()
      for root,dirs,files in os.walk(path) :
          # add folders
          newLst.extend( [dir for dir in dirs if dir not in hideTheseFolders] )
      
          # add files
          newLst.extend( [file for file in files if file.lower().endswith(fileFilters)] )
      
          break    # don't descend into subfolders
      
      # convert to the full path or whatever you need here
      newLst = [os.path.join(path, file) for file in newLst]
      

      【讨论】:

      • 相信我,我没有补习,但你想怎么想。
      • 尚未回答但已接受建议的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-26
      • 2012-02-02
      • 2014-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-23
      相关资源
      最近更新 更多