Python字符串操作——性能问题

Python字符串操作——性能问题,python,string,performance,Python,String,Performance,我在应用程序中执行了大约200万次以下代码来解析这么多记录。这一部分似乎是瓶颈,我想知道是否有人可以帮助我提出一些漂亮的技巧,使这些简单的字符串操作更快 try: data = [] start = 0 end = 0 for info in self.Columns(): end = start + (info.columnLength) slice = line[start:end] if slice == ''

我在应用程序中执行了大约200万次以下代码来解析这么多记录。这一部分似乎是瓶颈,我想知道是否有人可以帮助我提出一些漂亮的技巧,使这些简单的字符串操作更快

try:
    data = []
    start = 0
    end = 0
    for info in self.Columns():
        end = start + (info.columnLength)
        slice = line[start:end]
        if slice == '' or len(slice) != info.columnLength:
            raise 'Wrong Input'
        if info.hasSignage:
            if(slice[0:1].strip() != '+' and slice[0:1].strip() != '-'):
                raise 'Wrong Input'
        if not info.skipColumn:
            data.append(slice)
        start = end 
    parsedLine = data
except:
    parsedLine = False

不要每次通过此循环计算
start
end

在使用
self.Columns()
之前,只计算它们一次(不管是什么。如果'Columns'是带有静态值的类,那就太傻了。如果它是一个名称以大写字母开头的函数,那就很混乱。)

if slice==''或len(slice)!=信息.columnLength
仅当行与
所需的总大小相比太短时才会发生。检查一次,在回路外

slice[0:1].strip()!='+'确定看起来像
.startswith()


if not info.skipColumn
。在开始循环之前应用此过滤器。将这些从“代码>自我”栏中删除()/代码> < /p> < p>我首先考虑的是“代码>切片=行[开始:结束] < /代码>。切片创建新实例;您可以尝试避免显式构造
行[start:end]
,并手动检查其内容

你为什么要做
切片[0:1]
?这将产生一个包含单个
片段项的子序列(不应该吗?),因此可以更有效地检查它

怎么样(使用一些类来创建一个可执行示例):

编辑: 当切片的长度为0时,切片[0]不存在,因此必须首先检查
如果collength==0

编辑2: 您在许多行中使用这段代码,但列信息不会更改,对吗?那就让你,去,

  • 预先计算每个柱的起点列表(不再需要计算起点、终点)
  • 提前知道开始和结束,.Columns()只需要返回未被跳过且columnlength>0的列(或者您真的需要在每一行为length==0进行输入吗??)
  • 每条线的下颌骨长度已知且相等,或者每条线的下颌骨长度相等,可以在绕过列信息之前进行检查
编辑3:
我想知道如果您使用'skipColumn',您将如何知道什么数据索引属于哪个列……

我想告诉您使用某种内置Python功能来拆分字符串,但我想不出一个。所以我只剩下尽量减少代码量了

完成后,
end
应该指向字符串的末尾;如果是这种情况,则所有
.columnLength
值都必须正常。(除非其中一个是否定的或什么的!)

因为它引用了
self
,所以它必须是来自成员函数的剪报。因此,您不必引发异常,只需
返回False
即可提前退出函数并返回错误标志。但是我喜欢将
except
子句更改为不再捕获异常,并获取堆栈跟踪以确定问题的来源的调试潜力

