Rad sa datotekama i I/O operacije u Python-u

Gotovo svaki program u jednom trenutku mora da komunicira sa spoljnim svetom – bilo da čita podatke iz datoteka, upisuje rezultate obrade, ili razmenjuje informacije preko mreže. Python nudi bogat skup alata za rad sa različitim tipovima ulazno/izlaznih (I/O) operacija. U ovom članku ćemo detaljno istražiti kako Python upravlja različitim tipovima datoteka, od tekstualnih do binarnih, kao i kako efikasno strukturirati I/O operacije u vašim programima.

Osnove rada sa datotekama

Rad sa datotekama u Python-u prati jednostavan obrazac: otvorite datoteku, izvršite operacije čitanja ili pisanja, a zatim zatvorite datoteku. Ovaj proces je ključan za pravilno upravljanje resursima.

Otvaranje datoteka

Najčešći način otvaranja datoteke je pomoću ugrađene funkcije open():

# Otvaranje datoteke za čitanje (podrazumevani režim)
file = open('primer.txt', 'r')

# Otvaranje datoteke za pisanje (postojeći sadržaj se briše)
file = open('izlaz.txt', 'w')

# Otvaranje datoteke za dodavanje (append) na kraj
file = open('log.txt', 'a')

Funkcija open() prima dva glavna parametra:

  • Putanja do datoteke (relativna ili apsolutna)
  • Režim otvaranja koji određuje operacije koje možete izvršiti:
    • 'r' – čitanje (podrazumevano)
    • 'w' – pisanje (briše postojeći sadržaj)
    • 'a' – dodavanje na kraj datoteke
    • 'x' – ekskluzivno kreiranje (greška ako datoteka već postoji)
    • 'b' – binarni režim (npr. 'rb' za čitanje binarne datoteke)
    • 't' – tekstualni režim (podrazumevano)
    • '+' – ažuriranje (čitanje i pisanje)

Zatvaranje datoteka

Nakon što završite sa datotekom, važno je da je zatvorite da biste oslobodili sistemske resurse:

file = open('primer.txt', 'r')
# Rad sa datotekom...
file.close()

Korišćenje kontekst menadžera (with izjava)

Umesto ručnog zatvaranja datoteke, preporučena praksa je korišćenje kontekst menadžera (with izjava) koji će automatski zatvoriti datoteku, čak i ako dođe do greške:

with open('primer.txt', 'r') as file:
# Rad sa datotekom...
sadrzaj = file.read()

# Datoteka je automatski zatvorena kad se izađe iz 'with' bloka

Kontekst menadžer osigurava da se datoteka pravilno zatvori čak i ako se dogodi izuzetak unutar bloka, što je mnogo sigurnija praksa od ručnog pozivanja close().

Čitanje iz tekstualnih datoteka

Python nudi nekoliko metoda za čitanje iz tekstualnih datoteka, svaki sa svojim prednostima.

Čitanje celog sadržaja

with open('primer.txt', 'r', encoding='utf-8') as file:
sadrzaj = file.read()
print(sadrzaj)

Parametar encoding određuje kako se bajtovi u datoteci interpretiraju. Najčešće se koristi 'utf-8' jer podržava internacionalne karaktere. Izostavljanje ovog parametra znači korišćenje podrazumevanog kodiranja sistema, što može dovesti do problema sa posebnim karakterima.

Čitanje liniju po liniju

Za velike datoteke, efikasnije je čitati jednu liniju odjednom:

with open('primer.txt', 'r', encoding='utf-8') as file:
for linija in file:
print(linija, end='') # 'end=""' sprečava dvostruki newline

Datoteke se mogu iterirati direktno, što je memorijski efikasno jer se učitava samo jedna linija u isto vreme.

Čitanje svih linija u listu

with open('primer.txt', 'r', encoding='utf-8') as file:
linije = file.readlines()

for linija in linije:
print(linija, end='')

