Optimizando encontrar y reemplazar archivos grandes en Python

Soy un completo principiante de Python o cualquier lenguaje de progtwigción serio. Finalmente conseguí un código de prototipo para trabajar, pero creo que será demasiado lento.

Mi objective es encontrar y reemplazar algunos caracteres chinos en todos los archivos (son csv) en un directorio con enteros según un archivo csv que tengo. Los archivos están bien numerados por año-mes, por ejemplo 2000-01.csv, y serán los únicos archivos en ese directorio.

Recorreré aproximadamente 25 archivos que están en el entorno de 500 mb cada uno (y alrededor de un millón de líneas). El diccionario que usaré tendrá alrededor de 300 elementos y cambiaré unicode (caracteres chinos) a números enteros. Lo intenté con una prueba y, suponiendo que todo se amplíe linealmente (?), Parece que llevaría aproximadamente una semana para que esto funcione.

Gracias por adelantado. Aquí está mi código (¡no te rías!):

# -*- coding: utf-8 -*- import os, codecs dir = "C:/Users/Roy/Desktop/test/" Dict = {'hello' : 'good', 'world' : 'bad'} for dirs, subdirs, files in os.walk(dir): for file in files: inFile = codecs.open(dir + file, "r", "utf-8") inFileStr = inFile.read() inFile.close() inFile = codecs.open(dir + file, "w", "utf-8") for key in Dict: inFileStr = inFileStr.replace(key, Dict[key]) inFile.write(inFileStr) inFile.close() 

Related of "Optimizando encontrar y reemplazar archivos grandes en Python"

En su código actual, está leyendo todo el archivo en la memoria a la vez. Ya que son archivos de 500Mb, eso significa cadenas de 500Mb. Y luego haces reemplazos repetidos de ellos, lo que significa que Python tiene que crear una nueva cadena de 500Mb con el primer reemplazo, luego destruye la primera cadena, luego crea una segunda cadena de 500Mb para la segunda sustitución, luego destruye la segunda cadena, etc. para cada reemplazo. Eso resulta ser una gran cantidad de copias de datos de un lado a otro, sin mencionar el uso de mucha memoria.

Si sabe que los reemplazos siempre estarán contenidos en una línea, puede leer el archivo línea por línea iterando sobre él. Python almacenará en búfer la lectura, lo que significa que estará bastante optimizado. Debe abrir un nuevo archivo, con un nombre nuevo, para escribir el nuevo archivo simultáneamente. Realice el reemplazo en cada línea sucesivamente y escríbalo inmediatamente. Hacer esto reducirá en gran medida la cantidad de memoria utilizada y la cantidad de memoria copiada de un lado a otro al hacer los reemplazos:

 for file in files: fname = os.path.join(dir, file) inFile = codecs.open(fname, "r", "utf-8") outFile = codecs.open(fname + ".new", "w", "utf-8") for line in inFile: newline = do_replacements_on(line) outFile.write(newline) inFile.close() outFile.close() os.rename(fname + ".new", fname) 

Si no puede estar seguro de que siempre estarán en una línea, las cosas se ponen un poco más difíciles; tendría que leer los bloques manualmente, usando inFile.read(blocksize) , y hacer un seguimiento cuidadoso de si podría haber una coincidencia parcial al final del bloque. No es tan fácil de hacer, pero por lo general aún vale la pena evitar las cadenas de 500 Mb.

Otra gran mejora sería si pudiera hacer los reemplazos de una sola vez, en lugar de intentar un montón de reemplazos en orden. Hay varias formas de hacerlo, pero la que mejor se adapta depende completamente de lo que está reemplazando y con qué. Para traducir caracteres individuales a otra cosa, el método de translate de objetos Unicode puede ser conveniente. Le pasas un dict de mapeo de puntos de código Unicode (como enteros) a cadenas de Unicode:

 >>> u"\xff and \ubd23".translate({0xff: u"255", 0xbd23: u"something else"}) u'255 and something else' 

Para reemplazar las subcadenas (y no solo los caracteres individuales), puede usar el módulo re . La función re.sub (y el método sub de las expresiones regulares comstackdas) puede tomar un llamable (una función) como primer argumento, que luego se llamará para cada coincidencia:

 >>> import re >>> d = {u'spam': u'spam, ham, spam and eggs', u'eggs': u'saussages'} >>> p = re.compile("|".join(re.escape(k) for k in d)) >>> def repl(m): ... return d[m.group(0)] ... >>> p.sub(repl, u"spam, vikings, eggs and vikings") u'spam, ham, spam and eggs, vikings, saussages and vikings' 

Creo que puede reducir mucho el uso de memoria (y, por lo tanto, limitar el uso del intercambio y hacer que las cosas sean más rápidas) leyendo una línea a la vez y escribiéndola (después de los reemplazos de expresiones regulares ya sugeridos) a un archivo temporal, luego moviendo el archivo para reemplazar el original .

Algunas cosas (no relacionadas con el problema de optimización):

dir + file debe ser os.path.join(dir, file)

Es posible que no desee reutilizar el archivo, sino que abra (y escriba en) un archivo externo separado. Esto tampoco boostá el rendimiento, pero es una buena práctica.

No sé si estás enlazado a E / S o cpu, pero si la utilización de tu CPU es muy alta, es posible que desees usar subprocesos, con cada subproceso operando en un archivo diferente (por lo tanto, con un procesador de cuatro núcleos, estaría leyendo / escribiendo 4 archivos diferentes simultáneamente).

Abra los archivos de lectura / escritura (‘r +’) y evite la doble apertura / cierre (y el posible vaciado del búfer asociado). Además, si es posible, no escriba todo el archivo, busque y escriba solo las áreas modificadas después de reemplazar el contenido del archivo. Lea, reemplace, escriba las áreas cambiadas (si las hay).

Sin embargo, eso no ayudará demasiado al rendimiento: perfilaría y determinaría dónde está realmente el impacto del rendimiento y luego pasaré a optimizarlo. Puede ser que la lectura de los datos del disco sea muy lenta, y no hay mucho que puedas hacer al respecto en Python.