如何在Python中从文件中逐个读取字符?

96

在Python中,给定一个文件的名称,如何编写一个循环,每次通过循环读取一个字符?

15个回答

110
with open(filename) as f:
    while True:
        c = f.read(1)
        if not c:
            print("End of file")
            break
        print("Read a character:", c)

43
因为这是逐字节读取,所以对于非ASCII编码会失败,你同意吗? - David Chouinard
3
问题和答案混淆了字符和字节的概念。如果文件采用每个字符一个字节的编码方式,比如ASCII和许多其他编码方式,那么读取一个字节大小的块就是读取一个字符,否则,如果编码需要超过一个字节才能表示一个字符,那么你只读取了一个字节而不是一个完整的字符。 - Basel Shishani
2
没错。因此,我经常执行 result = open(filename).read() 然后逐个字符读取 result - Shravan
5
回答 David Chouinard 的问题:这个代码片段在使用 UTF-8 编码的文件中可以在 Python 3 中正常工作。如果你有一个使用 Windows-1250 编码的文件,只需将第一行更改为: with open(filename, encoding='Windows-1250') as f: - SergO
1
另外,补充一下SergO的观点,open(filename, "r")open(filename, "rb")之间的区别可能会导致不同数量的迭代(至少在Python 3中是这样)。如果遇到适当的特殊字符,“r”模式可以读取多个字节以获取c - dcc310
显示剩余2条评论

45

首先,打开一个文件:

with open("filename") as fileobj:
    for line in fileobj:  
       for ch in line: 
           print(ch)

此代码逐行读取文件中的每一行,然后遍历该行中的每个字符。


同意,这似乎是更符合Python风格的方法。 这样做是否也能处理非ASCII编码呢? - Ron7
21
你可能需要逐个字符地读取文件的一个原因是文件太大而无法放入内存。但上文的回答假设每行都可以放入内存。 - C S
已编辑以匹配Python 3。 - user11462042
1
由于OP从未提到一次一个字符地阅读整个文件,因此这种方法是非最优的,因为整个文件可能包含在单行中;在这种情况下,在进行字符处理之前,读取整行需要花费相当长的时间。最好在这些情况下对部分读取使用f.read(1)。 - owl7
-1. 附议@C S的评论。 OP问如何“一次读取一个字符”,所以这并没有回答问题。这不比被接受的答案更简单,最好有一个函数,它不会有时不必要地崩溃你的脚本/应用程序。 如果是一个完整表的SQL INSERT呢?或者使用非本地换行符?最好的情况是低效的缓冲;最坏的情况是内存耗尽。 - Douglas Myers-Turnbull

16

我喜欢被接受的答案:它简单明了且能够完成任务。我也想提供一个替代实现:

def chunks(filename, buffer_size=4096):
    """Reads `filename` in chunks of `buffer_size` bytes and yields each chunk
    until no more characters can be read; the last chunk will most likely have
    less than `buffer_size` bytes.

    :param str filename: Path to the file
    :param int buffer_size: Buffer size, in bytes (default is 4096)
    :return: Yields chunks of `buffer_size` size until exhausting the file
    :rtype: str

    """
    with open(filename, "rb") as fp:
        chunk = fp.read(buffer_size)
        while chunk:
            yield chunk
            chunk = fp.read(buffer_size)

def chars(filename, buffersize=4096):
    """Yields the contents of file `filename` character-by-character. Warning:
    will only work for encodings where one character is encoded as one byte.

    :param str filename: Path to the file
    :param int buffer_size: Buffer size for the underlying chunks,
    in bytes (default is 4096)
    :return: Yields the contents of `filename` character-by-character.
    :rtype: char

    """
    for chunk in chunks(filename, buffersize):
        for char in chunk:
            yield char

def main(buffersize, filenames):
    """Reads several files character by character and redirects their contents
    to `/dev/null`.

    """
    for filename in filenames:
        with open("/dev/null", "wb") as fp:
            for char in chars(filename, buffersize):
                fp.write(char)

if __name__ == "__main__":
    # Try reading several files varying the buffer size
    import sys
    buffersize = int(sys.argv[1])
    filenames  = sys.argv[2:]
    sys.exit(main(buffersize, filenames))

