Файлове
Проблем
Програмата забравя
Пишеш програма за разходите си. Въвеждаш ги вечер, затваряш я, а на другия ден тя не знае нищо за вчера. Ето най-краткият пример:
visits = 0visits += 1print(f"Пусната {visits} път.")Пусната 1 път.Колкото пъти и да я пуснеш, казва „1 път“. Променливите живеят в паметта, докато програмата работи. Когато свърши, паметта се освобождава и всичко изчезва.
За да помни, програмата трябва да запише данните там, където остават и след нея: във файл на диска.
Идеята
Файл с open и with
Ето цяла програма, която записва бележка във файл и я чете обратно:
try: with open("notes.txt", "w", encoding="utf-8") as file: file.write("Купи мляко\nПлати тока до 15-ти\n") with open("notes.txt", encoding="utf-8") as file: text = file.read() print(text, end="")except OSError as e: print(f"Проблем с файла: {e}")Купи млякоПлати тока до 15-тиopen(име, режим)отваря файла."notes.txt"е файл с това име в папката, от която е пусната програмата. Режимът"w"(write) е за писане: създава файла, ако го няма, и го изпразва, ако го има. Без режим файлът се отваря за четене.with ... as file:дава отворения файл под иметоfileи го затваря сам, когато отместените редове свършат. Защо затварянето е важно, ще видиш по-нататък.file.write(текст)записва, аfile.read()чете целия файл като един текст.encoding="utf-8"казва как буквите стават байтове. Пиши го винаги: без него Python ползва кодировката на компютъра, а на Windows тя често не е UTF-8 и кирилицата излиза сбъркана.OSErrorе общото изключение за проблем с файл: дискът е пълен, файлът е заключен или го няма. Python не те задължава да го хванеш (компилаторът на Java го изисква), но безexceptпрограмата спира.
Пусни програмата, после отвори папката: notes.txt е там и може да се отвори с всеки текстов редактор. Следващото пускане ще го намери.
Идеята
Добави и прочети редовете
Разходите се добавят един по един. Режимът "a" (append, „добави“) дописва в края, вместо да записва файла наново. read().splitlines() разделя прочетения текст на редове:
try: with open("expenses.txt", "w", encoding="utf-8") as file: file.write("Обяд;12.50\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Такси;8.20\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Книга;18.90\n") with open("expenses.txt", encoding="utf-8") as file: lines = file.read().splitlines() total = 0 for line in lines: parts = line.split(";") total += float(parts[1]) print(f"{parts[0]}: {parts[1]} €") print(f"{len(lines)} разхода, общо {total:.2f} €")except OSError as e: print(f"Проблем с файла: {e}")Обяд: 12.50 €Такси: 8.20 €Книга: 18.90 €3 разхода, общо 39.60 €lines е списък от текстове, по един за всеки ред, без знака за нов ред. Обхождаш го с for, а len(lines) е броят на редовете. Повече за списъците има в следващия модул, „Колекции“.
По-точно, за любопитните
"a"създава файла, ако го няма, затова първото добавяне не дава грешка.read()чете целия файл в паметта. За файл от няколко гигабайта е по-добре да четеш ред по ред. Виж картата заfor line in file.- В чужд код ще видиш и
file.readlines(). Той също дава списък с редовете, но всеки е със своя"\n"в края.
Пробвай
Две смени
Какво ще отпечата програмата?
try: with open("shift.txt", "w", encoding="utf-8") as file: file.write("Първа смяна: Иво\n") with open("shift.txt", "w", encoding="utf-8") as file: file.write("Втора смяна: Мила\n") with open("shift.txt", encoding="utf-8") as file: print(file.read(), end="")except OSError as e: print(e)"w" изпразва файла още при отварянето, а после записва новото. Първият ред е изчезнал още преди четенето. Това е най-честата причина „файлът ми се изтри“.
Идеята
Когато файла го няма
Настройките на програмата са във config.txt, а някой го е изтрил. Ако никой не хване изключението:
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
print(read_config())Traceback (most recent call last): File "/app/app.py", line 6, in <module> print(read_config()) ^^^^^^^^^^^^^ File "/app/app.py", line 2, in read_config with open("config.txt", encoding="utf-8") as file: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'config.txt'Python подрежда извикванията обратно на Java: най-горе е най-външното, ред 6, където е извикана read_config(). Под него е ред 2 в read_config, където open не намира файла. Най-отдолу е самата грешка: FileNotFoundError и съобщението. Затова в началото пише „most recent call last“: последното извикване е най-отдолу.
FileNotFoundError е наследник на OSError. Хвани го отделно, преди общия случай:
import os
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
try: print(read_config())except FileNotFoundError as e: print(f"Няма файл {e.filename}. Ползвам настройките по подразбиране.")except OSError as e: print(f"Не мога да прочета настройките: {e}")print(os.path.exists("config.txt"))Няма файл config.txt. Ползвам настройките по подразбиране.Falsee.filename е името на файла, който липсва. os.path.exists(path) проверява дали файлът съществува. Проверката не замества except: файлът може да изчезне между проверката и четенето, а и има други причини четенето да не стане.
Идеята
Ред по ред и with
Файлът от open може да се пише на части и да се чете ред по ред, без целият да е в паметта. Той държи файла отворен, докато работиш с него, и затова трябва да се затвори накрая:
try: with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Ден {day}: {day * 120} €\n")except OSError as e: print(f"Не мога да запиша: {e}")
try: with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))except OSError as e: print(f"Не мога да прочета: {e}")Ден 1: 120 €Ден 2: 240 €Ден 3: 360 €writeзаписва текст. Нов ред сам не идва:\nго пишеш ти.for line in fileдава редовете един по един, всеки със своя\nв края.rstrip("\n")го маха, иначеprintби сложил празен ред между тях.withзатваря файла, когато отместените редове свършат, по какъвто и да е начин: и при изключение.
Затварянето не е формалност: докато файлът е отворен, текстът може още да е в буфера му в паметта, а не на диска. За да е кодът кратък, тук няма try:
import os
with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Ден {day}: {day * 120} €\n") print(f"В блока: {os.path.getsize('report.txt')} байта")print(f"След блока: {os.path.getsize('report.txt')} байта")with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))Стъпка по стъпка, с думи
- os има функцията за размера на файла. Отляво е паметта на програмата, отдясно е дискът.
- open с "w" създава празния файл report.txt и файлов обект с буфер в паметта.
- write пише в буфера. До диска още не стига нищо.
- Файлът на диска все още е 0 байта.
- Блокът with свършва и затваря файла. Буферът отива на диска: файлът става 54 байта.
- 54 байта за 39 знака с новите редове: в UTF-8 всяка буква на кирилица е 2 байта, а € е 3.
- for line in file чете трите реда обратно от диска.
Обещахме го в урока за finally. Ето същото записване без with, с finally:
out = open("report.txt", "w", encoding="utf-8")try: out.write("Ден 1: 120 €\n")finally: out.close()with прави точно това вместо теб: затваря файла, каквото и да стане в блока.
По-точно, за любопитните
- В едно
withможе да има няколко файла, разделени със запетая:with open("app.log", encoding="utf-8") as src, open("errors.log", "w", encoding="utf-8") as out:. - Буфер. Файлът събира текста в паметта и го праща към диска на големи парчета. Всяко отделно писане на диска е бавно, а така са малко.
file.flush()праща буфера веднага, без да затваря файла. - Байтове и знаци. Отдолу файлът е поредица от байтове. Режимите
"rb"и"wb"четат и пишат байтове: за снимки, архиви, всичко, което не е текст. Безbфайлът е текстов и превръща байтовете в знаци поencoding. file.readline()чете един ред. Когато файлът свърши, връща празен текст"". Празен ред във файла е"\n", затова двете не се бъркат.
Пробвай
Кога се затваря?
Всеки клас с методите __enter__ и __exit__ може да стои във with. __enter__ се вика в началото на блока, а __exit__ в края му, и при изключение. Ето принтерът на касата от урока за finally, като ресурс:
Какво ще отпечата програмата?
class Printer: def __enter__(self): print("Принтерът е зает.") return self
def __exit__(self, exc_type, exc, tb): print("Принтерът е свободен.")
try: with Printer(): print("Печатам бележката...") qty = int("три") print(f"Количество: {qty}")except ValueError: print("Грешно количество.")int("три") хвърля ValueError и редът „Количество“ се прескача. На излизане от with Python вика __exit__ веднага, още преди except. Затова „свободен“ е преди „Грешно количество.“ Когато except се изпълнява, принтерът вече е освободен.Пробвай
Намери грешката
0. На кой ред е грешката?Избери реда с грешката.
with и не се затваря. Текстът е още в буфера в паметта и не е стигнал до диска, затова при четенето файлът е празен. Програмата не дава грешка, а тихо брои грешно. Отвори го с with open(...) as out: и прочети файла след блока: тогава той е затворен и редът е там.Идеята
Папки
Резервните копия на касата отиват в папка, по едно на ден:
import os
backup = "backup"try: os.makedirs(os.path.join(backup, "old"), exist_ok=True) with open(os.path.join(backup, "2026-10-01.csv"), "w", encoding="utf-8") as file: file.write("Кафе,2.40\n") with open(os.path.join(backup, "2026-10-02.csv"), "w", encoding="utf-8") as file: file.write("Кафе,2.40\nЧай,1.80\n") for name in sorted(os.listdir(backup)): p = os.path.join(backup, name) if os.path.isdir(p): print(f"{name}/") else: print(f"{name} ({os.path.getsize(p)} байта)") os.remove(os.path.join(backup, "2026-10-01.csv")) print(os.path.exists(os.path.join(backup, "2026-10-01.csv")))except OSError as e: print(e)2026-10-01.csv (14 байта)2026-10-02.csv (26 байта)old/False| Функция | Какво прави |
|---|---|
os.makedirs(p, exist_ok=True) |
създава папката и всички папки над нея, ако ги няма |
os.path.join(dir, "име") |
пътят до име вътре в dir |
os.path.isdir(p), os.path.exists(p) |
папка ли е, има ли го |
os.path.getsize(p) |
размерът в байтове |
os.remove(p) |
изтрива файл (os.rmdir(p) изтрива празна папка) |
os.listdir(dir) |
списък с имената на всичко в папката |
Размерите са 14 и 26 байта, макар че символите са по-малко: всяка буква на кирилица е 2 байта в UTF-8.
os.listdir дава имената в произволен ред, затова ги подреждаме със sorted.По-точно, за любопитните
os.rmdirизтрива само празна папка. Иначе даваOSError: [Errno 39] Directory not empty.shutil.rmtree(p)(модулътshutil) изтрива папката с всичко в нея, затова внимавай с него.shutil.copy(от, до)копира файл, аos.rename(от, до)го мести или преименува.- В много код ще видиш модула
pathlib:Path("backup") / "old"е пътят до папкатаoldвbackup, аp.exists()иp.read_text(encoding="utf-8")са методи на пътя. Това е същото, с обекти вместо функции. - Пътят
"notes.txt"е относителен: спрямо папката, от която е пусната програмата."/home/ana/notes.txt"е абсолютен.os.path.abspath("notes.txt")показва къде точно е файлът.
Упражнение
Програми, които помнят
Реши задачите в редактора си. Във всяка задача програмата първо сама създава входния файл с open(..., "w"), за да можеш да я пробваш веднага.
1. Конфигурационен файл. Създай server.conf с това съдържание, после го прочети и отпечатай всяка настройка като ключ: стойност. Празните редове и редовете, които започват с #, се пропускат. Подсказка: split("=", 1) разделя само на първото = (числото е колко пъти да среже).
# настройки на сървъраmax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. Филтър на лог файл. Създай app.log с четири реда (два INFO и два ERROR). Прочети от клавиатурата ниво, например ERROR, и запиши редовете с това ниво в errors.log ред по ред, с двата файла в едно with. Отпечатай errors.log и колко реда си записал.
ERRORERROR: Failed to load configurationERROR: Null pointer exceptionЗаписах 2 реда в errors.log3. Съдържание на папка. Създай папка project с файловете document.txt, image.jpg, script.js и папката backup. Запиши имената им, подредени по азбучен ред, в directory_contents.txt, като след имената на папките сложиш /. Отпечатай файла.
backup/document.txtimage.jpgscript.js4. Индексен файл. Създай text.txt с трите реда по-долу. Прочети дума от клавиатурата и запиши в index.txt всяка нейна поява във вида ред:позиция: номерът на реда (от 1) и индексът в реда (от 0). Ако думата е два пъти на един ред, това са два записа.
hello worldworld hellohello again, hellohello1:02:63:03:13Още задачи
- Брояч на пусканията. Поправи програмата от първата карта: брой пусканията във
visits.txt. Ако файлът го няма, започни от 0. - Намери и замени във файл. Задачата от урока „Търсене и замяна“: прочети файл, замени „старо“ с „ново“ и запиши резултата в нов файл.
- Дати от файл. Задачата за дати от урока „Регулярни изрази“: чети датите от файл, по една на ред.
- Копие с номерата на редовете. Прочети файл и запиши копие, в което всеки ред започва с номера си:
1: ...,2: ....
Тест