Metod readlines() vraća listu gde svaki element predstavlja jednu liniju u datoteci, uključujući karakter za novi red (\n).

Čitanje određenog broja karaktera

with open('primer.txt', 'r', encoding='utf-8') as file:
deo_sadrzaja = file.read(100) # Čita prvih 100 karaktera
print(deo_sadrzaja)

Pisanje u tekstualne datoteke

Pisanje u datoteke je takođe jednostavno sa Python-om.

Pisanje stringa

with open('izlaz.txt', 'w', encoding='utf-8') as file:
file.write("Ovo je prva linija.\n")
file.write("Ovo je druga linija.\n")

Pisanje više linija odjednom

linije = ["Prva linija.\n", "Druga linija.\n", "Treća linija.\n"]

with open('izlaz.txt', 'w', encoding='utf-8') as file:
file.writelines(linije)

Napomena: writelines() ne dodaje automatski karaktere za novi red. Morate ih eksplicitno uključiti u svoje stringove ako želite da svaki element bude u novoj liniji.

Dodavanje (append) na kraj datoteke

with open('log.txt', 'a', encoding='utf-8') as file:
file.write("Novi log zapis.\n")

Rad sa binarnim datotekama

Za rad sa datotekama koje sadrže binarne podatke (slike, zvuk, video, itd.), koristite binarni režim otvaranja.

Čitanje binarne datoteke

with open('slika.jpg', 'rb') as file:
podaci = file.read()
print(f"Veličina slike: {len(podaci)} bajtova")

Pisanje u binarnu datoteku

# Kreiranje binarnih podataka
binarni_podaci = bytes([0, 1, 2, 3, 4, 5])

# Pisanje u binarnu datoteku
with open('podaci.bin', 'wb') as file:
file.write(binarni_podaci)

Rad sa putanjama datoteka

Python-ov modul os.path pruža funkcije za rad sa putanjama koje su kompatibilne sa različitim operativnim sistemima.

import os.path

# Spajanje delova putanje
putanja = os.path.join('direktorijum', 'poddirektorijum', 'datoteka.txt')
print(putanja) # 'direktorijum/poddirektorijum/datoteka.txt' na Unix-u

# Provera da li datoteka postoji
postoji = os.path.exists('primer.txt')
print(f"Datoteka postoji: {postoji}")

# Dobijanje apsolutne putanje
abs_putanja = os.path.abspath('primer.txt')
print(f"Apsolutna putanja: {abs_putanja}")

# Razdvajanje putanje na direktorijum i naziv datoteke
direktorijum, naziv = os.path.split('/home/korisnik/dokumenti/datoteka.txt')
print(f"Direktorijum: {direktorijum}")
print(f"Naziv: {naziv}")

# Dobijanje ekstenzije datoteke
_, ekstenzija = os.path.splitext('slika.jpg')
print(f"Ekstenzija: {ekstenzija}") # '.jpg'

Od Python-a 3.4 nadalje, preporučuje se korištenje modula pathlib koji pruža objektno-orijentisani pristup operacijama sa datotekama i putanjama:

from pathlib import Path

# Kreiranje Path objekta
putanja = Path('direktorijum') / 'poddirektorijum' / 'datoteka.txt'
print(putanja)

# Provera da li datoteka postoji
postoji = putanja.exists()

# Čitanje sadržaja datoteke
if postoji:
sadrzaj = putanja.read_text(encoding='utf-8')
print(sadrzaj)

# Pisanje u datoteku
putanja.write_text("Novi sadržaj", encoding='utf-8')

# Dobijanje informacija o putanji
print(f"Roditelj: {putanja.parent}")
print(f"Naziv: {putanja.name}")
print(f"Stem: {putanja.stem}")
print(f"Sufiks: {putanja.suffix}")

Rad sa standardnim ulazom/izlazom

Python ima nekoliko ugrađenih objekata za rad sa standardnim ulazom, izlazom i greškom:

  • sys.stdin – standardni ulaz
  • sys.stdout – standardni izlaz
  • sys.stderr – standardni izlaz za greške