我建议的代码本质上与您接受的答案相同:从文件中读取指定数量的字节。不同之处在于它首先读取一块好的数据(4006对于X86来说是一个很好的默认值,但您可能想尝试1024或8192;任何页面大小的倍数),然后一次一个地生成该块中的字符。

我提出的代码可能对于较大的文件更快。例如,以托尔斯泰的《战争与和平》整篇文本为例。这是我的计时结果(Mac Book Pro使用OS X 10.7.4; so.py是我给粘贴的代码起的名字):

$ time python so.py 1 2600.txt.utf-8
python so.py 1 2600.txt.utf-8  3.79s user 0.01s system 99% cpu 3.808 total
$ time python so.py 4096 2600.txt.utf-8
python so.py 4096 2600.txt.utf-8  1.31s user 0.01s system 99% cpu 1.318 total

现在,请不要把缓冲区大小 4096 视为普遍真理;看一下我对于不同大小所得到的结果(缓冲区大小(字节)与墙壁时间(秒)):

   2 2.726 
   4 1.948 
   8 1.693 
  16 1.534 
  32 1.525 
  64 1.398 
 128 1.432 
 256 1.377 
 512 1.347 
1024 1.442 
2048 1.316 
4096 1.318 

正如你所看到的,你可以更早地开始看到收益(而我的时间可能非常不准确);缓冲区大小是性能和内存之间的权衡。4096的默认值只是一个合理的选择,但始终要先进行测量。


8

只需:

myfile = open(filename)
onecharacter = myfile.read(1)

8

Python本身可以在交互模式下帮助您完成此操作:

>>> help(file.read)
Help on method_descriptor:

read(...)
    read([size]) -> read at most size bytes, returned as a string.

    If the size argument is negative or omitted, read until EOF is reached.
    Notice that when in non-blocking mode, less data than what was requested
    may be returned, even if no size parameter was given.

6
我同意这种情感,但也许最好将其作为对原帖的评论呈现? - Mike Boers
2
可能吧,但我认为所有这些文本放在注释中看起来会很混乱。 - Mattias Nilsson

4

今天我在观看 Raymond Hettinger 的 将代码转换成美丽、惯用的 Python 视频时学到了一个新的成语:

import functools

with open(filename) as f:
    f_read_ch = functools.partial(f.read, 1)
    for ch in iter(f_read_ch, ''):
        print 'Read a character:', repr(ch) 

3

Python 3.8+的最佳答案:

with open(path, encoding="utf-8") as f:
    while c := f.read(1):
        do_my_thing(c)

您可能希望指定utf-8编码并避免使用平台编码。我选择在此处执行此操作。

功能 - Python 3.8+:

def stream_file_chars(path: str):
    with open(path) as f:
        while c := f.read(1):
            yield c

函数 - Python<=3.7:

def stream_file_chars(path: str):
    with open(path, encoding="utf-8") as f:
        while True:
            c = f.read(1)
            if c == "":
                break
            yield c

Function – pathlib + documentation:

from pathlib import Path
from typing import Union, Generator

def stream_file_chars(path: Union[str, Path]) -> Generator[str, None, None]:
    """Streams characters from a file."""
    with Path(path).open(encoding="utf-8") as f:
        while (c := f.read(1)) != "":
            yield c

2

只读取一个字符

f.read(1)

2
这也是可行的:
with open("filename") as fileObj:
    for line in fileObj:  
        for ch in line:
            print(ch)

它会遍历文件中的每一行,以及每一行中的每个字符。

(请注意,尽管现在这篇文章看起来与一个高赞答案非常相似,但在撰写时并非如此。)


1
这是一种不好的通用方法,因为它会将潜在的大行加载到内存中。此外,它并不比已接受的答案更简单。如果它是一个1000亿长度的核苷酸序列(ATGC)呢?或者是一个完整表的SQL插入语句?或者使用非本地换行符?最好的情况是低效的缓冲;最坏的情况是内存耗尽。 - Douglas Myers-Turnbull
非常正确;这并不高效。但对于初学Python的人来说,这通常是一种易于理解的简单for循环方法。 - Pro Q

0
你应该尝试使用f.read(1),这绝对是正确的做法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接