代码之家  ›  专栏  ›  技术社区  ›  Basj

splitlines()和迭代打开的文件会得到不同的结果

  •  0
  • Basj  · 技术社区  · 5 年前

    我的文件有时会有奇怪的行尾字符,比如 \r\r\n 。有了这个,它就像我想要的那样工作:

    with open('test.txt', 'wb') as f:  # simulate a file with weird end-of-lines
        f.write(b'abc\r\r\ndef')
    with open('test.txt', 'rb') as f:
        for l in f:
            print(l)
    # b'abc\r\r\n'         
    # b'def'
    

    我希望能够得到同样的结果 从字符串 我考虑过 splitlines 但它不会给出相同的结果:

    print(b'abc\r\r\ndef'.splitlines())
    # [b'abc', b'', b'def']
    

    即使有 keepends=True ,结果不一样。

    问题:如何有相同的行为 for l in f 具有 splitlines() ?

    链接: Changing str.splitlines to match file readlines https://bugs.python.org/issue22232

    注意:我不想把所有东西都放在 BytesIO StringIO ,因为它的速度性能为x0.5(已经过基准测试);我想保留一个简单的字符串。所以它不是 How do I wrap a string in a file in Python? .

    1 回复  |  直到 5 年前
        1
  •  1
  •   igrinis    5 年前

    你为什么不把它分开呢:

    input = b'\nabc\r\r\r\nd\ref\nghi\r\njkl'
    result = input.split(b'\n') 
    print(result)
    
    [b'', b'abc\r\r\r', b'd\ref', b'ghi\r', b'jkl']
    

    你会失去拖尾 \n 如果你真的需要,可以稍后添加到每一行。最后一行需要检查是否真的需要。喜欢

    fixed = [bstr + b'\n' for bstr in result]
    if input[-1] != b'\n':
        fixed[-1] = fixed[-1][:-1]
    print(fixed)
    
    [b'\n', b'abc\r\r\r\n', b'd\ref\n', b'ghi\r\n', b'jkl']
    

    另一种带有发电机的变体。这样,它将对巨大的文件进行内存管理,语法将与原始文件相似 for l in bin_split(input) :

    def bin_split(input_str):
        start = 0
        while start>=0 :
            found = input_str.find(b'\n', start) + 1
            if 0 < found < len(input_str):
                yield input_str[start : found]
                start = found
            else:
                yield input_str[start:]
                break
    
        2
  •  1
  •   Pi Marillion    5 年前

    有几种方法可以做到这一点,但没有一种方法特别快。

    如果你想保留行尾,你可以试试 re 模块:

    lines = re.findall(r'[\r\n]+|[^\r\n]+[\r\n]*', text)
    # or equivalently
    line_split_regex = re.compile(r'[\r\n]+|[^\r\n]+[\r\n]*')
    lines = line_split_regex.findall(text)
    

    如果你需要结尾,而文件真的很大,你可能想迭代:

    for r in re.finditer(r'[\r\n]+|[^\r\n]+[\r\n]*', text):
        line = r.group()
        # do stuff with line here
    

    如果你不需要结局,那么你可以更容易地做到这一点:

    lines = list(filter(None, text.splitlines()))
    

    您可以省略 list() 如果你只是迭代结果(或者使用Python2):

    for line in filter(None, text.splitlines()):
        pass # do stuff with line
    
        3
  •  0
  •   Matt Cottrill    5 年前

    我会这样迭代:

    text  = "b'abc\r\r\ndef'"
    
    results = text.split('\r\r\n')
    
    for r in results:
        print(r)
    
        4
  •  0
  •   Booboo    5 年前

    这是一个 for l in f: 解决方案:

    关键在于 newline 关于 open 电话。从文档中:

    [![在此处输入图像描述][1]][1]

    因此,您应该使用 newline='' 在写入时禁止换行翻译,然后在读取时使用 newline='\n' ,如果您的所有线路都以0或更多结尾,这将起作用 '\r' 字符后跟a '\n' 字符:

    with open('test.txt', 'w', newline='') as f:
        f.write('abc\r\r\ndef')
    with open('test.txt', 'r', newline='\n') as f:
        for line in f:
            print(repr(line))
    

    打印:

    'abc\r\r\n'
    'def'
    

    A. 准分割线 解决方案:

    严格来说,这不是 splitlines 解决方案,因为能够处理任意行尾的正则表达式版本 split 必须使用捕捉线条末端,然后重新组装线条及其末端。因此,这个解决方案只是使用正则表达式来分解输入文本,允许行尾由任意数量的 r 字符后跟a n 字符:

    import re
    
    input = '\nabc\r\r\ndef\nghi\r\njkl'
    
    with open('test.txt', 'w', newline='') as f:
        f.write(input)
    with open('test.txt', 'r', newline='') as f:
        text = f.read()
        lines = re.findall(r'[^\r\n]*\r*\n|[^\r\n]+$', text)
        for line in lines:
            print(repr(line))
    

    打印:

    '\n'
    'abc\r\r\n'
    'def\n'
    'ghi\r\n'
    'jkl'
    

    Regex Demo

    推荐文章