Read a few lines from a file

Asked

Viewed 8,766 times

4

I have a file with about 3 million lines. I have to read line by line and process some modifications, and after these modifications in line store the result in a list to then write to another file.

The problem is performance. It’s too slow.

I thought I’d do it this way: I will divide the file lines by 10 (e.g. 300000 lines) and process. When this 300000 lines is over, write the file. Then I read the other 300000 and so on, until the lines of the source file end.

My question is: Whereas I have a file with 3 million lines, I would like to read just a stretch of lines from the archive (from 300000 to 300000). This is possible in python?

Follows the method:

def processa_arquivos_rlt(arquivos_rlt, newFileName, sms):
try:
    for arquivo in arquivos_rlt:
        if Modulo.andamento_processo == 0:
            break

        Modulo.arquivo = 'Aguarde...'
        Modulo.arquivo = (arquivo[arquivo.rindex('/')+1:])
        contador = 1

        with open(arquivo, 'r') as linhas_rlt, open(newFileName, "at") as linhas_saida:
            for linha in linhas_rlt:
                if Modulo.andamento_processo == 0:
                    break

                item = [i.strip() for i in linha.split(";")]

                linha = Linha()

                linha.dddOrigem = item[2]
                linha.numeroOrigem = item[3]
                linha.valorBruto = item[15]
                if linha.valorBruto.find(",") > 0:
                    if len(''.join(linha.valorBruto[linha.valorBruto.rindex(",")+1:].split())) == 1:
                        linha.valorBruto = linha.valorBruto + '0'
                else:
                    if (len(linha.valorBruto)) <= 2:
                        linha.valorBruto = linha.valorBruto + '00'

                linha.valorBruto = re.sub(r'[^0-9]', '', linha.valorBruto)
                linha.dddDestino = item[7]
                linha.numeroDestino = item[8]
                linha.localidade = item[10]
                linha.codigoServico = item[17]
                linha.contrato = item[18]

                if 'claro' in arquivo.lower():
                    linha.operadora = '36'
                    #[Resolvi removendo esse trecho de código. Ao invés de executar
                    #uma consulta a cada iteração, agora eu executo a consulta apenas
                    #uma vez, coloco o resultado em uma lista e percorro essa lista. A
                    #consulta é feita apenas uma vez!]

                    """
                    cc = CelularesCorporativos.objects.filter(ddd=linha.dddOrigem, numero=linha.numeroOrigem)
                    if len(cc) > 0:
                        if 'vc1' in linha.localidade.lower() or 'sms' in linha.localidade.lower():
                                if linha.dddOrigem == linha.dddDestino:
                                    if int(linha.valorBruto) > 0:
                                        linha.valorBruto = '0'
                    """
                    #chamadas inválidas
                    if len(linha.numeroDestino) < 8 and linha.numeroDestino != '100' \
                        and int(linha.valorBruto) > 0:
                        if item[0] == '3' and linha.dddDestino == '10' \
                            and linha.numeroDestino == '0' and 'secretaria claro' in linha.localidade.lower():
                    ...
  • You can put the code you have developed so far into the body of the question?

  • 1

    Boy... that code could use a little work, huh? =)

  • I solved it as follows: within each iteration I ran a database query, it consumed a lot of memory, since the list was huge. So I put the result of the query in a list and checked the list with each iteration. Improved the performance and had no memory overflow!

  • You could post your solution as an answer, and accept your own answer? This would make the question more organized. Here we do not put "solved" in the title or in the body of the question (I will edit to remove ok?). Thanks.

  • Use the Spark apache project....

3 answers

5

Three million lines could take forever - but the best way to treat it in Python, if each line must be processed independently of the others, it is read one line at a time, using the built-in iterator itself in the Python open file object (file) with a for. Also, record one line at a time.

If your slowness is due to memory (while reading the whole file at once, the machine could be entering into SWAP, which would leave the whole process hundreds of times slower), you solve - if it is because the task is even time consuming, at least you will be able to see the output file gradually increasing in size as the task is executed.

Looking at your code, you’re already doing the processing line by line when you do: for linha in linhas_rlt: - however, a little above, to know the file size you make

totalLinhas = len(fi.readlines()) #abro e fecho o arquivo pra saber a qtd de linhas

That’s a problem: don’t do it. It’s not just "open and close the file" - you’re reading the entire file for memory, simply to count how many lines there are. It’s okay to do this for a 30 - 40 line file, but since it’s your "database",and you’re having a performance problem, Voce shouldn’t do this.

Actually there is no way to know the length of a text file, in lines, without opening it and counting the lines this way - only that.... That’s why it’s not done. In this particular case, it’s past time for you to put your information in a relational database. This is the way to treat 3 million textual records quickly "and painless".

I’ve made some improvements to your code below, but nothing that will more than double the current speed (the current one will be almost twice as slow because of your file size check, as I wrote above).

The suggestion for your problem is to put this data in a database - it could be Sqlite itself, which comes embedded in Python: Processing 3 million records is already something heavy to do in plain text files (as you realized).

Essentially your code back, with some relevant modifications and comments:

def processa_arquivos_rlt(arquivos_rlt, newFileName, sms):
    listaLinhas = [] #aqui  serao adicionadas as linhas modificadas
    # m,elhor nao ter isso, e guardar linha a linha no arquivo de saida!
    contador = 1


    totalLinhas = 0 #eu preciso saber a quantidade de linhas do arquivo, eu mostro pro usuário o andamento do processo.
    # melhornao! :-)

    try:
        for arquivo in arquivos_rlt: # eu leio 3 arquivos, então aqui vai um por vez
            # invertendo o teste do if e encerrando o "for" se o teste for verdadeiro:
            # dimijnuimos um nivel de identação de todo o código.
            # em vez de :
            #if Modulo.andamento_processo > 0: #classe estática pra não usar variável global (performance)
            # fazemos:
            if  Modulo.andamento_processo <= 0:
                continue
            # e removemos a identação de todo o código restante
            contador = 1

            # Aqui, a nao ser que "Modulo.arquivo" seja uma property que
            # vá criando um log, ou imprimindo tudo o que for colocado nela,
            # a proxima linha não faz nada, já que o conteudo da variável
            # será sobre-escrito na linha seguinte.
            Modulo.arquivo = 'Aguarde...'
            Modulo.arquivo = (arquivo[arquivo.rindex('/')+1:])
            # voce porde até dobrar o tempo da tarefa com as linhas abaixo: desligadas
            #fi = open(arquivo, 'r')
            #totalLinhas = len(fi.readlines()) #abro e fecho o arquivo pra saber a qtd de linhas
            fi.close()

            # Abrir o arquivo de gravacao junto, e gravar linha a linha, em vez de mater uma lista:
            # abrir o aruivo com o modo "at" mantem o conteudo do arquivo já existente e abre no final
            # para escrita de novos dados:


            with open(arquivo, 'r') as linhas_rlt, open(newFilename, "at") as linhas_saida:
                for linha in linhas_rlt:
                    #if Modulo.andamento_processo > 0: #se for = a zero eu cancelo o processo.
                    # entao, vamos fazer isso: cancelar o processo se a variavel for zero,
                    # e diminuir um nivel de identação.
                    if Modulo.andamento_processo == 0: # inverte o if acima
                        break # e sai do processo se o valor for zero
                    # tiramos todo o códgigo abaixo de dentro do if
                    # meno sum nivel de identacao = codigo mais agraael de ler:

                    # A linha abaixo funciona - mas considere trocar a leitura do arquivo para usar
                    # o módulo "csv" do Python em vez de fazer o split manualmente.
                    # O principal motivo é que se você tiver campos com texto
                    # que possam estar entre aspas, ou possam conter o caractere ";"
                    # o módulo csv trata pra você:

                    item = [i.strip() for i in linha.split(";")]

                    linha = Linha()


                    # Aqui entrama s linahs onde voce processa o conteudo, e etc...
                    # a principio nada que deva deixar muito lento.

                    #para gravar:
                    # em vez de adicionar o objeto "linha" em uma lista, grava-lo imediatamente:

                    #listaLinhas.append(linha)

                    linhas_saida.writeline(<seu código para serializar o objeto 'Linha'> + "\n")
  • I did following your advice but gave error Memoryerror Unhandled Exception in thread Started by... Type, burst the memory when had read and recorded something around 190 thousand lines... It is absurd! I made this in java and it worked perfectly. I decided to migrate to python and I can’t. It does not justify I have to create a table to put the file data, treat them and then generate another file! Must have a middle! Above I put the complete method. Must have some hole to give memory burst. If you can help...

0

Store in a file the number of the line being read, when starting the process the script reads the number of the line and starts with it or the first if the line is not informed. To know the line number, use a variable that increments with each loop loop loop loop.

-1

If I were you, I would put the entire process in thread using the native library multiprocessing:

https://docs.python.org/2/library/multiprocessing.html

For example:

from multiprocessing import Process
import os

def processa_arquivos_rlt(arquivos_rlt, newFileName, sms):
    listaLinhas = [] #aqui vai ser adicionado as linhas modificadas
    contador = 1
    totalLinhas = 0 #eu preciso saber a quantidade de linhas do arquivo, eu mostro pro usuário o andamento do processo.

try:
    for arquivo in arquivos_rlt: # eu leio 3 arquivos, então aqui vai um por vez
        p = Process(target=worker_function, args=(arquivo,))
        p.start()
        p.join()

def worker_function(arquivo):
    if Modulo.andamento_processo > 0: #classe estática pra não usar variável global (performance)
            contador = 1
            Modulo.arquivo = 'Aguarde...'
            Modulo.arquivo = (arquivo[arquivo.rindex('/')+1:])
            fi = open(arquivo, 'r')
            totalLinhas = len(fi.readlines()) #abro e fecho o arquivo pra saber a qtd de linhas
            fi.close()

            with open(arquivo, 'r') as linhas_rlt:
                for linha in linhas_rlt:
                    if Modulo.andamento_processo > 0: #se for = a zero eu cancelo o processo.
                        item = [i.strip() for i in linha.split(";")]

                        linha = Linha()

                        linha.dddOrigem = item[2]
                        linha.numeroOrigem = item[3]
                        linha.valorBruto = item[15]
                        if linha.valorBruto.find(",") > 0:
                            if len(''.join(linha.valorBruto[linha.valorBruto.rindex(",")+1:].split())) == 1:
                                linha.valorBruto = linha.valorBruto + '0'
                        else:
                            if (len(linha.valorBruto)) <= 2:
                                linha.valorBruto = linha.valorBruto + '00'

                        linha.valorBruto = re.sub(r'[^0-9]', '', linha.valorBruto)
                        linha.dddDestino = item[7]
                        linha.numeroDestino = item[8]
                        linha.localidade = item[10]
                        linha.codigoServico = item[17]
                        linha.contrato = item[18]

PS: I have not tested this code. It is just an initial suggestion of what is possible to do.

  • 1

    Putting it in multiple threads doesn’t ease the memory problem, nor, without other features, will give you some very good gain with Python (it may be that it bypasses the blocking I/O to read lines from the file, so it may have won). Another idle - thread != multiprocessing: these are completely dithoned things) and finally, using either threads or multi-processing, it is difficult to ensure that the order of the lines in the output file will respect the order of the initial file. We do not know if 'is a requirement of O.P (original poster: "the person who asked the question") - but in doubt, better preserve.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.