@Remi在'+-'中使用了
slice[0],其中我使用了
slice.startswith(('+','-)
。我想我更喜欢@Remi的代码,但我的代码没有改变,只是为了给大家展示一种不同的方式。
.startswith()
方法适用于长度大于1的字符串,但由于这只是长度为1的字符串,因此简洁的解决方案有效

try:
    line = line.strip('\n')
    data = []
    start = 0
    for info in self.Columns():
        end = start + info.columnLength
        slice = line[start:end]
        if info.hasSignage and not slice.startswith(('+', '-')):
            raise ValueError, "wrong input"
        if not info.skipColumn:
            data.append(slice)
        start = end

    if end - 1 != len(line):
        raise ValueError, "bad .columnLength"

    parsedLine = data

except ValueError:
    parsedLine = False

编辑:我对这个答案做了一些修改。我将在下面留下原始答案

在我的另一个回答中,我评论说最好的办法是找到一个内置的Python模块来进行解包。我想不出一个,但也许我应该在谷歌上搜索一个@John Machin提供了一个说明如何实现的答案:使用Python
struct
模块。因为它是用C编写的,所以应该比我的纯Python解决方案快。(我实际上没有测量任何东西,所以这只是猜测。)

我同意原始代码中的逻辑是“非Pythonic的”。返回sentinel值不是最好的;最好返回有效值或引发异常。另一种方法是返回一个有效值列表,再加上另一个无效值列表。既然@johnmachin提供了生成有效值的代码,我想我应该在这里编写一个返回两个列表的版本

注意:也许最好的答案是采用@John Machin的答案并对其进行修改,以将无效值保存到一个文件中,供以后可能的审查。他的答案一次只能给出一个答案,因此无需构建大量解析记录列表;将坏行保存到磁盘意味着不需要建立一个可能很大的坏行列表

import struct

def parse_records(self):
    """
    returns a tuple: (good, bad)
    good is a list of valid records (as tuples)
    bad is a list of tuples: (line_num, line, err)
    """

    cols = self.Columns()
    unpack_fmt = ""
    sign_checks = []
    start = 0
    for colx, info in enumerate(cols, 1):
        clen = info.columnLength
        if clen < 1:
            raise ValueError("Column %d: Bad columnLength %r" % (colx, clen))
        if info.skipColumn:
            unpack_fmt += str(clen) + "x"
        else:
            unpack_fmt += str(clen) + "s"
            if info.hasSignage:
                sign_checks.append(start)
        start += clen
    expected_len = start
    unpack = struct.Struct(unpack_fmt).unpack

    good = []
    bad = []
    for line_num, line in enumerate(self.whatever_the_list_of_lines_is, 1):
        if len(line) != expected_len:
            bad.append((line_num, line, "bad length"))
            continue
        if not all(line[i] in '+-' for i in sign_checks):
            bad.append((line_num, line, "sign check failed"))
            continue
        good.append(unpack(line))

    return good, bad
raise
语句中有不同的错误消息是没有意义的;他们从未见过。嘘#3

更新:这里有一个建议的改进,它使用
struct.unpack
快速划分输入行。它还演示了更好的异常处理,假设代码编写者也在运行它,并且在第一个错误时停止是可以接受的。另一个问题是,一个健壮的实现可以为用户用户记录所有行的所有列中的所有错误。请注意,通常情况下,每列的错误检查会更广泛,例如,检查前导符号,但不检查列是否包含有效数字似乎有点奇怪

import struct

def unpacked_records(self):
    cols = self.Columns()
    unpack_fmt = ""
    sign_checks = []
    start = 0
    for colx, info in enumerate(cols, 1):
        clen = info.columnLength
        if clen < 1:
            raise ValueError("Column %d: Bad columnLength %r" % (colx, clen))
        if info.skipColumn:
            unpack_fmt += str(clen) + "x"
        else:
            unpack_fmt += str(clen) + "s"
            if info.hasSignage:
                sign_checks.append(start)
        start += clen
    expected_len = start
    unpack = struct.Struct(unpack_fmt).unpack

    for linex, line in enumerate(self.whatever_the_list_of_lines_is, 1):
        if len(line) != expected_len:
            raise ValueError(
                "Line %d: Actual length %d, expected %d"
                % (linex, len(line), expected_len))
        if not all(line[i] in '+-' for i in sign_checks):
            raise ValueError("Line %d: At least one column fails sign check" % linex)
        yield unpack(line) # a tuple
导入结构
def未打包_记录(自我):
cols=self.Columns()
解包_fmt=“”
签署支票=[]
开始=0
对于colx,枚举中的信息(cols,1):
clen=info.columnLength
如果clen<1:
raise VALUERROR(“列%d:错误的列长度%r”%(colx,clen))
如果info.skipColumn:
解包_fmt+=str(clen)+“x”
其他:
def parse_records(self):
    cols = self.Columns()

    slices = []
    sign_checks = []
    start = 0
    for info in cols:
        if info.columnLength < 1:
            raise ValueError, "bad columnLength"
        end = start + info.columnLength
        if not info.skipColumn:
            tup = (start, end)
            slices.append(tup)   
            if info.hasSignage:
                sign_checks.append(start)

    expected_len = end # or use (end - 1) to not count a newline

    try:
        for line in self.whatever_the_list_of_lines_is:
            if len(line) != expected_len:
                raise ValueError, "wrong length"
            if not all(line[i] in '+-' for i in sign_checks):
                raise ValueError, "wrong input"
            parsedLine = [line[s:e] for s, e in slices]

    except ValueError:
        parsedLine = False
def fubarise(data):
    try:
        if nasty(data):
            raise ValueError("Look, Ma, I'm doing a big fat GOTO ...") # sheesh #1
        more_of_the_same()
        parsed_line = data
    except ValueError:
        parsed_line = False
        # so it can be a "data" or False -- sheesh #2
    return parsed_line
import struct

def unpacked_records(self):
    cols = self.Columns()
    unpack_fmt = ""
    sign_checks = []
    start = 0
    for colx, info in enumerate(cols, 1):
        clen = info.columnLength
        if clen < 1:
            raise ValueError("Column %d: Bad columnLength %r" % (colx, clen))
        if info.skipColumn:
            unpack_fmt += str(clen) + "x"
        else:
            unpack_fmt += str(clen) + "s"
            if info.hasSignage:
                sign_checks.append(start)
        start += clen
    expected_len = start
    unpack = struct.Struct(unpack_fmt).unpack

    for linex, line in enumerate(self.whatever_the_list_of_lines_is, 1):
        if len(line) != expected_len:
            raise ValueError(
                "Line %d: Actual length %d, expected %d"
                % (linex, len(line), expected_len))
        if not all(line[i] in '+-' for i in sign_checks):
            raise ValueError("Line %d: At least one column fails sign check" % linex)
        yield unpack(line) # a tuple