【问题标题】:How to make this end trimming python script faster?如何使这结束修剪python脚本更快?
【发布时间】:2012-05-06 19:41:26
【问题描述】:

有什么建议可以让这个脚本运行得更快吗?这个脚本我通常有两到千万行。

while True:
    line=f.readline()
    if not line:break
    linker='CTGTAGGCACCATCAAT'
    line_1=line[:-1]
    for i in range(len(linker)):
        if line_1[-i:]==linker[:i]:
            new_line='>\n'+line_1[:-i]+'\n'
            seq_1.append(new_line_1)
            if seq_2.count(line)==0:
                seq_2.append(line)
            else:pass
        else:pass

【问题讨论】:

  • 对于初学者,请正确格式化。它不会使程序运行得更快,但它会帮助任何可以改变它的人运行得更快。
  • python 会花费很多时间。很多时候,我的意思是太多的时间。试试 CPython。 C/Python API。赛通等
  • 您的脚本不完整或无效。
  • 当然,最后是 f2.writelines(seq_1) 和 seq_2 用于再次筛选整个文件以查找没有链接器的任何内容
  • 看来这个问题可能属于Code Review

标签: python trim


【解决方案1】:

首先,您似乎在内部循环中创建了许多字符串对象。您可以先尝试构建前缀列表:

linker = 'CTGTAGGCACCATCAAT'
prefixes = []
for i in range(len(linker)):
    prefixes.append(linker[:i])

此外,您可以使用string 的方法endswith 而不是在内循环的条件中创建新对象:

while True:
    line=f.readilne()
    if not line:
        break
    for prefix in prefixes:
        if line_1.endswith(prefix):
             new_line='>\n%s\n' % line_1[:-len(prefix)]
             seq_1.append(new_line_1)
             if seq_2.count(line)==0:
                 seq_2.append(line)  

我不确定那里的索引(例如len(prefix))。也不知道能快多少。

【讨论】:

  • 如果您迭代 enumerate(prefixes) 而不是仅仅在前缀上进行迭代,如 for prefix_len,prefix in enumerate(prefixes): 那么您将获得 len 作为枚举索引 prefix_len 而无需调用 len 函数 - 任何时候你可以避免在 Python 中调用函数,你会赢得性能。
  • .endswith 易于使用但比line_1[-i:]==linker[:i]
  • seq_2.count - 很重要。你真的需要计数吗?假设,没有。所以你应该使用`line in seq_2'。
【解决方案2】:

我不确定您的代码是什么意思,但一般做法是:

  1. 避免不必要的操作、条件等
  2. 尽可能将所有内容移出循环。
  3. 尽量减少循环级别。
  4. 尽可能使用常见的 Python 实践(它们通常更有效)。

但最重要的是:尽量简化和优化算法,不一定是实现它的代码。

从代码来看,应用上面的一些规则,代码可能是这样的:

seq_2 = set()  # seq_2 is a set now (maybe seq_1 should be also?)
linker = 'CTGTAGGCACCATCAAT'  # assuming same linker for every line
linker_range = range(len(linker))  # result of the above assumption
for line in f:
    line_1=line[:-1]
    for i in linker_range:
        if line_1[-i:] == linker[:i]:
            # deleted new_line_1
            seq_1.append('>\n' + line_1[:-i] + '\n')  # do you need this line?
            seq_2.add(line)  # will ignore if already in set

【讨论】:

    【解决方案3】:

    问题的很大一部分可能是seq_2.count(line) == 0 测试line 是否在seq_2 中。这将遍历seq_2 的每个元素并测试它是否等于line——随着seq_2 的增长,这将花费越来越长的时间。您应该改用一个集合,这将为您提供恒定时间测试,以确定它是否通过散列存在。这将丢弃seq_2 的顺序——如果您需要保持顺序,您可以同时使用集合和列表(测试它是否在集合中,如果不在,则同时添加)。

    这可能不会影响速度,但循环 for line in f 而不是 while True 循环与 line = f.readline() 和测试何时中断要好得多。此外,else: pass 语句完全没有必要,可以删除。

    linker 的定义应该移到循环之外,因为它不会被改变。 @uhz 关于预构建前缀和使用endswith 的建议可能也不错。

    【讨论】:

      【解决方案4】:

      比所有这些变体快大约两倍(至少在 python 2.7.2 上)

      seq_2 = set()
      # Here I use generator. So I escape .append lookup and list resizing
      def F(f):
          # local memory
          local_seq_2 = set()
          # lookup escaping
          local_seq_2_add = local_seq_2.add
          # static variables
          linker ='CTGTAGGCACCATCAAT'
          linker_range = range(len(linker))
          for line in f:
              line_1=line[:-1]
              for i in linker_range:
                  if line_1[-i:] == linker[:i]:
                      local_seq_2_add(line)
                      yield '>\n' + line_1[:-i] + '\n'
          # push local memory to the global
          global seq_2
          seq_2 = local_seq_2
      # here we consume all data
      seq_1 = tuple(F(f))
      

      是的,它丑陋且非 Python 风格,但它是完成这项工作的最快方法。

      您也可以在生成器中使用with open('file.name') as f: 升级此代码或添加一些其他逻辑。

      注意: 这个地方'>\n' + line_1[:-i] + '\n' - 值得怀疑。在某些机器上,这是连接字符串的最快方法。在某些机器上,最快的方法是'>\n'%s'\n'%line_1[:-i]''.join(('>\n',line_1[:-i],'\n'))(当然,在没有查找的版本中)。我不知道什么对你最好。 这很奇怪,但我电脑上的新格式化程序 '{}'.format(..) 显示最慢的结果。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-25
        • 2021-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多