Read some lines from a file

4

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

The problem is performance. It's very slow.

I thought of doing the following: I'll split the lines of the file by 10 (eg 300000 lines) and render. When I finish these 300000 lines, I write the file. Then I read the other 300,000, and so on, until the lines in the source file are gone.

My question is: Considering that I have a file with 3 million lines, I would like to read only a few lines of the file (from 300000 to 300000). Is this possible in python?

The method follows:

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():
                    ...
    
asked by anonymous 23.04.2014 / 20:06

3 answers

5

Three million lines may be delayed - but the best way to handle this in Python, if each line must be processed independently of the others, it is read one line at a time, using the iterator itself built into the Python open file object with a for . And also, record one line at a time.

If your slowness is due to memory (when reading the whole file at one time, the machine could be entering SWAP, which would leave the process hundreds of times slower), you decide - that's why the task is time-consuming, at least you can see the output file increase in size gradually as the task is executed.

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

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

That's probiitvo: do not do that. It is not simply "opening and closing the file" - you are reading the whole file to memory, simply to count how many lines it has. It's okay to do this for a 30-40 line file, but since it's your "database", and you're experiencing a performance problem, you should not do that.

Actually there is no way to know the length of a text file, in lines, without opening it and counting the lines this way - except that .... that is why this is not done. In this specific case, it's about time you put your information in a relational database. This is the way to treat 3 million textual records quickly and painlessly.

I've made some improvements in your code below, but nothing that goes more than double the current speed (the current one will be almost doubled slower due to your file size check, as I wrote above).

The suggestion for your very problem is to put this data in a database - it may be the same SQLite, which comes embedded in Python: Processing 3 million records is already a heavy thing to do in pure text files (as you have noticed).

Essentially your return code, 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")
    
24.04.2014 / 00:41
0

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

    
10.05.2014 / 23:10
-1

In its place, I would put the whole process in thread using the native multiprocessing library:

link

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's just an initial suggestion of what you can do.

    
23.04.2014 / 20:24