Datoteke

Tekstualne datoteke

Primjer koji upisuje neka slova i brojeve u datoteku.

with open("txt1.txt","w") as f:
  print("1.red: 123", 456, file=f)
  print("2.red 1230", 4560.123, file=f)

Rezultat je datoteka:

1.red: 123 456
2.red 1230 4560.123

U C-u je običaj štedljivo učitavati ono što je potrebno broj po broj slovo po slovo. U pythonu je običaj učitati odjednom sve a nakon toga analizirati.

with open("txt1.txt","r") as f:
  s = f.read()
print(s)
1.red: 123 456
2.red 1230 4560.123

Sadržaj učitan u jedan veliki string.

with open("txt1.txt","r") as f:
  L = f.readlines()
print(L)
['1.red: 123 456\n', '2.red 1230 4560.123\n']

Sadržaj učitan kao lista stringova: jedan red = jedan string.

with open("txt1.txt","r") as f:
  L = f.readlines()
for red in L:
  L1 = red.split()
  print(L1[:-1], float(L1[-1]))
['1.red:', '123'] 456.0
['2.red', '1230'] 4560.123

Sadržaj učitan kao lista stringova. Zatim svaki red rastavljen u listu stringova. Zadnji element liste pretvaramo u float i ispisujemo. Ostatak liste ispisujemo kako jest.

Napomena

Datoteke se zatvaraju u trenutku napuštanja with bloka, tj. nakon izvršenja zadnje naredbe u with bloku.

Tekstualni format JSON

JSON omogućuje lagano prebacivanje iz kombinacije dictionary+list+osnovni tipovi u string i obratno. Time se lako mogu iz datoteke učitati npr. ulazni podaci za program.

>>> import json
>>> čestice = [{'rb':1, 'm':10, 'x':0.0, 'y':0.0, 'fiksna':True}, {'rb':2, 'm':20, 'x':1.0, 'y':1.1, 'fiksna':False}]
>>> print(čestice[0]['rb'])
1
>>> print(čestice[0]['x'])
0.0
>>> print(čestice[0]['y'])
0.0
>>> print(čestice[1]['rb'])
2
>>> print(čestice[1]['x'])
1.0
>>> print(čestice[1]['y'])
1.1
>>> print(repr(json.dumps(čestice)))
'[{"rb": 1, "m": 10, "x": 0.0, "y": 0.0, "fiksna": true}, {"rb": 2, "m": 20, "x": 1.0, "y": 1.1, "fiksna": false}]'
>>> print(json.dumps(čestice,indent=4))
[
    {
        "rb": 1,
        "m": 10,
        "x": 0.0,
        "y": 0.0,
        "fiksna": true
    },
    {
        "rb": 2,
        "m": 20,
        "x": 1.0,
        "y": 1.1,
        "fiksna": false
    }
]

U gornjem primjeru smo pomoću json.dumps() prebacili sadržaj varijable u string. Pomoću json.dump() sadržaj varijable upisujemo u datoteku.

import json
data = {'datum':'2018-09-01','popis':[(1,2),5,10,15]}
with open("json1.txt","w") as f:
  json.dump(data,f)

U datoteci je upisano:

{"datum": "2018-09-01", "popis": [[1, 2], 5, 10, 15]}

Učitavanje iz stringa radimo pomoću json.loads() a iz datoteke pomoću json.load().

import json
with open("json1.txt","r") as f:
  data = json.load(f)
print(data)
print(data['popis'])
{'datum': '2018-09-01', 'popis': [[1, 2], 5, 10, 15]}
[[1, 2], 5, 10, 15]

Binarne datoteke

Strukture

C interno u memoriji drži samo gole podatke koje je stoga lako direktno prepisati iz memorije u datoteku. Python interno u memoriji drži puno više dodatnih informacija pa je potrebno prije upisa u datoteku izvući same podatke u obliku liste byteova. To se radi pomoću funkcije struct.pack().

>>> import struct
>>> print(struct.pack('10s',b"tekst"))
b'tekst\x00\x00\x00\x00\x00'
>>> print(struct.pack('20s',b"tekst"))
b'tekst\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> print(struct.pack('i',10))
b'\n\x00\x00\x00'
>>> print(struct.pack('d',0.1))
b'\x9a\x99\x99\x99\x99\x99\xb9?'

Slijedi primjeri koji upisuju i čitaju riječ, int i double u datoteku.

import struct
L=[b"tekst",10,0.1]
byteovi = struct.pack('10sid',*L)
print(len(byteovi))
with open("bin1.bin","wb") as f:
  f.write(byteovi)
24

