Operacje na plikach w Pythonie
Ten artykuł wyjaśnia operacje na plikach w Pythonie.
Mając na uwadze „bezpieczeństwo”, „wydajność” i „czytelność”, ten przewodnik wyjaśnia koncepcje krok po kroku, od podstaw po praktyczne techniki.
YouTube Video
Operacje na plikach w Pythonie
Operacje na plikach to podstawowa umiejętność, istotna zarówno w małych skryptach, jak i dużych aplikacjach.
Otwieranie i zamykanie plików
Najpierw przyjrzyjmy się przykładom odczytu i zapisu plików tekstowych. Użycie instrukcji with (menedżera kontekstu) zapewnia prawidłowe zamknięcie pliku.
Poniższy kod otwiera plik tekstowy, odczytuje jego zawartość i przetwarza ją linia po linii.
1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3 # Read all lines lazily (as an iterator) and process each line.
4 for line in f:
5 print(line.rstrip())- Wyraźne określenie
encoding="utf-8"pomaga zredukować problemy zależne od platformy. - Nawet przy dużych plikach,
for line in fjest wydajne pod względem pamięci.
Zapis (nadpisywanie i dopisywanie)
Zwróć uwagę na tryb podczas zapisywania do plików. w służy do nadpisywania, a do dopisywania. Również podczas zapisu używaj instrukcji with.
Poniższy kod pokazuje podstawowe przykłady nadpisywania i dopisywania.
1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3 f.write("First line\n")
4 f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8 f.write("Appended line\n")- Ten kod zapisuje tekst do
output.txt, a następnie dopisuje kolejne dane do tego samego pliku. Tryb"w"nadpisuje plik, a tryb"a"dopisuje nową linię na końcu istniejącej zawartości. - Jeśli potrzebujesz opróżnić bufor, wywołaj
flush(), ale zazwyczaj zawartość jest opróżniana automatycznie po zakończeniu bloku kontekstu. - Jeśli wiele procesów lub wątków może pisać jednocześnie, należy rozważyć użycie mechanizmów wyłącznego dostępu, takich jak blokowanie plików.
Odczyt i zapis danych binarnych
Obrazy i pliki skompresowane obsługuje się w trybie binarnym (rb lub wb). W trybie binarnym parametr encoding jest ignorowany.
Poniższy kod odczytuje plik binarny i kopiuje go do innego pliku w trybie binarnym.
1# Read and write binary files.
2with open("image.png", "rb") as src:
3 data = src.read()
4
5with open("copy.png", "wb") as dst:
6 dst.write(data)- Przy obsłudze dużych plików binarnych unikaj wczytywania wszystkiego naraz przez
read(); czytanie i zapisywanie w kawałkach jest bardziej efektywne pamięciowo.
Przykład obsługi dużych plików w kawałkach
W przypadku bardzo dużych plików, które nie mieszczą się w pamięci, czytaj i zapisuj je kawałkami o stałym rozmiarze. Jako że operacje są ograniczone przez I/O, warto dostosować rozmiar bufora.
Poniższy kod kopiuje plik w kawałkach o rozmiarze 64KB. Dzięki temu operacja przebiega szybko przy niskim użyciu pamięci.
1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024 # 64 KB
3
4with open("large_source.bin", "rb") as src, open("large_dest.bin", "wb") as dst:
5 while True:
6 chunk = src.read(CHUNK_SIZE)
7 if not chunk:
8 break
9 dst.write(chunk)- Możesz dostosować rozmiar kawałków do właściwości swojej sieci lub dysku. Na dyskach SSD zwykle lepiej działa nieco większy rozmiar kawałka.
Przykład użycia argumentu buffering
Określając argument buffering w funkcji open(), możesz kontrolować rozmiar bufora używanego wewnętrznie przez Pythona. Pozwala to dodatkowo zoptymalizować wydajność operacji wejścia/wyjścia.
1# Explicitly set the internal buffer size to 128 KB for faster I/O.
2CHUNK_SIZE = 64 * 1024
3BUFFER_SIZE = 128 * 1024 # 128 KB internal buffer
4
5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src, open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
6 while True:
7 chunk = src.read(CHUNK_SIZE)
8 if not chunk:
9 break
10 dst.write(chunk)- Jeśli ustawisz wartość
bufferingna 0, operacje wejścia/wyjścia są wykonywane bez buforowania; 1 włącza buforowanie liniowe, natomiast wartości 2 lub wyższe używają bufora o określonym rozmiarze w bajtach. - Zazwyczaj wartość domyślna jest wystarczająca, ponieważ system operacyjny wydajnie zarządza pamięcią podręczną, jednak dostosowanie tego parametru może być skuteczne w przypadku bardzo dużych plików lub specjalnych urządzeń.
Nowoczesne operacje na plikach z użyciem Pathlib
Standardowy moduł pathlib sprawia, że obsługa ścieżek jest bardziej intuicyjna. Poprawia to czytelność i bezpieczeństwo w porównaniu do użycia ścieżek tekstowych.
1from pathlib import Path
2
3path = Path("data") / "notes.txt"
4
5# Ensure parent directory exists.
6path.parent.mkdir(parents=True, exist_ok=True)
7
8# Write and read using pathlib.
9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)- Ten kod demonstruje tworzenie katalogu przy pomocy
pathlib, zapis do pliku tekstowego i odczyt jego zawartości. Dzięki obiektowiPathmożesz obsługiwać ścieżki w sposób intuicyjny i bezpieczny. Pathoferuje wygodne metody, takie jakiterdir()orazglob(). Możesz pisać kod bez martwienia się o separatory ścieżek w różnych systemach operacyjnych.
Pliki i katalogi tymczasowe (tempfile)
Pliki tymczasowe można bezpiecznie tworzyć za pomocą modułu tempfile. Dzięki temu unikasz warunków wyścigu i kolizji nazw.
Poniższy kod pokazuje przykład tworzenia danych tymczasowych przy użyciu pliku tymczasowego.
1import tempfile
2
3# Create a temporary file that is deleted on close.
4with tempfile.NamedTemporaryFile(
5 mode="w+",
6 encoding="utf-8",
7 delete=True
8) as tmp:
9 tmp.write("temporary data\n")
10 tmp.seek(0)
11 print(tmp.read())
12
13# tmp is deleted here- Ten kod tworzy plik tymczasowy, zapisuje i odczytuje dane, a następnie automatycznie usuwa plik po zakończeniu bloku
with. Korzystając ztempfile.NamedTemporaryFile, możesz obsługiwać pliki tymczasowe bezpiecznie i bez konfliktów. Ponieważ ustawionodelete=True, plik jest automatycznie usuwany. - W systemie Windows plik może nie być dostępny od razu z innego uchwytu, więc ustaw
delete=Falsei samodzielnie zadbaj o jego usunięcie.
shutil: Operacje wyższego poziomu dla kopiowania, przenoszenia i usuwania
Rekursywne kopiowanie, przenoszenie i usuwanie plików oraz katalogów są łatwe dzięki shutil.
1import shutil
2
3# Copy a file preserving metadata.
4shutil.copy2("source.txt", "dest.txt")
5
6# Move a file or directory.
7shutil.move("old_location", "new_location")
8
9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")shutil.copy2kopiuje także metadane (np. czas modyfikacji).movewykona przeniesienie pliku nawet jeśli nie można użyć zamiany nazwy (rename).- Operacja
rmtreejest niebezpieczna, więc zawsze potwierdź i wykonaj kopię zapasową przed usunięciem.
Metadane plików (os.stat) i obsługa uprawnień
Rozmiar pliku, czas modyfikacji oraz uprawnienia można odczytywać i zmieniać za pomocą os i stat.
Poniższy kod uzyskuje podstawowe informacje o pliku przez os.stat oraz zmienia uprawnienia przez os.chmod.
1import os
2import stat
3from datetime import datetime
4
5st = os.stat("example.txt")
6print("size:", st.st_size)
7print("modified:", datetime.fromtimestamp(st.st_mtime))
8
9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)- Uprawnienia zachowują się różnie w systemach POSIX i Windows. Jeśli ważna jest kompatybilność między platformami, używaj wyższych API lub dodaj obsługę warunkową.
Blokowanie plików (dostęp wyłączny) — różnice między Unix a Windows
Dostęp wyłączny jest potrzebny, gdy wiele procesów równolegle korzysta z tego samego pliku. W UNIX używane jest fcntl, a w Windows msvcrt.
Poniższy kod stosuje fcntl.flock w systemach UNIX do uzyskania wyłącznej blokady podczas zapisu.
1# Unix-style file locking example
2import fcntl
3
4with open("output.txt", "a+", encoding="utf-8") as f:
5 fcntl.flock(f, fcntl.LOCK_EX)
6 try:
7 f.write("Safe write\n")
8 f.flush()
9 finally:
10 fcntl.flock(f, fcntl.LOCK_UN)- Ten kod uzyskuje wyłączną blokadę przez
fcntl.flockw systemach typu UNIX, co umożliwia bezpieczny zapis i zapobiega jednoczesnym zapisom do pliku. Zawsze zwalniaj blokadę po zakończonej operacji, aby umożliwić dostęp innym procesom. - W systemie Windows używaj
msvcrt.locking(). Do zastosowań wyższego poziomu rozważ użycie zewnętrznych bibliotek, takich jakportalockerczyfilelock.
Atomowy zapis plików
Aby zapobiec uszkodzeniu pliku podczas aktualizacji, najpierw zapisz do pliku tymczasowego, a następnie zastąp oryginał za pomocą os.replace po udanym zapisie.
Zapis do pliku tymczasowego i późniejsza podmiana pozwala uniknąć uszkodzenia pliku w przypadku awarii.
1import os
2from pathlib import Path
3import tempfile
4
5def atomic_write(path: Path, data: str, encoding="utf-8"):
6 # Write to a temp file in the same directory and atomically replace.
7 dirpath = path.parent
8 with tempfile.NamedTemporaryFile(
9 mode="w",
10 encoding=encoding,
11 dir=str(dirpath),
12 delete=False
13 ) as tmp:
14 tmp.write(data)
15 tmp.flush()
16 tempname = tmp.name
17
18 # Atomic replacement (on most OSes)
19 os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')os.replace wykonuje atomową zamianę pliku w obrębie tego samego systemu plików. Pamiętaj, że atomowość nie jest gwarantowana między różnymi punktami montowania.
Szybki dostęp przy użyciu mmap (dla dużych zbiorów danych)
Do losowego dostępu do dużych plików mmap poprawia wydajność operacji I/O. Dotyczy to głównie operacji binarnych.
Poniższy kod mapuje plik do pamięci i odczytuje bądź zapisuje określone zakresy bajtów. Zachowaj ostrożność podczas zmiany rozmiaru pliku.
1import mmap
2
3with open("data.bin", "r+b") as f:
4 mm = mmap.mmap(f.fileno(), 0) # map entire file
5 try:
6 print(mm[:20]) # first 20 bytes
7 mm[0:4] = b"\x00\x01\x02\x03" # modify bytes
8 finally:
9 mm.close()- Ten kod mapuje binarny plik do pamięci za pomocą
mmapi wykonuje bezpośrednie operacje odczytu/zapisu na poziomie bajtów. Mapowanie pamięci pozwala na szybki, losowy dostęp do dużych zbiorów danych. mmapjest wydajny, ale nieprawidłowe użycie może prowadzić do problemów z integralnością danych. Wywołujflush(), aby synchronizować dane według potrzeb.
CSV / JSON / Pickle: Odczyt i zapis zgodnie z formatem
Dla określonych formatów danych istnieją dedykowane moduły. Użyj csv do plików CSV, json do plików JSON oraz pickle do zapisywania obiektów Pythona.
Poniższy kod przedstawia podstawowe przykłady odczytu i zapisu CSV i JSON, a także użycia Pickle. Ponieważ Pickle może wykonywać dowolny kod, unikaj ładowania danych z nieznanych źródeł.
1import csv
2import json
3import pickle
4
5# CSV write
6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
7 writer = csv.writer(f)
8 writer.writerow(["name", "age"])
9 writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14 json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19 pickle.dump(obj, f)- Zaleca się ustawienie
newline=""dla CSV, aby uniknąć dodatkowych pustych linii w systemie Windows. Przy ustawieniuensure_ascii=False, plik JSON zachowuje czytelność znaków UTF-8.
Bezpośredni odczyt i zapis plików skompresowanych (gzip / bz2 / zipfile)
Bezpośrednia obsługa gzip i zip pozwala zaoszczędzić miejsce na dysku. Biblioteka standardowa zawiera odpowiednie moduły.
Poniższy kod to prosty przykład odczytu i zapisu plików gzip jako tekst.
1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5 f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9 print(f.read())- Istnieje kompromis między współczynnikiem kompresji a szybkością w zależności od poziomu kompresji i formatu.
Środki bezpieczeństwa i przeciwdziałania podatnościom
Następujące kwestie można wziąć pod uwagę przy środkach bezpieczeństwa i przeciwdziałania podatnościom.
- Nie używaj niezweryfikowanych danych wejściowych bezpośrednio w nazwach plików ani ścieżkach.
- Używaj Pickle tylko z zaufanych źródeł.
- Minimalizuj uprawnienia wykonawcze i nadawaj tylko niezbędne uprawnienia procesom obsługującym pliki.
- Używaj plików tymczasowych przez
tempfilei nie przechowuj zwykłych plików w katalogach publicznych.
Jeśli wykorzystujesz dane użytkownika w ścieżkach plików, wymagane jest ich normalizowanie i walidacja. Na przykład użyj Path.resolve() i sprawdź katalogi nadrzędne.
1from pathlib import Path
2
3def safe_path(base_dir: Path, user_path: str) -> Path:
4 candidate = (base_dir / user_path).resolve()
5 if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
6 raise ValueError("Invalid path")
7 return candidate
8
9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')- Zachowaj szczególną ostrożność, gdy w aplikacjach webowych lub publicznych API używasz zewnętrznych danych jako ścieżek.
Podsumowanie typowych wzorców
- Zawsze używaj instrukcji
with(automatyczne zamknięcie pliku). - Wyraźnie określ kodowanie (
encoding) dla danych tekstowych. - Odczytuj i zapisuj duże pliki w kawałkach.
- Wprowadź blokowanie plików dla współdzielonych zasobów.
- Dla krytycznych aktualizacji używaj atomowego wzorca: „zapis do pliku tymczasowego → os.replace”.
- Zawsze potwierdzaj i wykonuj kopie zapasowe przed wykonaniem niebezpiecznych operacji (takich jak usuwanie lub nadpisywanie).
- Normalizuj i weryfikuj dane podczas używania zewnętrznego wejścia jako ścieżek plików.
Podsumowanie
Podczas wykonywania operacji na plikach ważne jest korzystanie z bezpiecznych i niezawodnych technik, takich jak użycie instrukcji with, jawne określanie kodowania oraz zapisy atomowe. W przypadku przetwarzania na dużą skalę lub dostępu równoległego konieczne jest wdrożenie systemów blokowania i zarządzania logami, aby zapobiec uszkodzeniom danych i konfliktom. Zrównoważenie wydajności i bezpieczeństwa jest kluczem do niezawodnych operacji na plikach.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.