如何实现Pythonic行续读

问题描述 投票:0回答:2

我正在尝试实现一个 Python 脚本来从 ASCII 文本文件中读取和提取行。这似乎是一件相当容易的事情,但是我最终遇到了一个我自己无法解决的问题。

我尝试读取的文件包含测试,其中一些行以

*tr999
开头。该模式可以有大写字母或小写字母,数字的数量和
*
的存在是可选的。星号也可以在之前和之后。该信号关键字后跟数字(int 或 float)。为了捕获信号,我使用 Python 正则表达式。

re.search("[*]{0,1}[Tt][Rr][0-9]{1,5}[*]{0,1}", line)

文本文件看起来像这样

tr10* 1 2 3 22 1 1 13 12 33 33 33
*Tr20 12 22 -1 2  2 2 5 5 5 6 6 6 77
Tr20 1 1 1 &
           2 0 0
           1 1 1
           2 2 2
c that is a comment and below is the problem case '&' is missing
*tr22221 2 2 2
         1 1 1
         2 2 2

我写的代码无法捕获最后一种情况。缺少连续线信号

&
的地方。使用
&
续行是可选的,可以用续行开头的多个空格替换。

我写的代码是

import sys

fp = open(sys.argv[1], 'r')
import re

# Get the integers only
def loop_conv(string):
        conv = []
        for i in string.split(" "):
            try:
                conv.append(float(i))
            except ValueError:
                pass
        return conv

# Extract the information
def extract_trans_card(line, fp):
            extracted = False
            if len(line) > 2 and not re.search("[cC]", line.split()[0]) and re.search("[*]{0,1}[Tt][Rr][0-9]{1,5}[*]{0,1}", line):
                extracted = True
                trans_card = []
                trans_card.append(line.split()[0])
                line_old = line
   # This part here is because after the read signal,
   # data to be extracted might be on the same line
                for val in loop_conv(line):
                        trans_card.append(val)
# This part here fails. I am not able to catch the case '&' missing.
# I tried to peek the next line with seek(), but I got a system error.
# The idea is to loop until I have a continue line case
                while (re.search("^(\s){5,60}", line) or re.search("[&$]", line_old)) and len(trans_card) < 13:

                    line = fp.readline()
                    for val in loop_conv(line):
                        trans_card.append(val)
                    line_old = line

                #print('M', trans_card)
                print('value', trans_card)
                trans_card = []
            return extracted



# Read the file with a loop
for line in fp:
        if not extract_trans_card(line, fp):
            print(line, end='')

输出为:

value ['tr10*', 1.0, 2.0, 3.0, 22.0, 1.0, 1.0, 13.0, 12.0, 33.0, 33.0, 33.0]
value ['*Tr20', 12.0, 22.0, -1.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 6.0, 6.0, 6.0, 77.0]
value ['Tr20', 1.0, 1.0, 1.0, 2.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
    c that is a comment and below is the problem case '&' is missing
value ['*tr22221', 2.0, 2.0, 2.0]
             1 1 1
             2 2 2

最后一行是问题所在。由于

1 1 1 
2 2 2
被忽略并且只是回显。

这个问题看起来与 Python 的 continue line 类似。通过空格或使用

&

我希望有人能帮助我们解决这个问题,并指出解决这个问题的正确方法。

python regex python-3.x file
2个回答
1
投票

代码工作流程的问题是,当连续行信号是可选的时,很难检测到与 current trans_card 关联的最后一行而不弄乱下一个 trans_card

由于可以使用

re.search(r"[*]?[Tt][Rr][0-9]{1,5}[*]?"
找到 trans_card 的开头(标头),因此每当检测到此 header 模式时,处理 previous trans_card 会更容易。

下面是我从您的代码逻辑中粗略复制的示例代码,并将生成的 trans_card 保存到列表列表中:

import sys
import re

# get the floats only from line, copied from your code
def loop_conv(string):
    conv=[]
    for i in string.split(" "):
      try:
        conv.append(float(i))
      except ValueError:
        pass
    return conv

# set previous trans_card with non-EMPTY vals list
def set_prev_trans_card(card, vals):
    if len(vals):
        card.append(vals)
        #print ('value: {}'.format(vals))

# below new code logic:
with open(sys.argv[1], 'r') as fp:
    trans_card = []

    # a list to save items retrieved from lines associated with the same trans_card
    values = []

    # set up a flag to identify header
    is_header = 0

    for line in fp:
        # if line is a comment, then skip it 
        if re.search("[cC]",line.split()[0]):
            #print(line, end='')
            continue

        # if line is a header, append the existing values[] (from the previous trans_card) 
        # list to trans_card[] and then reset values[]
        if len(line)>2 and re.search(r"[*]?[Tt][Rr][0-9]{1,5}[*]?", line):
            # append values[] to trans_card
            set_prev_trans_card(trans_card, values)

            # reset values[] to the first \S+ on the header 
            values = [ line.split()[0] ]

            # set is_header flag to 1
            is_header = 1

        # if line ends with &\n, then concatenate the next lines
        while line.endswith('&\n'):
            line += ' ' + fp.readline()

        # add all numbers(floats) from header or lines starts with 5-60 white-spaces into the values[] list, and reset is_header flag to 0
        if is_header or re.search("^(\s){5,60}",line):
            values.extend(loop_conv(line))
            is_header = 0

    # append the last values[] to trans_card
    set_prev_trans_card(trans_card, values)

for v in trans_card:
    print ('value: {}'.format(v))

输出为:

value: ['tr10*', 1.0, 2.0, 3.0, 22.0, 1.0, 1.0, 13.0, 12.0, 33.0, 33.0, 33.0]
value: ['*Tr20', 12.0, 22.0, -1.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 6.0, 6.0, 6.0, 77.0]
value: ['Tr20', 1.0, 1.0, 1.0, 2.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
value: ['*tr22221', 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]

注意:我在代码中跳过了

len(trans_card) <13
的条件,认为它只是用于防止无限循环。如果没有,应该很容易添加到上面的示例代码中。

顺便说一句。您可能需要将

^
添加到注释和标题的模式中,以便它们仅匹配字符串的开头而不是搜索字符串中的任何位置。


0
投票

这是处理文件的 Pythonic 方法(实际上是任何可迭代的文件,其中 next() 项返回字符串,可能以换行符结尾或不以换行符结尾),其中可以通过最后一列中的 '&' 指定继续当前“记录”的(Python 实际上使用“\”)或下一个“记录”中的空格字符:

import re


def read_lines_with_continue(iter):
    """This function is passed an interator where each iteration returns the next line.
       This function processes logical continuations consisting of lines that end with '&' or lines
       that begin a space."""

    next_line = ''
    saw_continue = True
    for line in iter:
        # get rid of any trailing '&'
        edited_line = re.sub(r'&$', '', line)
        if saw_continue:
            next_line += edited_line
            saw_continue = False
        elif line[0] == ' ':
            next_line += edited_line
        elif next_line != '':
            yield next_line
            next_line = edited_line
        if line != edited_line:
            saw_continue = True
    if next_line != '':
        yield next_line


lines = [
    '1abc',
    '2def&',
    'ghi',
    ' xyz',
    ' ver&',
    'jkl',
    '3aaa',
    '4xxx',
    ' yyy'
]


# instead of passing a list, you could also pass a file
for l in read_lines_with_continue(lines):
    print(l)

1abc
2defghi xyz verjkl
3aaa
4xxx yyy
© www.soinside.com 2019 - 2024. All rights reserved.