Física Computacional - FSC-5705

só um divisor

Leitura e escritura de arquivos em disco

disco rigido

No incio deste estudo sobre python dizemos que as variáveis que criamos são armazenadas na memoria do computador por blocos de no mínimo 8 bits. Ainda que essa abordagem resulte muito útil o problema com ela é que depende do computador se manter ligado à fonte de energia (eletricidade), caso o computador seja desligado, a informação armazenada na memoria volátil é perdida lo qual pode ser problemático dado o objetivo do programa desenvolvido. A fim de evitar este inconveniente em 1956 a IBM introduz o primeiro disco rígido (ou HD). O disco rígido é um dispositivo de estado sólido onde as informações são armazenadas de forma quase permanente. O funcionamento desses dispositivos é relativamente simples, é formado por bolachas magnéticas e uma(s) cabeça(s) de leitura e/ou escrita que modificam a orientação dos dipolos magnéticos ("imã-zinhos" atômicos) na superfície das bolachas, como os dipolos (na verdade grupo de dipolos) podem assumir duas orientações conforme a magnetização realizada pela cabeça de leitura se obtém um sistema binário que pode ser utilizado para gravar a informação desejada.

O acesso a esse dispositivo de armazenamento permanente é controlado pelo sistema operativo, é o sistema operativo quem organiza a informação e realiza a gravação em forma de pastas e arquivos. O python proporciona funções (veremos mais à frente o que é isso) que permitem solicitar ao sistema operativo acesso ao disco a fim de ler ou gravar algum conjunto de dados.

De forma genérica as operações que podemos realizar sobre os arquivos no disco são: Abertura, Leitura, Escritura e Fechamento. Abrir um arquivo significa informar ao sistema operativo que durante um tempo trabalharemos com um determinado arquivo - associado a alguma área do disco.

A outra operação é a operação de escrita, quando solicitada por python o sistema operativo grava a informação no disco contudo é possível que as operações de escrita no disco não sejam realizada de imediato e, em lugar disso, se mantenha em memoria os dados (buffer) a fim de atingir um tamanho suficiente que permita diminuir o número de escritas (o disco é um dispositivo mecânico, consequentemente as operações realizadas por ele são muito mais lentas do que as realizada com a memoria).

Quando python solicita ao sistema operativo o fechamento de um arquivo, este move para o disco todas as operações que faltam e libera os recursos empregados durante o uso do dispositivo.

Ao ser aberto um arquivo, a função que realiza este processo devolve uma referência ao arquivo (um endereço de memoria), o qual "deve" ser armazenado em uma variável. Essa variável será utilizada sempre que quisermos ler ou escrever no arquivo associado à variável.

Existem dois (na verdade mais) formatos como os dados são armazenados no disco. O formato que utilizaremos é o formato de texto, onde os dados são armazenados segundo o código ASCII. A vantagem desse formato é que pode ser lida por editores de texto (tipo kate ou mesmo o word) e são portáveis entre os diversos sistemas operativos.

O outro formato é o formato binário. A vantagem desse formato é que é mais compacto que o formato de texto, além de no necessitar ser pré formatado antes de gravar ao disco e dessa forma serem, em geral, mas rápidos na hora de gravar (ler) já que simplesmente se despeja o conteúdo da memória no disco. Para termos uma ideia do que estou falando considere o número 31121973, no formato de texto são necessários 8 bytes para armazenar esse número (1 byte para cada algarismo); no caso do formato binário somente são utilizados 4 bytes.

Abrindo um arquivo

O formato básico da apertura de arquivos é

      arq = open('nome_arq', 'modo', buf)
      

ou seja, a saída (em que posição do disco está o arquivo) da função open é associada uma variável, no caso chamada de arq, mas o nome é totalmente arbitrário.

O primeiro argumento da função open é uma string que contem o nome do arquivo ('nome_arq') com o qual vamos trabalhar. O segundo argumento é outra string que qual é o modo com que desejamos tratar o arquivo, os possíveis modos são:

Para entrar no modo binário se necessita somente colocar b no fim de cada caso anterior:

Enviando dados ao disco

Já foi dito que quando solicitamos a gravação dos dados no disco na realidade isso não é feito de imediato já que os dados são armazenados num espaço temporário de armazenamento na memoria (RAM) ou buffer. O terceiro argumento do open trata justamente deste ponto, o uso ou não do buffer e tamanho deste. as possíveis opções para buf são:

Essa últimas opções poderiam ser utilizadas como:

      bufferSize = 10
      arq = open('teste.dat', 'w', bufferSize)
      

De qualquer forma podemos despejar o conteúdo no disco ao fechar o arquivo ou solicitando isso explicitamente com o método flush:

      arq.flush()
      

