如何在 Python 中连接文本文件?

Posted

技术标签:

【中文标题】如何在 Python 中连接文本文件?【英文标题】:How do I concatenate text files in Python? 【发布时间】:2012-11-16 19:00:21 【问题描述】:

我有一个包含 20 个文件名的列表,例如 ['file1.txt', 'file2.txt', ...]。我想编写一个 Python 脚本来将这些文件连接成一个新文件。我可以通过f = open(...) 打开每个文件,通过调用f.readline() 逐行读取,然后将每一行写入新文件。这对我来说似乎不是很“优雅”,尤其是我必须逐行读/写的部分。

有没有更“优雅”的方式在 Python 中做到这一点?

【问题讨论】:

它不是 python,但在 shell 脚本中你可以做类似cat file1.txt file2.txt file3.txt ... > output.txt 的事情。在 python 中,如果你不喜欢readline(),总是有readlines() 或简单的read() @jedwards 只需使用subprocess 模块运行cat file1.txt file2.txt file3.txt 命令就可以了。但我不确定cat 是否适用于 Windows。 请注意,您描述的方式是读取文件的糟糕方式。使用with 语句确保您的文件正确关闭,并遍历文件以获取行,而不是使用f.readline() @jedwards cat 在文本文件为 unicode 时不起作用。 实战分析waymoot.org/home/python_string 【参考方案1】:

应该这样做

对于大文件:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            for line in infile:
                outfile.write(line)

对于小文件:

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for fname in filenames:
        with open(fname) as infile:
            outfile.write(infile.read())

……还有一个我想到的有趣的

filenames = ['file1.txt', 'file2.txt', ...]
with open('path/to/output/file', 'w') as outfile:
    for line in itertools.chain.from_iterable(itertools.imap(open, filnames)):
        outfile.write(line)

遗憾的是,最后一种方法留下了一些打开的文件描述符,GC 无论如何都应该处理这些描述符。我只是觉得这很有趣

【讨论】:

这对于大文件来说内存效率非常低。 我们认为文件是什么? @dee: 一个文件太大以至于它的内容不适合主内存 你为什么要对整个内容进行解码和重新编码?并在只需要连接文件时搜索换行符和所有不必要的东西。下面的shutil.copyfileobj 答案会快得多。 重申一下:这是错误的答案,shutil.copyfileobj 是正确的答案。【参考方案2】:

UNIX 命令有什么问题? (假设你不是在 Windows 上工作):

ls | xargs cat | tee output.txt 完成这项工作(如果需要,您可以使用子进程从 python 调用它)

【讨论】:

因为这是一个关于python的问题。 一般没有错,但是这个答案被打破了(不要将ls的输出传递给xargs,只需将文件列表直接传递给cat:cat * | tee output.txt)。 如果它也能插入文件名那就太好了。 @Deqing 指定输入文件名,可以使用cat file1.txt file2.txt | tee output.txt ... 您可以通过在命令末尾添加 1> /dev/null 来禁用发送到标准输出(在终端中打印)【参考方案3】:

查看 File 对象的 .read() 方法:

http://docs.python.org/2/tutorial/inputoutput.html#methods-of-file-objects

你可以这样做:

concat = ""
for file in files:
    concat += open(file).read()

或更“优雅”的python方式:

concat = ''.join([open(f).read() for f in files])

根据这篇文章:http://www.skymind.com/~ocrow/python_string/ 也是最快的。

【讨论】:

这将产生一个巨大的字符串,根据文件的大小,它可能大于可用内存。由于 Python 提供了对文件的轻松惰性访问,因此这是个坏主意。【参考方案4】:

如果文件不是很大:

with open('newfile.txt','wb') as newf:
    for filename in list_of_files:
        with open(filename,'rb') as hf:
            newf.write(hf.read())
            # newf.write('\n\n\n')   if you want to introduce
            # some blank lines between the contents of the copied files

如果文件太大而无法完全读取并保存在 RAM 中,则算法必须稍有不同,以按固定长度的块在循环中读取要复制的每个文件,例如使用 read(10000)

【讨论】:

