it-swarm.com.de

Wie teile ich eine große Textdatei in Python auf?

Ich habe eine riesige Textdatei (~ 1 GB) und leider kann der Texteditor, den ich verwende, keine so große Datei lesen. Wenn ich es aber einfach in zwei oder drei Teile aufteilen kann, ist alles in Ordnung, also wollte ich als Übung ein Programm in Python schreiben, um es zu tun. 

Ich denke, ich möchte, dass das Programm die Größe einer Datei ermittelt, diese Zahl in Teile teilt und für jeden Teil bis zu diesem Punkt in Abschnitten liest und in eine Dateiname .nnn-Ausgabedatei schreibt. dann bis zum nächsten Zeilenumbruch lesen und schreiben, dann die Ausgabedatei schließen, usw. Die letzte Ausgabedatei wird natürlich nur bis zum Ende der Eingabedatei kopiert.

Können Sie mir bei den wichtigsten Teilen des Dateisystems helfen: Dateigröße, Lesen und Schreiben in Blöcken und Lesen in Zeilenumbrüche?

Ich schreibe diesen Code test-first, so dass ich keine vollständige Antwort geben muss, es sei denn, es ist ein Einzeiler ;-)

21
quamrana

Check out os.stat() für Dateigröße und file.readlines([sizehint]). Diese zwei Funktionen sollten alles sein, was Sie für den Leseteil benötigen, und hoffentlich wissen Sie, wie man das Schreiben macht :)

15
Kamil Kisiel

linux hat einen Split-Befehl

split -l 100000 datei.txt

würde in Dateien mit einer Zeilengröße von 100.000 aufgeteilt

31
James

Alternativ können Sie die Protokollierungsbibliothek verwenden:

>>> import logging.handlers
>>> log = logging.getLogger()
>>> fh = logging.handlers.RotatingFileHandler("D://filename.txt", 
     maxBytes=2**20*100, backupCount=100) 
# 100 MB each, up to a maximum of 100 files
>>> log.addHandler(fh)
>>> log.setLevel(logging.INFO)
>>> f = open("D://biglog.txt")
>>> while True:
...     log.info(f.readline().strip())

Ihre Dateien werden wie folgt angezeigt:

dateiname.txt (Ende der Datei)
Dateiname.txt.1
Dateiname.txt.2
...
Dateiname.txt.10 (Dateianfang)

Dies ist eine schnelle und einfache Möglichkeit, um eine große Protokolldatei an Ihre RotatingFileHandler-Implementierung anzupassen.

9
Alex L

Diese Generatormethode ist ein (langsamer) Weg, um ein Stück von Zeilen zu erhalten, ohne dabei den Speicher zu sprengen.

import itertools

def slicefile(filename, start, end):
    lines = open(filename)
    return itertools.islice(lines, start, end)

out = open("/blah.txt", "w")
for line in slicefile("/python27/readme.txt", 10, 15):
    out.write(line)
5
Ryan Ginstrom

Während Ryan Ginstroms Antwort richtig ist, dauert es länger als es sollte (wie er bereits bemerkt hat). So können Sie die mehrfachen Aufrufe von itertools.islice umgehen, indem Sie den geöffneten Dateideskriptor nacheinander durchlaufen:

def splitfile(infilepath, chunksize):
    fname, ext = infilepath.rsplit('.',1)
    i = 0
    written = False
    with open(infilepath) as infile:
        while True:
            outfilepath = "{}{}.{}".format(fname, i, ext)
            with open(outfilepath, 'w') as outfile:
                for line in (infile.readline() for _ in range(chunksize)):
                    outfile.write(line)
                written = bool(line)
            if not written:
                break
            i += 1
4
inspectorG4dget

vergessen Sie nicht seek () und mmap () für den wahlfreien Zugriff auf Dateien.

def getSomeChunk(filename, start, len):
    fobj = open(filename, 'r+b')
    m = mmap.mmap(fobj.fileno(), 0)
    return m[start:start+len]
4
Joe Koberg

Jetzt gibt es ein Pypi-Modul, mit dem Sie Dateien beliebiger Größe in Blöcke teilen können. Schau dir das an

https://pypi.org/project/filesplit/

