ChatGPT解决这个技术问题 Extra ChatGPT

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

我有一个包含 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()
当文本文件是 unicode 时,@jedwards cat 不起作用。

i
inspectorG4dget

这应该这样做

对于大文件:

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 是正确的答案。
J
Jeyekomon

使用 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
N
Novice C

这正是 fileinput 的用途:

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

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

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

如评论中所述,以及在另一个 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 不,10000 不是一个好选择。这确实是一个非常糟糕的选择,因为它不是 2 的幂,而且它的尺寸小得离谱。更好的尺寸是 2097152 (221)、16777216 (224) 甚至 134217728 (2**27) ,为什么不呢?,在 4 GB 的 RAM 中,128 MB 什么都不是。
示例代码对 Python 2.7.10 及更高版本不太有效:stackoverflow.com/questions/30835090/…
D
Daniel

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

    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 轻松确保安全
l
lucasg

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 来禁用发送到标准输出(在终端中打印)
C
Clint Chelak
outfile.write(infile.read()) # time: 2.1085190773010254s
shutil.copyfileobj(fd, wfd, 1024*1024*10) # time: 0.60599684715271s

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


J
João Palma

@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()

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


M
Michael H.

如果目录中有很多文件,那么 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 中的通配功能?
A
Alex Kawrykow

查看 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 提供了对文件的轻松惰性访问,因此这是一个坏主意。
e
eyquem

如果文件不是很大:

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、文件系统以及内核缓冲和缓存将隐藏大部分这些问题,但为什么要首先尝试创建它们呢?
u
user2825287
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()

V
VasanthOPT
  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)