J'ai un énorme fichier texte (~ 1 Go) et, malheureusement, l'éditeur de texte que j'utilise ne lit pas un fichier aussi volumineux. Cependant, si je peux simplement le scinder en deux ou trois parties, tout ira bien. En tant qu'exercice, je voulais écrire un programme en python pour le faire.
Ce que je pense que je veux que le programme fasse, c'est trouver la taille d'un fichier, diviser ce nombre en parties et, pour chaque partie, lire jusqu'à ce point en morceaux, en écrivant dans un fichier de sortie filename .nnn, puis lisez-le jusqu'au saut de ligne suivant et écrivez-le, puis fermez le fichier de sortie, etc. Évidemment, le dernier fichier de sortie est simplement copié à la fin du fichier d'entrée.
Pouvez-vous m'aider avec les parties clés du système de fichiers: la taille du fichier, la lecture et l'écriture en gros morceaux et la lecture jusqu'à la rupture de ligne?
Je vais d'abord écrire ce test de code, il n'est donc pas nécessaire de me donner une réponse complète, à moins que ce ne soit un one-liner ;-)
Consultez os.stat()
pour la taille du fichier et file.readlines([sizehint])
. Ces deux fonctions devraient être tout ce dont vous avez besoin pour la partie lecture et, espérons-le, vous savez faire l'écriture :)
linux a une commande split
split -l 100000 fichier.txt
serait divisé en fichiers de taille égale à 100 000 lignes
Comme méthode alternative, en utilisant la bibliothèque de journalisation:
>>> 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())
Vos fichiers apparaîtront comme suit:
filename.txt (fin du fichier)
nomfichier.txt.1
nomfichier.txt.2
...
nomfichier.txt.10 (début du fichier)
C'est un moyen rapide et facile de faire correspondre un fichier journal volumineux à votre implémentation RotatingFileHandler
.
Cette méthode de génération est un moyen (lent) d’obtenir une tranche de lignes sans faire exploser votre mémoire.
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)
Alors que la réponse de Ryan Ginstrom est correcte, cela prend plus de temps qu'il ne le devrait (comme il l'a déjà noté). Voici un moyen de contourner les appels multiples à itertools.islice
en itérant successivement sur le descripteur de fichier ouvert:
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
Il existe maintenant un module pypi que vous pouvez utiliser pour fractionner des fichiers de toute taille en morceaux. Regarde ça
Vous pouvez utiliser wc
et split
(voir les pages de manuel respectives) pour obtenir l'effet souhaité. Dans bash
:
split -dl$((`wc -l 'filename'|sed 's/ .*$//'` / 3 + 1)) filename filename-chunk.
produit 3 parties du même nombre de lignes (avec une erreur d’arrondi dans la dernière, bien sûr), nommées filename-chunk.00
à filename-chunk.02
.
usage - split.py nom de fichier 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)
J'ai écrit le programme et il semble bien fonctionner. Merci donc à Kamil Kisiel de m'avoir lancé.
(Notez que FileSizeParts () est une fonction non montrée ici)
Plus tard, je pourrai peut-être faire une version qui lira une lecture binaire pour voir si c'est plus rapide.
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
Cela a fonctionné pour moi
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()
Voici un script python que vous pouvez utiliser pour fractionner des fichiers volumineux à l'aide de subprocess
:
"""
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
Vous pouvez l'appeler en externe:
import os
fs_result = os.system("python file_splitter.py {}".format(local_file_path))
Vous pouvez également importer subprocess
et l'exécuter directement dans votre programme.
Le problème avec cette approche est une utilisation importante de la mémoire: subprocess
crée une fourchette avec une empreinte mémoire identique à celle de votre processus et si la mémoire de votre processus est déjà lourde, elle est doublée pendant la durée de son exécution. La même chose avec os.system
.
Voici une autre façon de faire cela, bien que je ne l’aie pas testé sur d’énormes fichiers, cela va être plus lent, mais soyez plus maigre en mémoire:
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
Voici un autre exemple utilisant 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)
J'avais l'obligation de fractionner les fichiers csv à importer dans Dynamics CRM, car la taille maximale des fichiers à importer est de 8 Mo et les fichiers que nous recevons sont beaucoup plus volumineux. Ce programme permet à l'utilisateur de saisir FileNames et LinesPerFile, puis divise les fichiers spécifiés en nombre de lignes demandé. Je ne peux pas croire à quelle vitesse cela fonctionne!
# 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()
Ou bien, une version python de wc et split:
lines = 0
for l in open(filename): lines += 1
Ensuite, un code pour lire les premières lignes/3 dans un fichier, les lignes suivantes/3 dans un autre, etc.