import sys

# Čitanje sa standardnog ulaza
linija = sys.stdin.readline()
print(f"Uneli ste: {linija}")

# Pisanje na standardni izlaz
sys.stdout.write("Ovo je poruka na standardnom izlazu.\n")

# Pisanje na standardni izlaz za greške
sys.stderr.write("Ovo je poruka o grešci.\n")

Funkcije input() i print() su jednostavnije alternative za rad sa standardnim ulazom i izlazom:

# Čitanje sa standardnog ulaza
ime = input("Unesite svoje ime: ")

# Pisanje na standardni izlaz
print(f"Zdravo, {ime}!")

# Print sa više argumenata
print("Vrednosti:", 1, 2, 3, sep=", ", end="!\n")

Rad sa različitim formatima datoteka

Python ima biblioteke za rad sa raznim formatima datoteka. Pogledajmo neke od najčešćih.

CSV (Comma-Separated Values)

CSV format je popularan za tabelarne podatke:

import csv

# Čitanje CSV datoteke
with open('podaci.csv', 'r', newline='', encoding='utf-8') as file:
csv_reader = csv.reader(file)
for red in csv_reader:
print(red) # red je lista vrednosti

# Čitanje u rečnik
with open('podaci.csv', 'r', newline='', encoding='utf-8') as file:
csv_reader = csv.DictReader(file)
for red in csv_reader:
print(red) # red je rečnik gde su ključevi nazivi kolona

# Pisanje CSV datoteke
with open('izlaz.csv', 'w', newline='', encoding='utf-8') as file:
csv_writer = csv.writer(file)
csv_writer.writerow(['Ime', 'Godine', 'Grad'])
csv_writer.writerow(['Ana', 25, 'Beograd'])
csv_writer.writerow(['Marko', 30, 'Novi Sad'])

# Pisanje koristeći rečnike
with open('izlaz.csv', 'w', newline='', encoding='utf-8') as file:
polja = ['Ime', 'Godine', 'Grad']
csv_writer = csv.DictWriter(file, fieldnames=polja)

csv_writer.writeheader()
csv_writer.writerow({'Ime': 'Ana', 'Godine': 25, 'Grad': 'Beograd'})
csv_writer.writerow({'Ime': 'Marko', 'Godine': 30, 'Grad': 'Novi Sad'})

Parametar newline='' je važan kada radite sa CSV datotekama da biste osigurali da se novi redovi pravilno obrađuju na različitim platformama.

JSON (JavaScript Object Notation)

JSON je lagan format za razmenu podataka:

import json

# Python objekat
podaci = {
'ime': 'Ana',
'godine': 25,
'grad': 'Beograd',
'hobi': ['čitanje', 'plivanje', 'programiranje'],
'aktivan': True
}

# Serijalizacija (Python -> JSON string)
json_str = json.dumps(podaci, indent=4)
print(json_str)

# Pisanje JSON u datoteku
with open('podaci.json', 'w', encoding='utf-8') as file:
json.dump(podaci, file, indent=4)

# Čitanje JSON iz datoteke
with open('podaci.json', 'r', encoding='utf-8') as file:
ucitani_podaci = json.load(file)
print(ucitani_podaci)

# Deserijalizacija (JSON string -> Python)
json_string = '{"ime": "Marko", "godine": 30}'
obj = json.loads(json_string)
print(obj['ime']) # Marko

Parametar indent formatirani JSON čini čitljivijim.

XML (eXtensible Markup Language)

Za rad sa XML, možete koristiti modul xml.etree.ElementTree:

import xml.etree.ElementTree as ET

# Kreiranje XML strukture
root = ET.Element("korisnici")

korisnik1 = ET.SubElement(root, "korisnik")
ET.SubElement(korisnik1, "ime").text = "Ana"
ET.SubElement(korisnik1, "godine").text = "25"