4
Ram

Sie können wc und split (siehe die entsprechenden Manpages) verwenden, um den gewünschten Effekt zu erzielen. In bash:

split -dl$((`wc -l 'filename'|sed 's/ .*$//'` / 3 + 1)) filename filename-chunk.

erzeugt 3 Teile derselben Zeilenanzahl (natürlich mit Rundungsfehler), die filename-chunk.00 bis filename-chunk.02 heißen.

3
Svante

usage - split.py Dateiname splitsizeinkb

import os
import sys

def getfilesize(filename):
   with open(filename,"rb") as fr:
       fr.seek(0,2) # move to end of the file
       size=fr.tell()
       print("getfilesize: size: %s" % size)
       return fr.tell()

def splitfile(filename, splitsize):
   # Open original file in read only mode
   if not os.path.isfile(filename):
       print("No such file as: \"%s\"" % filename)
       return

   filesize=getfilesize(filename)
   with open(filename,"rb") as fr:
    counter=1
    orginalfilename = filename.split(".")
    readlimit = 5000 #read 5kb at a time
    n_splits = filesize//splitsize
    print("splitfile: No of splits required: %s" % str(n_splits))
    for i in range(n_splits+1):
        chunks_count = int(splitsize)//int(readlimit)
        data_5kb = fr.read(readlimit) # read
        # Create split files
        print("chunks_count: %d" % chunks_count)
        with open(orginalfilename[0]+"_{id}.".format(id=str(counter))+orginalfilename[1],"ab") as fw:
            fw.seek(0) 
            fw.truncate()# truncate original if present
            while data_5kb:                
                fw.write(data_5kb)
                if chunks_count:
                    chunks_count-=1
                    data_5kb = fr.read(readlimit)
                else: break            
        counter+=1 

if __== "__main__":
   if len(sys.argv) < 3: print("Filename or splitsize not provided: Usage:     filesplit.py filename splitsizeinkb ")
   else:
       filesize = int(sys.argv[2]) * 1000 #make into kb
       filename = sys.argv[1]
       splitfile(filename, filesize)
2
Mudit Verma

Ich habe das Programm geschrieben und es scheint gut zu funktionieren. Vielen Dank an Kamil Kisiel für den Einstieg.
(Beachten Sie, dass FileSizeParts () eine hier nicht gezeigte Funktion ist).
Später komme ich vielleicht zu einer Version, die binär gelesen wird, um zu sehen, ob sie schneller ist.

def Split(inputFile,numParts,outputName):
    fileSize=os.stat(inputFile).st_size
    parts=FileSizeParts(fileSize,numParts)
    openInputFile = open(inputFile, 'r')
    outPart=1
    for part in parts:
        if openInputFile.tell()<fileSize:
            fullOutputName=outputName+os.extsep+str(outPart)
            outPart+=1
            openOutputFile=open(fullOutputName,'w')
            openOutputFile.writelines(openInputFile.readlines(part))
            openOutputFile.close()
    openInputFile.close()
    return outPart-1
2
quamrana

Das hat für mich funktioniert

import os

fil = "inputfile"
outfil = "outputfile"

f = open(fil,'r')

numbits = 1000000000

for i in range(0,os.stat(fil).st_size/numbits+1):
    o = open(outfil+str(i),'w')
    segment = f.readlines(numbits)
    for c in range(0,len(segment)):
        o.write(segment[c]+"\n")
    o.close()
1
Ryan

Hier ist ein Python-Skript, das Sie zum Teilen großer Dateien mit subprocess verwenden können:

"""
Splits the file into the same directory and
deletes the original file
"""

import subprocess
import sys
import os

SPLIT_FILE_CHUNK_SIZE = '5000'
SPLIT_PREFIX_LENGTH = '2'  # subprocess expects a string, i.e. 2 = aa, ab, ac etc..

if __== "__main__":

    file_path = sys.argv[1]
    # i.e. split -a 2 -l 5000 t/some_file.txt ~/tmp/t/
    subprocess.call(["split", "-a", SPLIT_PREFIX_LENGTH, "-l", SPLIT_FILE_CHUNK_SIZE, file_path,
                     os.path.dirname(file_path) + '/'])

    # Remove the original file once done splitting
    try:
        os.remove(file_path)
    except OSError:
        pass