A utilização de fflush é obrigatória quando se trabalha com arquivos aberto em modo leitura escritura. Devemos realizar um "flush" dos dados sempre que mudemos de escrita para leitura ou de leitura para escrita.

Fechando um arquivo

      arq.close()
      

Esta função fecha o arquivo f com que estamos trabalhando. O buffer é descarregado no disco.

Métodos de leitura e escrita

Para ler uma linha do arquivo, arq, aberto utilizamos o método readline(numByte):

      #!/usr/bin/python
      # -*- coding: utf-8 -*-
      
      # abrindo um arquivo
      arq = open("file.txt", "r")
      print "Nome do arquivo =  ", arq.name
      
      # Suponha que or arquivo tenha as seguintes 5 linhas
      # Esta é a I   linha
      # Esta é a II  linha
      # Esta é a III linha
      # Esta é a VI  linha
      # Esta é a V   linha
      
      linha = arq.readline()
      print "Linha: %s" % (linha)
      
      linha = arq.readline(5)
      print "Linha: %s" % (linha)
      
      # Fechando o arquivo
      arq.close()
      
[usuario@pclabfis: ]# python exemplo01.py
Nome do arquivo = file.txt
Linha: Esta é a I linha
Linha: Esta

O python te permite escrever usando a função write() como mostra o exemplo embaixo

        #!/usr/bin/env python

        arq = open('texto.txt', 'w')
        arq.write('Ola, ')
        arq.write('mundo!')
        arq.close()
      

Podemos utilizar a função print para escrever no arquivo

        #!/usr/bin/env python

        arq = open('fibonacci.dat', 'w')

        f1 = 1
        f2 = 1

        proximo = f1 + f2

        while (proximo <= 10000):
          print >>arq, proximo  #note que o >> envia a saida do print para arq
          f1      = f2
          f2      = proximo
          proximo = f1 + f2

        arq.close()
      

Para ler podemos utilizar o comando read()

        arq = open('texto.txt', 'w')
        arq.read(2)
        'Ol'           #<-- saida no terminal
        arq.read()
        'ola, mundo!'  #<-- saida no terminal
      

Se nosso arquivo tem varias linhas podemos utilizar a função (método) readline() - ler linha:

        #!/usr/bin/env python

        arq = open('textoLongo.txt', 'w')
        While True:
          linha = arq.readline()
          if not linha: break
          temp    = linha.rstrip()  #remove o '\n' final
          colunas = temp.split()    #separa em colunas
        arq.close()
      

o, utilizar um laço for para ler a linha

        #!/usr/bin/env python

        arq = open('dados.dat', 'r')

        for linha in arq:
          temp    = linha.rstrip()
          colunas = temp.split()

        arq.close()
      

Na linha 3 é aberto o arquivo dados.dat para leitura (r de read). O for da linha 5 percorre todas as linhas. Como cada linha acaba em um ENTER (retorno de carro \n) é necessário eliminar esse caráter final isso é feito por rstrip() e a nova linha modificada é armazenada em temp (de temporário). Depois linha é separadas em colunas usando como delimitador o espaço em branco. Finalmente fechamos o arquivo.

Caso queiramos ler uma opção da entrada padrão, ao estilo opção para o programa, utilizamos o modulo sys

        #!/usr/bin/env python

        import sys

        texto    = sys.stdin.read()
        palavras = texto.split()
        quanPal  = len(palavras)
        print 'O total de palavras no texto eh = %d' %quanPal
      

quando executado no terminal como por exemplo python script.py texto.txt ele vai imprimir a quantidade de palavras no texto.

Rebobinando um arquivo

A leitura de um arquivo se da linha por linha via um ponteiro de leitura. Existem situações atípicas onde você lê todas as linhas do se arquivo e logo a seguir precisa colocar o ponteiro de leitura de volta no inicio. Para se conseguir este feito o python disponibiliza o método arq.seek() o qual permite reposicionamento do ponteiro. Quando é feito arq.seek(0) informamos ao python colocar o ponteiro no inicio. Como exemplo vejamos a leitura do arquivo dados1.dat

      #!/usr/bin/python
      # -*- coding: utf-8 -*-
      
      # abrindo um arquivo
      arqDados = open("dados1.dat", "r")

      #vamos ler todo o conteudo do arquivo
      a = arqDados.readlines()
      print 'todo o arquivo: ', a

      print '\n'

      totalPalavras = 0
      for i in range(len(a)):
        print 'a linha', i, 'tem ', len(a[i]), 'palavras: '

      #vamos tentar ler uma nova linha
      b = arqDados.readline()
      print 'outra linha:', b

      print '\n'
      #rebobinando e lendo
      print "rebobinando..."
      arqDados.seek(0)
      b = arqDados.readline()
      print 'outra linha:', b

      #lendo o resto do arquivo
      a = arqDados.readlines()
      print 'resto do arquivo: ', a

      #lendo a III linha contando do inicio
      print "movendo para a III linha..."
      arqDados.seek(12,0)
      b = arqDados.readline()
      print 'III linha:', b

      #lendo a III linha contando desde o fim
      #em sentido reverso
      print "movendo para a III linha ..."
      arqDados.seek(-20,2)
      b = arqDados.readline()
      print 'III linha:', b

      #lendo a II linha a partir da posição
      #atual (antepenúltima linha)
      print "movendo para a II linha a partir da antepenúltima..."
      arqDados.seek(-14,1)
      b = arqDados.readline()
      print 'III linha:', b      
      