@Lattyware 因为我很确定执行速度更快。顺便说一句,事实上,即使代码命令逐行读取文件,文件也是按块读取的,这些块被放入缓存中,然后逐行读取每一行。更好的过程是将读取块的长度设置为等于缓存的大小。但是我不知道如何确定这个缓存的大小。 这是 CPython 中的实现,但没有任何保证。像这样进行优化是个坏主意,因为虽然它可能对某些系统有效,但可能对其他系统无效。 是的,当然逐行读取是缓冲的。这正是它没有那么慢的原因。 (事实上​​,在某些情况下,它甚至可能会稍微快一些,因为无论谁将 Python 移植到您的平台,都选择了比 10000 更好的块大小。)如果这真的很重要,您将不得不分析不同的实现。但是在 99.99...% 的情况下,无论哪种方式都足够快,或者实际的磁盘 I/O 是慢速部分,与您的代码做什么无关。 另外,如果你真的需要手动优化缓冲,你会想要使用os.openos.read,因为普通的open 使用 Python 的 C 的 stdio 包装器,这意味着要么1 或 2 个额外的缓冲区妨碍您。 PS,至于为什么 10000 不好:您的文件可能在磁盘上,块的长度为字节长。假设它们是 4096 字节。因此,读取 10000 字节意味着读取两个块,然后是下一个块的一部分。再读取 10000 意味着读取下一个块的其余部分,然后是两个块,然后是下一个块的一部分。计算你有多少部分或完整的块读取,你浪费了很多时间。幸运的是,Python、stdio、文件系统和内核缓冲和缓存将隐藏大部分这些问题,但为什么首先尝试创建它们呢?【参考方案5】:

这正是fileinput 的用途:

import fileinput
with open(outfilename, 'w') as fout, fileinput.input(filenames) as fin:
    for line in fin:
        fout.write(line)

对于这个用例,它实际上并不比手动迭代文件简单得多,但在其他情况下,使用一个迭代器来迭代所有文件,就像它们是一个文件一样非常方便。 (另外,fileinput 会在每个文件完成后立即关闭它,这意味着不需要withclose 每个文件,但这只是节省了一行,没什么大不了的。)

fileinput 中还有其他一些不错的功能,例如只需过滤每一行即可对文件进行就地修改。


如 cmets 中所述,并在另一个 post 中讨论过,Python 2.7 的 fileinput 将无法正常工作。此处稍作修改以使代码符合 Python 2.7 标准

with open('outfilename', 'w') as fout:
    fin = fileinput.input(filenames)
    for line in fin:
        fout.write(line)
    fin.close()

【讨论】:

@Lattyware:我认为大多数了解fileinput 的人都被告知这是一种将简单的sys.argv(或optparse/etc. 之后的args)转换为用于琐碎脚本的大虚拟文件,不要考虑将其用于其他任何事情(即,当列表不是命令行参数时)。或者他们确实学习了,但后来忘记了——我每隔一两年就会重新发现它…… @abament 我认为for line in fileinput.input() 不是在这种特殊情况下选择的最佳方式:OP 想要连接文件,而不是逐行读取它们,这在理论上是一个更长的执行过程 @eyquem:这不是一个更长的执行过程。正如您自己指出的那样,基于行的解决方案不会一次读取一个字符。他们读取块并从缓冲区中拉出行。 I/O 时间将完全淹没行解析时间,所以只要实现者在缓冲中没有做一些非常愚蠢的事情,它就会一样快(甚至可能比试图猜测一个好的缓冲区更快)如果您认为 10000 是一个不错的选择,请自行调整大小)。 @abarnert NO,10000 不是一个好的选择。这确实是一个非常糟糕的选择,因为它不是 2 的幂,而且它的尺寸小得离谱。更好的尺寸是 2097152 (221)、16777216 (224) 甚至 134217728 (2**27) ,为什么不呢?,在 4 GB 的 RAM 中,128 MB 算不了什么。 示例代码对 Python 2.7.10 及更高版本不太有效:***.com/questions/30835090/…【参考方案6】:
def concatFiles():
    path = 'input/'
    files = os.listdir(path)
    for idx, infile in enumerate(files):
        print ("File #" + str(idx) + "  " + infile)
    concat = ''.join([open(path + f).read() for f in files])
    with open("output_concatFile.txt", "w") as fo:
        fo.write(path + concat)