korisnik2 = ET.SubElement(root, "korisnik")
ET.SubElement(korisnik2, "ime").text = "Marko"
ET.SubElement(korisnik2, "godine").text = "30"

# Pisanje XML u datoteku
tree = ET.ElementTree(root)
tree.write("korisnici.xml", encoding='utf-8', xml_declaration=True)

# Čitanje XML iz datoteke
tree = ET.parse('korisnici.xml')
root = tree.getroot()

for korisnik in root.findall('korisnik'):
ime = korisnik.find('ime').text
godine = korisnik.find('godine').text
print(f"Korisnik: {ime}, Godine: {godine}")

YAML (YAML Ain’t Markup Language)

YAML je čoveku-čitljiv format za konfiguracije. Nije u standardnoj biblioteci, pa morate instalirati paket pyyaml:

pip install pyyaml
import yaml

# Python objekat
config = {
'server': {
'host': 'localhost',
'port': 5000
},
'database': {
'username': 'admin',
'password': 'secret',
'name': 'app_db'
},
'debug': True
}

# Pisanje YAML u datoteku
with open('config.yaml', 'w') as file:
yaml.dump(config, file, default_flow_style=False)

# Čitanje YAML iz datoteke
with open('config.yaml', 'r') as file:
loaded_config = yaml.safe_load(file)
print(loaded_config)

Rad sa privremenim datotekama

Modul tempfile vam omogućava da radite sa privremenim datotekama i direktorijumima:

import tempfile
import os

# Kreiranje privremene datoteke
with tempfile.NamedTemporaryFile(delete=False) as temp:
temp.write(b"Ovo su neki privremeni podaci.\n")
tempname = temp.name

print(f"Privremena datoteka: {tempname}")

# Korišćenje privremene datoteke
with open(tempname, 'rb') as file:
content = file.read()
print(content.decode('utf-8'))

# Brisanje privremene datoteke kad više nije potrebna
os.unlink(tempname)

# Kreiranje privremenog direktorijuma
with tempfile.TemporaryDirectory() as tempdir:
print(f"Kreiran privremeni direktorijum: {tempdir}")
# Korišćenje direktorijuma...
# Direktorijum se automatski briše po izlasku iz bloka

Serijalizacija objekata sa pickle

Modul pickle vam omogućava da serijalizujete i deserijalizujete Python objekte:

import pickle

# Objekat za serijalizaciju
class Osoba:
def __init__(self, ime, godine):
self.ime = ime
self.godine = godine

def pozdrav(self):
return f"Zdravo, ja sam {self.ime}!"

osoba = Osoba("Ana", 25)

# Serijalizacija: čuvanje objekta u datoteku
with open('osoba.pickle', 'wb') as file:
pickle.dump(osoba, file)

# Deserijalizacija: učitavanje objekta iz datoteke
with open('osoba.pickle', 'rb') as file:
ucitana_osoba = pickle.load(file)
print(ucitana_osoba.pozdrav()) # Zdravo, ja sam Ana!

Važno upozorenje o pickle-u: Ne deserijalizujte pickle datoteke iz nepouzdanih izvora, jer to može omogućiti izvršavanje proizvoljnog koda.

Kompresija datoteka

Python pruža module za rad sa kompresovanim datotekama.

ZIP datoteke

import zipfile
import os

# Kreiranje ZIP arhive
with zipfile.ZipFile('arhiva.zip', 'w') as zipf:
zipf.write('primer.txt')
zipf.write('podaci.json')

# Čitanje ZIP arhive
with zipfile.ZipFile('arhiva.zip', 'r') as zipf:
print(zipf.namelist()) # Lista svih datoteka u arhivi

# Ekstrahovanje svih datoteka
zipf.extractall('extracted_files')

# Čitanje datoteke iz arhive bez ekstrahovanja
content = zipf.read('primer.txt')
print(content.decode('utf-8'))

GZIP kompresija

import gzip

# Kompresovanje stringa
original_data = "Ovo je neki tekst koji će biti kompresovan." * 100
compressed_data = gzip.compress(original_data.encode('utf-8'))

