4.1 Закриття файла і обробка помилок

Іноді при читанні файла виникають помилки або винятки, і файл залишається незакритим. Це потенційно може призвести до витоку пам'яті або витоку file handler'ів. Тому роботу з файлом потрібно обертати в блок try-except.

Допустимо, у вас був код:


file = open('example.txt', 'a') # Відкриваємо файл
file.write("This is a new line added to the file.")
file.close()  # Закриваємо файл

Його потрібно загорнути в блок try-except:


try:
    file = open('example.txt', 'a')  # Відкриваємо файл
    file.write("This is a new line added to the file.")
    file.close()  # Закриваємо файл
except FileNotFoundError:
    print("File not found")
    file.close()  # Закриваємо файл

Щоб двічі не писати метод close(), можна винести його в блок finally:


try:
    file = open('example.txt', 'a')  # Відкриваємо файл
    file.write("This is a new line added to the file.")
except FileNotFoundError:
    print("File not found")
finally:
    file.close()  # Закриваємо файл

Цей код виглядає красиво, але… не працює, оскільки змінна file визначена тільки в блоці try і недоступна з блоків except і finally.

Тому нам потрібно визначити змінну на рівень вище:


file = None
try:
    file = open('example.txt', 'a')  # Відкриваємо файл
    file.write("This is a new line added to the file.")
except FileNotFoundError:
    print("File not found")
finally:
    file.close()  # Закриваємо файл

Це рішення краще, але у нього теж є недоліки. Наприклад, якщо файл так і не буде відкритий, то в змінній file залишиться None. І тоді спроба закрити файл викличе помилку — буде спроба звернутися до методу close() у неіснуючого об'єкта.

Тому нам потрібно додати перевірку перед викликом методу close():


file = None
try:
    file = open('example.txt', 'a')  # Відкриваємо файл
    file.write("This is a new line added to the file.")
except FileNotFoundError:
    print("File not found")
finally:
    if file:
        file.close()  # Закриваємо файл

Якщо ви здивовані тим, що 3 рядки коду перетворилися в 9, то ви не самі. На щастя, вже є готове рішення для цієї проблеми, про яке ми поговоримо далі.

4.2 Оператор with

Оператор with в Python надає зручний спосіб управління ресурсами, такими як файли, забезпечуючи їх автоматичне закриття після завершення блоку with. Це спрощує код і запобігає витокам ресурсів, таких як незакриті файли.

Загальний синтаксис оператора with:


with вираз as змінна:
    робота з змінною

Оператор with використовується для обгортання виконання блоку коду менеджером контексту. При використанні оператора with Python автоматично викликає методи __enter__() і __exit__() об'єкта менеджера контексту, що спрощує управління ресурсами.

Приклад використання with для роботи з файлами:


with open('example.txt', 'w') as file:
    file.write("Hello, World!\n")
    file.write("This is a test file.\n")

У цьому прикладі файл example.txt відкривається в режимі запису, а ім'я файла зв'язується з змінною file. Блок коду всередині with автоматично закриває файл після виконання всіх операцій запису.

4.3 Автоматичне закриття файла

Однією з головних переваг використання оператора with є автоматичне закриття файла після завершення блоку коду. Це відбувається навіть у випадку виникнення винятку, що робить код більш безпечним і надійним.

Приклад автоматичного закриття файла:


try:
    with open('example.txt', 'w') as file:
        file.write("Hello, World!\n")
        file.write("This is a test file.\n")
        # Виняток, щоб перевірити, що файл все одно закриється
        raise Exception("Something went wrong")
except Exception as e:
    print(f"Caught an exception: {e}")
# У цьому місці файл вже закритий

Завжди використовуйте оператор with при роботі з файлами. Це просто і дозволяє уникнути багатьох помилок.

4.4 Під капотом оператора with

В основі роботи оператора with лежать методи __enter__() і __exit__(), які повинні бути реалізовані в класі, що використовується як менеджер контексту.

Для того, щоб об'єкт міг використовуватись з оператором with, він повинен реалізовувати методи __enter__() і __exit__(). Ці методи визначають поведінку при вході і виході з контексту.

Метод __enter__()

Метод __enter__() викликається при вході в блок with. Він виконує ініціалізацію ресурсу і повертає об'єкт, який буде прив'язаний до змінної, вказаної після as.

Метод __exit__()

Метод __exit__() викликається при виході з блоку with. Він виконує завершальні дії, такі як звільнення ресурсів або закриття файлів. Метод приймає три аргумента: exc_type, exc_val і exc_tb, які містять інформацію про виняток, якщо він стався.

  • exc_type: тип винятку (наприклад, ZeroDivisionError).
  • exc_val: значення винятку (наприклад, повідомлення про помилку).
  • exc_tb: трасування стека винятку.

Якщо метод __exit__() повертає True, то виняток буде придушено. Якщо повертає False, виняток буде повторно викликано.

Приклад:


class MyContextManager:
    def __enter__(self):
        print("Entering the context")
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            print(f"An exception occurred: {exc_type}, {exc_val}")
        return False  # Виняток не придушується
        
with MyContextManager() as manager:
    print("Inside the with block")