if __name__ == "__main__":
    concatFiles()

【讨论】:

【参考方案7】:

我不知道优雅,但这有效:

    import glob
    import os
    for f in glob.glob("file*.txt"):
         os.system("cat "+f+" >> OutFile.txt")

【讨论】:

你甚至可以避免循环:import os; os.system("cat 文件*.txt >> OutFile.txt") 不是跨平台的,会因文件名中包含空格而中断 这是不安全的;另外,cat 可以获取文件列表,因此无需重复调用。您可以通过调用subprocess.check_call 而不是os.system 轻松确保安全【参考方案8】:

使用shutil.copyfileobj

它会自动为您逐块读取输入文件,效率更高,并且即使某些输入文件太大而无法放入内存,也可以将输入文件读取并工作:

import shutil

with open('output_file.txt','wb') as wfd:
    for f in ['seg1.txt','seg2.txt','seg3.txt']:
        with open(f,'rb') as fd:
            shutil.copyfileobj(fd, wfd)

【讨论】:

for i in glob.glob(r'c:/Users/Desktop/folder/putty/*.txt'): 好吧,我替换了 for 语句以包含目录中的所有文件,但我的 output_file 在非常快的时间内开始变得非常巨大,就像 100 的 gb 一样。 注意,如果没有 EOL 字符,这会将每个文件的最后一个字符串与下一个文件的第一个字符串合并。就我而言,使用此代码后我得到了完全损坏的结果。我在 copyfileobj 之后添加了 wfd.write(b"\n") 以获得正常结果 @Thelambofgoat 我会说在这种情况下这不是一个纯粹的串联,但是,嘿,任何适合你的需要。 这是迄今为止最好的答案! 这是超快的,符合我的要求。是的,它没有在“两个文件结束和开始”之间添加新行,而这正是我需要的。所以不要更新它:D【参考方案9】:

@inspectorG4dget 答案的替代方法(截至 2016 年 3 月 29 日的最佳答案)。我用 3 个 436MB 的文件进行了测试。

@inspectorG4dget 解决方案:162 秒

以下解决方案:125 秒

from subprocess import Popen
filenames = ['file1.txt', 'file2.txt', 'file3.txt']
fbatch = open('batch.bat','w')
str ="type "
for f in filenames:
    str+= f + " "
fbatch.write(str + " > file4results.txt")
fbatch.close()
p = Popen("batch.bat", cwd=r"Drive:\Path\to\folder")
stdout, stderr = p.communicate()

这个想法是利用“古老的好技术”创建一个批处理文件并执行它。它的半蟒蛇,但工作得更快。适用于窗户。

【讨论】:

【参考方案10】:

如果目录中有很多文件,那么glob2 可能是生成文件名列表而不是手动编写文件名列表的更好选择。

import glob2

filenames = glob2.glob('*.txt')  # list of all .txt files in the directory

with open('outfile.txt', 'w') as f:
    for file in filenames:
        with open(file) as infile:
            f.write(infile.read()+'\n')

【讨论】:

这与问题有什么关系?为什么要使用glob2 而不是glob 模块或pathlib 中的通配功能?【参考方案11】:
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

一个简单的基准测试表明shutil表现更好。

【讨论】:

【参考方案12】:
  import os
  files=os.listdir()
  print(files)
  print('#',tuple(files))
  name=input('Enter the inclusive file name: ')
  exten=input('Enter the type(extension): ')
  filename=name+'.'+exten
  output_file=open(filename,'w+')
  for i in files:
    print(i)
    j=files.index(i)
    f_j=open(i,'r')
    print(f_j.read())
    for x in f_j:
      outfile.write(x)

【讨论】:

以上是关于如何在 Python 中连接文本文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中检测文件是不是为二进制(非文本)文件?

如何在python中拆分一个巨大的文本文件

如何使用连接作为文件向文本文件添加行

Python - 如何在文本文件中制作字典?

如何利用Python给文本文件中的每个人的姓名加上拼音?

如何在Python中读取压缩文件夹中的文本文件