print(f"Original: {len(original_data)} bajtova")
print(f"Kompresovano: {len(compressed_data)} bajtova")

# Pisanje kompresovanih podataka u datoteku
with gzip.open('podaci.txt.gz', 'wb') as f:
f.write(original_data.encode('utf-8'))

# Čitanje kompresovanih podataka
with gzip.open('podaci.txt.gz', 'rb') as f:
decompressed_data = f.read().decode('utf-8')
print(f"Dekompresovano: {len(decompressed_data)} bajtova")
assert original_data == decompressed_data

Baferovani I/O i performanse

Kada radite sa velikim datotekama, baferovane I/O operacije mogu značajno poboljšati performanse.

Čitanje velikih datoteka u delovima

def count_lines(filename):
count = 0
buffer_size = 1024 * 1024 # 1MB

with open(filename, 'r', encoding='utf-8') as file:
while True:
buffer = file.read(buffer_size)
if not buffer:
break
count += buffer.count('\n')

return count

# Pretpostavimo da imamo veliku tekstualnu datoteku
try:
num_lines = count_lines('velika_datoteka.txt')
print(f"Datoteka ima {num_lines} linija")
except FileNotFoundError:
print("Datoteka nije pronađena.")

Korišćenje io modula za prilagođeni baferovani I/O

import io

# Kreiranje baferovane tekstualne datoteke u memoriji
buffer = io.StringIO()
buffer.write("Prva linija.\n")
buffer.write("Druga linija.\n")

# Čitanje iz bafera
buffer.seek(0) # Vraćanje pozicije na početak
sadrzaj = buffer.read()
print(sadrzaj)

# Zatvaranje bafera
buffer.close()

# Kreiranje baferovane binarne datoteke u memoriji
binary_buffer = io.BytesIO()
binary_buffer.write(b"\x00\x01\x02\x03")

# Čitanje iz binarnog bafera
binary_buffer.seek(0)
binary_content = binary_buffer.read()
print(binary_content)

binary_buffer.close()

Ovi memorijski baferi su korisni za testiranje ili kada želite da koristite datotečni API bez stvarnog pisanja na disk.

Praćenje promena u datotekama (file watching)

Za praćenje promena u datotekama, možete koristiti biblioteku watchdog:

pip install watchdog
pythonimport time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class PromeneHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.is_directory:
            return
        print(f"Datoteka {event.src_path} je modifikovana!")
    
    def on_created(self, event):
        if event.is_directory:
            return
        print(f"Datoteka {event.src_path} je kreirana!")

path = "."  # Tekući direktorijum
event_handler = PromeneHandler()
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()

try:
    print(f"Pratim promene u direktorijumu: {path}")
    print("Pritisnite Ctrl+C za prekid...")
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    observer.stop()

observer.join()

Asinhroni I/O

Za aplikacije koje zahtevaju visoke performanse, asinhroni I/O može biti odlično rešenje. Modul asyncio omogućava pisanje konkurentnog koda koristeći async/await sintaksu.

import asyncio
import aiofiles # Morate instalirati: pip install aiofiles

async def read_file_async(filename):
async with aiofiles.open(filename, 'r', encoding='utf-8') as file:
return await file.read()

async def write_file_async(filename, content):
async with aiofiles.open(filename, 'w', encoding='utf-8') as file:
await file.write(content)

async def main():
# Čitanje više datoteka paralelno
tasks = [
read_file_async('datoteka1.txt'),
read_file_async('datoteka2.txt'),
read_file_async('datoteka3.txt')
]
results = await asyncio.gather(*tasks, return_exceptions=True)

for i, result in enumerate(results):
if isinstance(result, Exception):
print(f"Greška pri čitanju datoteke {i+1}: {result}")
else:
print(f"Datoteka {i+1} sadrži {len(result)} karaktera")

# Pisanje u datoteku asinhrono
await write_file_async('izlaz.txt', "Ovo je asinhroni tekst.")
print("Zapisivanje završeno.")