Oznaka 10sid sastoji se od 10s, i, d. To znači da će se sastaviti struktura u kojoj će string zauzimati 10 byteova nakon kojih slijede int i double. Pri tome se ubacuje isti alignment kao u C-u. Više o oznakama za kodiranje strukture na https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment i na https://docs.python.org/3/library/struct.html#format-characters.

Pomoću programa hexdump možemo vidjeti sadržaj binarnih datoteka. U terminal upišemo hexdump bin1.bin

#include <stdio.h>
typedef struct {
  char s[10];
  int i;
  double d;
} zapis;

int main()
{
  zapis z = {"tekst", 10, 0.1};
  printf("%lu\n",sizeof(z));
  FILE* f = fopen("bin2.bin","wb");
  if(!f)return 1;
  if( fwrite(&z, sizeof(z), 1, f) != 1 )return 1;
  if( fclose(f) != 0 )return 1;
  return 0;
}
24

U terminal upišemo hexdump bin2.bin.

0000000 74 65 6b 73 74 00 00 00 00 00 00 00 0a 00 00 00
0000010 9a 99 99 99 99 99 b9 3f                        
0000018

Vidimo da je sadržaj datoteke bin1.bin napravljene iz pythona isti kao datoteke bin2.bin napravljene iz C-a.

0000000 74 65 6b 73 74 00 00 00 00 00 00 00 0a 00 00 00
0000010 9a 99 99 99 99 99 b9 3f                        
0000018

Učitavanje iz datoteke:

import struct
with open("bin1.bin","rb") as f:
  byteovi = f.read()
print(struct.unpack('10sid',byteovi))
(b'tekst\x00\x00\x00\x00\x00', 10, 0.1)
#include <stdio.h>
typedef struct {
  char s[10];
  int i;
  double d;
} zapis;

int main()
{
  zapis z;
  FILE* f = fopen("bin2.bin","rb");
  if(!f)return 1;
  if( fread(&z, sizeof(z), 1, f) != 1 )return 1;
  if( fclose(f) != 0 )return 1;
  printf("%s; %d; %f\n",z.s, z.i, z.d);
  return 0;
}
tekst; 10; 0.100000

Homogena polja

Python omogućuje i homogena polja tj. ona koja sadrže elemente istog tipa. Prilikom inicijalizacije potrebno je navesti tip podataka za elemente.

>>> from array import array
>>> z = array( 'l', [1, 2, 3, 4, 5] )
>>> print(z)
array('l', [1, 2, 3, 4, 5])
>>> print(z.itemsize)
8
>>> print(len(z))
5
>>> print(len(z.tobytes()))
40

Byteove koje definiraju polje možemo dobiti pomoću array.tobytes() i nakon toga ih možemo upisati u datoteku. Također je moguće direktno upisivanje iz polja u datoteku pomoću array.tofile().

from array import array
z = array( 'l', [1, 2, 3, 4, 5] )
with open("bin_array1.bin","wb") as f:
  z.tofile(f)
  #ili f.write( z.tobytes() )
print(z.itemsize, len(z))
8 5
#include <stdio.h>

int main()
{
  long z[] = {1, 2, 3, 4, 5};
  size_t l = sizeof(z[0]);
  size_t N = sizeof(z)/l;

  FILE* f = fopen("bin_array2.bin","wb");
  if(!f)return 1;
  if( fwrite(&z, l, N, f) != N )return 1;
  if( fclose(f) != 0 )return 1;

  printf( "%lu %lu\n", l, N );
  return 0;
}
8 5
0000000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
0000010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
0000020 05 00 00 00 00 00 00 00                        
0000028
0000000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
0000010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00
0000020 05 00 00 00 00 00 00 00                        
0000028

Vidimo da su rezultati prethodnih programa isti.

Byteove koji definiraju elemente polja možemo interpretirati kao elemente i dodati ih u postojeće polje pomoću array.frombytes(). Također je moguće byteove elemenata zapisanih u datoteku pročiti i dodati u postojeće polje pomoću array.fromfile().

from array import array
z = array( 'l' )
with open("bin_array1.bin","rb") as f:
  z.fromfile( f, 5 )
  #ili z.frombytes( f.read() )
print(z)
array('l', [1, 2, 3, 4, 5])
#include <stdio.h>

int main()
{
  long z[16];
  size_t l = sizeof(z[0]);
  size_t N = 5;

  FILE* f = fopen("bin_array2.bin","rb");
  if(!f)return 1;
  if( fread(&z, l, N, f) != N )return 1;
  if( fclose(f) != 0 )return 1;

  for(int i=0; i<N; i++ )
    printf("%ld ", z[i]);
  printf("\n");

  return 0;
}
1 2 3 4 5