[usuario@pclabfis: ]# python exemplo02.py
todo o arquivo: ['Esta eh a I linha\n', 'Esta eh a II linha\n', 'Esta eh a III linha\n', 'Esta eh a VI linha\n', 'Esta eh a V linha\n', 'Esta eh a VI linha\n', 'Esta eh a VII linha']

a linha 0 tem 20 palavras: Esta eh a I linha
a linha 1 tem 20 palavras: Esta eh a II linha
a linha 2 tem 20 palavras: Esta eh a III linha
a linha 3 tem 20 palavras: Esta eh a VI linha
a linha 4 tem 20 palavras: Esta eh a V linha
a linha 5 tem 20 palavras: Esta eh a VI linha
a linha 6 tem 19 palavras: Esta eh a VII linha


outra linha:


rebobinando...
outra linha: Esta eh a I linha

resto do arquivo: ['Esta eh a II linha\n', 'Esta eh a III linha\n', 'Esta eh a VI linha\n', 'Esta eh a V linha\n', 'Esta eh a VI linha\n', 'Esta eh a VII linha']


movendo para a III linha desde o inicio...
III linha: Esta eh a III linha

movendo para a III linha contando desde atras...
III linha: Esta eh a V linha

movendo para a II linha a partir da antepenúltima... III linha: Esta eh a III linha

O método seek tem varias funções, a descrição do método é:

Lendo e escrevendo no disco utilizando numpy

O numpy possui uma função para ler desde um arquivo uma tabela de dado, a função loadtxt() e seu uso é muito simples, como se mostra no exemplo embaixo (use os dados: dados.dat):

        #!/usr/bin/env python

        from numpy import *

        print '\nLendo todos os dados e armazenando em uma matriz'
        DataIn = loadtxt('dados1.dat')
        print DataIn

        print '\nLendo dado a dado da matriz'
        for line in DataIn:
          print line[0], line[1], line[2]


        print '\nUsando unpack'
        x, y, yerr = loadtxt('dados1.dat', unpack=True)

        for i in range(len(x)):
          print x[i], y[i], yerr[i]

        print '\nUsando unpack para ler colunas especificas'
        x, y = loadtxt('dados1.dat', unpack=True, usecols=[0,1])

        for i in range(len(x)):
          print x[i], y[i]
      

No caso da escritura a gente dispõe da função savetxt(). É claro que seu uso é simples, como o da função loadtxt() más ela possui mais opções, como é de se esperar, já que nos podemos querer escrever formatado nossos dados, nesse caso devemos utilizar

%[flags][largura][.precisão]código

onde o código está dado pela tabela acima.

Lendo arquivos binários

Existem várias formas de se gravar em binário num arquivo. O tópico aqui tratado supõe que os dados foram gravados utilizando um programa em C, em Fortran 2008 utilizando o método Acess='stream', em python utilizando o modo de escrita binária 'wb' ou utilizando a função de numpy .tofile. Nessa situações resulta útil utilizar a função do exemplo embaixo:

      #!/usr/bin/env python
      #-*- coding: utf-8 -*-
      
      import numpy as np
      
      arq = open('estadoInicial.dat', 'rb')
      B = np.fromfile(file=arq, dtype=np.float).reshape(N,6)
      R = B[:,0:3]
      V = B[:,3:N]
      

Nesse exemplo no arquivo em binário foi guardada a posição e velocidade (em 6 colunas) de N partículas (em N linhas) em um programa em fortran, por isso quando foram lidos os dados eles foram transformado em matriz (já que são lidos como uma única coluna).

Tarefa

  1. Num teste de psicologia são realizadas $5$ perguntas simples cujas resposta pode ser sim ou não. Escreva um programa onde sejam armazenadas as respostas de $N$ possíveis voluntários e que no final mostre a porcentagem de pessoas que respondeu de forma afirmativa em cada questão.
  2. Modifique o programa anterior a fim de poder ler o arquivo respostas.dat (o arquivo está compactado, tem que descompactar), onde foram previamente armazenadas as respostas de um teste com $25$ perguntas realizado a um número indefinido de pessoas. Cada linha do arquivo representa as $25$ respostas dadas por cada pessoa. $1$ significa que afirmativo e $0$ negativo.