# Pokretanje asinhronog programa
asyncio.run(main())

Najbolje prakse za I/O operacije

1. Uvek koristite kontekst menadžere

# Dobro
with open('datoteka.txt', 'r') as file:
sadrzaj = file.read()

# Izbegavajte
file = open('datoteka.txt', 'r')
sadrzaj = file.read()
file.close() # Može biti propušteno ako dođe do izuzetka

2. Eksplicitno definišite enkoding

# Dobro
with open('datoteka.txt', 'r', encoding='utf-8') as file:
sadrzaj = file.read()

# Problematično
with open('datoteka.txt', 'r') as file: # Koristi sistemski podrazumevani enkoding
sadrzaj = file.read()

3. Koristite baferovano čitanje za velike datoteke

# Dobro - memorijski efikasno
with open('velika_datoteka.txt', 'r', encoding='utf-8') as file:
for linija in file: # Čita jednu liniju odjednom
process_line(linija)

# Ili učitajte određenu veličinu odjednom
with open('velika_datoteka.txt', 'r', encoding='utf-8') as file:
while True:
chunk = file.read(1024 * 1024) # 1MB
if not chunk:
break
process_chunk(chunk)

# Izbegavajte za velike datoteke
with open('velika_datoteka.txt', 'r', encoding='utf-8') as file:
sadrzaj = file.read() # Učitava celu datoteku u memoriju

4. Koristite biblioteke višeg nivoa za strukturirane podatke

# Dobro - koristi specijalizovane parsere
import csv
with open('podaci.csv', 'r', newline='', encoding='utf-8') as file:
reader = csv.reader(file)
for row in reader:
process_row(row)

# Izbegavajte - reinventiranje točka
with open('podaci.csv', 'r', encoding='utf-8') as file:
for line in file:
row = line.strip().split(',') # Naivno parsiranje CSV-a
process_row(row)

5. Rukujte izuzecima specifično za I/O

try:
with open('datoteka.txt', 'r', encoding='utf-8') as file:
sadrzaj = file.read()
except FileNotFoundError:
print("Datoteka ne postoji!")
except PermissionError:
print("Nemate dozvolu za čitanje datoteke!")
except UnicodeDecodeError:
print("Problem sa enkodingom datoteke!")
except Exception as e:
print(f"Neočekivana greška: {e}")

6. Koristite pathlib za manipulacije putanjama

# Moderno i čisto
from pathlib import Path

data_dir = Path("podaci")
file_path = data_dir / "izvestaj.txt"

if file_path.exists():
content = file_path.read_text(encoding='utf-8')

# Tradicionalno i manje intuitivno
import os.path

data_dir = "podaci"
file_path = os.path.join(data_dir, "izvestaj.txt")

if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()

Zaključak

Sposobnost efikasnog rada sa datotekama i I/O operacijama je osnovna veština u programiranju. Python pruža bogat skup alata za sve vrste I/O operacija – od jednostavnog čitanja i pisanja tekstualnih datoteka do složenog asinhronog I/O-a i serijalizacije objekata.

Najvažniji principi za efikasan rad sa datotekama su:

  • Korišćenje kontekst menadžera za automatsko upravljanje resursima
  • Razumevanje različitih tipova datoteka i odgovarajućih načina za rad sa njima
  • Pravilno rukovanje izuzecima specifičnim za I/O operacije
  • Odabir pravog nivoa apstrakcije za vaš zadatak (od niskog do visokog nivoa API-ja)

Pažljivo razmišljanje o I/O operacijama je važno za pisanje efikasnih, robusnih i održivih programa. Sa pravim pristupom, možete značajno poboljšati performanse i pouzdanost vaših aplikacija.

U sledećem članku, obradićemo izuzetke i rukovanje greškama u Python-u, što je direktno povezano sa sigurnim izvođenjem I/O operacija koje smo obradili ovde.

Leave a Comment