Python writelines() y write () gran diferencia horaria


Estaba trabajando en un script que lee una carpeta de archivos(cada uno de tamaño que va desde 20 MB a 100 MB), modifica algunos datos en cada línea, y escribe de nuevo a una copia del archivo.

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.writelines('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

Al ejecutar este código con un archivo de 90 MB (~900,000 líneas), imprimió 140 segundos como el tiempo que tomó escribir en el archivo. Aquí usé writelines(). Así que busqué diferentes maneras de mejorar la velocidad de escritura de archivos, y en la mayoría de los artículos que leí, decía write() y writelines() no debería mostrar ninguna diferencia ya que estoy escribiendo una sola cadena concatenada. También comprobé el tiempo tomado solo para la siguiente declaración:

new_string = '\n'.join(new_my_list) + '\n'

Y tomó solo 0.4 segundos, por lo que el gran tiempo que tomó no fue debido a la creación de la lista. Solo para probar write() Probé este código:

with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.write('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')

E imprimió 2,5 segundos. ¿Por qué hay una diferencia tan grande en el tiempo de escritura del archivo para write() y writelines() a pesar de que son los mismos datos? ¿Es este comportamiento normal o hay algo mal en mi código? El archivo de salida parece ser el mismo para ambos casos, por lo que sé que no hay pérdida de datos.

Author: Arjun Balgovind, 2017-06-15

3 answers

file.writelines() espera un iterable de cadenas. Luego procede a hacer un bucle y llamar a file.write() para cada cadena en el iterable. En Python, el método hace esto:

def writelines(self, lines)
    for line in lines:
        self.write(line)

Usted está pasando en una sola cadena grande, y una cadena es un iterable de cadenas también. Al iterar se obtiene caracteres individuales, cadenas de longitud 1. Así que en efecto estás haciendo len(data) llamadas separadas a file.write(). Y eso es lento, porque usted está construyendo un búfer de escritura de un solo carácter en un tiempo.

No pase una sola cadena a file.writelines(). Pase en su lugar una lista o tupla u otra iterable.

Puede enviar líneas individuales con una nueva línea añadida en una expresión generadora, por ejemplo:

 myWrite.writelines(line + '\n' for line in new_my_list)

Ahora, si pudiera hacer clean_data() un generador , produciendo líneas limpias, podría transmitir datos desde el archivo de entrada, a través de su generador de limpieza de datos, y hacia el archivo de salida sin usar más memoria de la necesaria para la lectura y escritura búferes y cualquier estado que se necesite para limpiar sus líneas:

with open(inputPath, 'r+') as myRead, open(outPath, 'w+') as myWrite:
    myWrite.writelines(line + '\n' for line in clean_data(myRead))

Además, consideraría actualizar clean_data() para emitir líneas con nuevas líneas incluidas.

 38
Author: Martijn Pieters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-15 20:06:19

Como complemento a la respuesta de Martijn, la mejor manera sería evitar construir la lista usando join en primer lugar

Simplemente pase una comprensión del generador a writelines, agregando la nueva línea al final: sin asignación de memoria innecesaria y sin bucle (además de la comprensión)

myWrite.writelines("{}\n".format(x) for x in my_list)
 5
Author: Jean-François Fabre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-15 07:06:52

El método'write(arg)' espera string como argumento. Así que una vez que llama, escribe directamente. esta es la razón por la que es mucho más rápido. donde como si estuviera utilizando el método writelines(), espera lista de cadena como iterador. así que incluso si está enviando datos a writelines, asume que tiene iterador y trata de iterar sobre él. así que ya que es un iterador que tomará algún tiempo para iterar sobre y escribirlo.

Está claro ?

 2
Author: nanithehaddock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-15 07:03:12