Sie können es extern aufrufen:

import os
fs_result = os.system("python file_splitter.py {}".format(local_file_path))

Sie können auch subprocess importieren und direkt in Ihrem Programm ausführen.

Das Problem bei diesem Ansatz ist eine hohe Speicherauslastung: subprocess erstellt eine Verzweigung mit einem Speicherabdruck, der der Größe Ihres Prozesses entspricht. Wenn Ihr Prozessspeicher bereits sehr groß ist, wird er für die Dauer seiner Ausführung verdoppelt. Dasselbe mit os.system.

Hier ist eine weitere reine Python-Methode, obwohl ich es noch nicht mit großen Dateien getestet habe. Es wird zwar langsamer sein, aber mit weniger Speicher arbeiten:

CHUNK_SIZE = 5000

def yield_csv_rows(reader, chunk_size):
    """
    Opens file to ingest, reads each line to return list of rows
    Expects the header is already removed
    Replacement for ingest_csv
    :param reader: dictReader
    :param chunk_size: int, chunk size
    """
    chunk = []
    for i, row in enumerate(reader):
        if i % chunk_size == 0 and i > 0:
            yield chunk
            del chunk[:]
        chunk.append(row)
    yield chunk

with open(local_file_path, 'rb') as f:
    f.readline().strip().replace('"', '')
    reader = unicodecsv.DictReader(f, fieldnames=header.split(','), delimiter=',', quotechar='"')
    chunks = yield_csv_rows(reader, CHUNK_SIZE)
    for chunk in chunks:
        if not chunk:
            break
        # Do something with your chunk here

Hier ist ein weiteres Beispiel mit readlines():

"""
Simple example using readlines()
where the 'file' is generated via:
seq 10000 > file
"""
CHUNK_SIZE = 5


def yield_rows(reader, chunk_size):
    """
    Yield row chunks
    """
    chunk = []
    for i, row in enumerate(reader):
        if i % chunk_size == 0 and i > 0:
            yield chunk
            del chunk[:]
        chunk.append(row)
    yield chunk


def batch_operation(data):
    for item in data:
        print(item)


with open('file', 'r') as f:
    chunks = yield_rows(f.readlines(), CHUNK_SIZE)
    for _chunk in chunks:
        batch_operation(_chunk)
0
radtek

Ich musste csv-Dateien für den Import in Dynamics CRM aufteilen, da die Dateigrößenbeschränkung für den Import 8 MB beträgt und die empfangenen Dateien viel größer sind. Dieses Programm ermöglicht es dem Benutzer, FileNames und LinesPerFile einzugeben, und teilt dann die angegebenen Dateien in die angeforderte Anzahl von Zeilen auf. Ich kann nicht glauben, wie schnell es geht!

# user input FileNames and LinesPerFile
FileCount = 1
FileNames = []
while True:
    FileName = raw_input('File Name ' + str(FileCount) + ' (enter "Done" after last File):')
    FileCount = FileCount + 1
    if FileName == 'Done':
        break
    else:
        FileNames.append(FileName)
LinesPerFile = raw_input('Lines Per File:')
LinesPerFile = int(LinesPerFile)

for FileName in FileNames:
    File = open(FileName)

    # get Header row
    for Line in File:
        Header = Line
        break

    FileCount = 0
    Linecount = 1
    for Line in File:

        #skip Header in File
        if Line == Header:
            continue

        #create NewFile with Header every [LinesPerFile] Lines
        if Linecount % LinesPerFile == 1:
            FileCount = FileCount + 1
            NewFileName = FileName[:FileName.find('.')] + '-Part' + str(FileCount) + FileName[FileName.find('.'):]
            NewFile = open(NewFileName,'w')
            NewFile.write(Header)

        NewFile.write(Line)
        Linecount = Linecount + 1

    NewFile.close()
0
Ron Smith

Oder eine Python-Version von wc und split:

lines = 0
for l in open(filename): lines += 1

Dann etwas Code, um die ersten Zeilen/3 in eine Datei zu lesen, die nächsten Zeilen/3 in eine andere usw.

0
Claudiu