2

我正在将文本格式的 Databasedump 的几个部分导入 MySQL,问题是在有趣的数据之前,前面有很多不有趣的东西。我编写了这个循环来获取所需的数据:

def readloop(DBFILE):
    txtdb=open(DBFILE, 'r')

sline = ""

# loop till 1st "customernum:" is found
while sline.startswith("customernum:  ") is False: 
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    data = []
    data.append(sline)
    sline = txtdb.readline()
    while sline.startswith("customernum:  ") is False:
        data.append(sline)
        sline = txtdb.readline()
        if len(sline) == 0:
            break
    customernum = getitem(data, "customernum:  ")
    street = getitem(data, "street:  ")
    country = getitem(data, "country:  ")
    zip = getitem(data, "zip:  ")

文本文件非常大,所以循环到第一个想要的条目需要很长时间。任何人都知道这是否可以更快地完成(或者如果我修复这不是最好的主意)?

提前谢谢了!

4

5 回答 5

5

请不要写这段代码:

while condition is False:

布尔条件是大声哭泣的布尔值,因此可以直接测试(或否定和测试)它们

while not condition:

你的第二个while循环没有写成“while condition is True:”,我很好奇为什么你觉得需要在第一个循环中测试“is False”。

拉出 dis 模块,我想我会进一步剖析这个。在我的 pyparsing 经验中,函数调用是性能杀手,所以如果可能的话最好避免函数调用。这是您的原始测试:

>>> test = lambda t : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_GLOBAL              1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

这里发生了两件昂贵的事情,CALL_FUNCTION并且LOAD_GLOBAL。您可以LOAD_GLOBAL通过为 False 定义一个本地名称来减少:

>>> test = lambda t,False=False : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_FAST                1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

但是,如果我们完全放弃 'is' 测试呢?

>>> test = lambda t : not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 UNARY_NOT
             13 RETURN_VALUE

我们用一个简单LOAD_xxx的. “是假的”当然对性能没有任何帮助。COMPARE_OPUNARY_NOT

现在,如果我们可以在不进行任何函数调用的情况下对一行进行一些粗略的消除会怎样。如果该行的第一个字符不是'c',它就不可能以('customernum')开头。让我们试试:

>>> test = lambda t : t[0] != 'c' and not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_CONST               0 (0)
              6 BINARY_SUBSCR
              7 LOAD_CONST               1 ('c')
             10 COMPARE_OP               3 (!=)
             13 JUMP_IF_FALSE           14 (to 30)
             16 POP_TOP
             17 LOAD_FAST                0 (t)
             20 LOAD_ATTR                0 (startswith)
             23 LOAD_CONST               2 ('customernum')
             26 CALL_FUNCTION            1
             29 UNARY_NOT
        >>   30 RETURN_VALUE

(请注意,使用 [0] 获取字符串的第一个字符不会创建切片 - 这实际上非常快。)

现在,假设没有大量以“c”开头的行,粗切过滤器可以使用所有相当快的指令来消除一行。事实上,通过测试 "t[0] != 'c'" 而不是 "not t[0] == 'c'" 我们为自己节省了一条无关UNARY_NOT指令。

所以使用这个关于捷径优化的学习,我建议改变这个代码:

while sline.startswith("customernum:  ") is False:
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    ... do the rest of the customer data stuff...

对此:

for sline in txtdb:
    if sline[0] == 'c' and \ 
       sline.startswith("customernum:  "):
        ... do the rest of the customer data stuff...

请注意,我还删除了 .readline() 函数调用,并使用“for sline in txtdb”对文件进行迭代。

我意识到 Alex 提供了完全不同的代码体来查找第一个“customernum”行,但我会尝试在算法的一般范围内进行优化,然后再拿出大而晦涩的块读取枪。

于 2009-09-12T20:54:14.793 回答
2

优化的总体思路是“按大块”(主要是忽略行结构)来定位第一条感兴趣的行,然后继续对其余的行进行逐行处理)。它有点挑剔且容易出错(一个接一个等),所以它确实需要测试,但总体思路如下......:

import itertools

def readloop(DBFILE):
  txtdb=open(DBFILE, 'r')
  tag = "customernum:  "
  BIGBLOCK = 1024 * 1024
  # locate first occurrence of tag at line-start
  # (assumes the VERY FIRST line doesn't start that way,
  # else you need a special-case and slight refactoring)
  blob = ''
  while True:
    blob = blob + txtdb.read(BIGBLOCK)
    if not blob:
      # tag not present at all -- warn about that, then
      return
    where = blob.find('\n' + tag)
    if where != -1:  # found it!
      blob = blob[where+1:] + txtdb.readline()
      break
    blob = blob[-len(tag):]
  # now make a by-line iterator over the part of interest
  thelines = itertools.chain(blob.splitlines(1), txtdb)
  sline = next(thelines, '')
  while sline.startswith(tag):
    data = []
    data.append(sline)
    sline = next(thelines, '')
    while not sline.startswith(tag):
      data.append(sline)
      sline = next(thelines, '')
      if not sline:
        break
    customernum = getitem(data, "customernum:  ")
    street = getitem(data, "street:  ")
    country = getitem(data, "country:  ")
    zip = getitem(data, "zip:  ")

在这里,我试图尽可能多地保持您的结构完好无损,仅在此重构的“大创意”之外进行微小的增强。

于 2009-09-12T16:10:31.543 回答
1

我猜你正在编写这个导入脚本,在测试它的过程中等待很无聊,所以数据一直保持不变。

您可以运行脚本一次以检测要跳转到的文件中的实际位置,使用print txtdb.tell(). 写下这些并将搜索代码替换为txtdb.seek( pos ). 基本上这是为文件建立索引;-)

另一种更传统的方法是以更大的块读取数据,一次几 MB,而不仅仅是一行上的几个字节。

于 2009-09-12T15:40:52.633 回答
0

这可能会有所帮助:Python Performance Part 2: Parsing Large Strings for 'A Href' Hypertext

于 2009-09-12T15:40:33.150 回答
0

告诉我们有关该文件的更多信息。

您可以使用 file.seek 进行二进制搜索吗?寻找到一半,阅读几行,确定你是在你需要的部分之前还是之后,递归。这会将您的 O(n) 搜索变为 O(logn)。

于 2009-09-12T15:42:35.453 回答