Рубрики
Без рубрики

Управление чистыми ресурсами в Python с использованием контекстных менеджеров

Управление ресурсами является жизненно важным аспектом программного обеспечения. Закрытие открытых файлов, освобождение динамической памяти, Termina … Tagged с Python.

Управление ресурсами является жизненно важным аспектом программного обеспечения. Закрытие открытых файлов, освобождение динамической памяти, завершение сеансов базы данных. Хотя это утомительно, мы знаем, что это необходимо.

Но разве не было бы неплохо, если бы мы могли делегировать распределение и освобождение ресурсов, избегая этих громоздких Попробуйте ... наконец блоки?

Что ж, у Python наши спины благодаря с Заявления и контекстных менеджеров!

А с заявлением

Многие из вас будут знакомы с «Pythonic» способом открытия файла, работы с ним, а затем следить за тем, чтобы он был закрыт; что было бы чем -то вроде:

with open('my-file.txt') as my_file:
    # Do your thing

Таким образом, вы открываете файл, работаете с ним и знаете, что он будет автоматически закрыт, как только вы закончите; Даже если ваш код повышает ошибку. Этот код эквивалентен:

my_file = open('my-file.txt')
try:
    # Do your thing
finally:
    my_file.close()

Однако, как вы можете видеть; Первое решение намного чище и легче понять. Эта версия является чистой бизнес -логикой, вы открываете файл и делаете что -то с ним. Он не включает в себя тот факт, что файл должен быть закрыт или что ошибка может возникнуть при работе с файлом, как вторая версия, все это сделано за кулисами.

Другой экземпляр, когда вы можете использовать с оператор используется при использовании объекта блокировки из потокового модуля.

lock = Lock()

# Why write this?
lock.acquire()
try:
    ...
finally:
    lock.release()

# When you can write this
with lock:
    ...

Теперь вопрос в том, можете ли вы управлять своими пользовательскими ресурсами, используя с утверждение?

Да! Ты сможешь!

Контекстные менеджеры

Контекст -диспетчер – это любой объект, который указывает две операции, которые следует выполнять одну за другой, с некоторой произвольной логикой между ними. Это происходит через методы __enter __ () и __exit __ () Анкет Обратите внимание, что мы говорим о любых двух операциях, не обязательно приобретении и выпуске ресурса.

Эти контекстные менеджеры могут быть использованы внутри с утверждение следующим образом:

with some_context_manager [as foo]:
    # Your code

Когда Python выполняет этот кусок кода:

  1. some_context_manager .__ Enter __ () называется. Метод передается без аргументов и, если как Предложение использовалось, его возвращаемое значение связано с именем Фу Внутри внутреннего блока.

  2. Ваш код выполняется, имея доступ к foo Если указано.

  3. some_context_manager .__ Выход __ () называется. Это передается 3 аргумента для обработки любого исключения, поднятого вашим кодом. Если не было никаких исключений, все три Нет Анкет Эти:

    • exc_type : Тип поднятого исключения.
    • exc : само исключение.
    • Exc_traceback : Traceback исключения.

    Если __exit __ () Желания подавить исключение, поднятое внутренним кодовым блоком, предотвращая его размножение, оно должно вернуть Верно Анкет

Написание собственного контекстного менеджера

Как мы описали ранее, менеджер контекста справедливо и объект, который отвечает на __enter __ () и __exit __ () методы Мы можем написать простого менеджера, как это:

class MyContextManager:

    def __enter__(self):
        print('Acquiring a resource')
        self.resource = acquire()
        return self.resource

    def __exit__(self, exc_type, exc, exc_traceback):
        if exc_type is not None:
            print('Oops, this error was raised:', exc)
        print('Releasing the resource')
        self.resource.release()

Попробуем это.

>>> with MyContextManager():
...   print('Using the resource')

# Output
Acquiring a resource
Using the resource
Releasing the resource

Теперь давайте посмотрим, что произойдет, если мы подведем исключение внутри с утверждение:

>>> with MyContextManager():
...   raise ValueError('Something went wrong')

# Output
Acquiring a resource
Oops, this error was raised: Something went wrong
Releasing the resource

Traceback (most recent call last):
  File "", line 2, in 
ValueError: Something went wrong

Вы можете видеть это, хотя мы выразили ошибку внутри кодового блока, Выход () Метод все еще был выполнен, и ресурс был выпущен до пропаганды исключения за пределами с утверждение.

Что если мы хотим подавить ошибку? В этом случае мы могли бы изменить __exit __ () и вернуть Верно Из внедрения:

def __exit__(self, exc_type, exc, exc_traceback):
    if exc_type is not None:
        print('Oops, this error was raised:', exc)
    print('Releasing the resource')
    self.resource.release()
    return True

И сейчас:

>>> with MyContextManager():
...   raise ValueError('Something went wrong')

# Output
Acquiring a resource
Oops, this error was raised: Something went wrong
Releasing the resource

Исключение было поднято и __exit __ () Может сделать что -то, чтобы справиться с этим, но это не было распространено.

Написание контекстного менеджера в качестве функции

Встроенный модуль Python контекст Предоставляет утилиты для работы с контекстными менеджерами. Он включает в себя много полезных вещей, таких как общие контекстные менеджеры, уже реализованные, но в этом разделе мы сосредоточимся на ContextManager декоратор.

Этот декоратор позволяет писать диспетчер контекста в качестве функции, которая возвращает генератор, который дает одно значение. Например, мы могли бы написать MyContextManager как это:

from contextlib import contextmanager

@contextmanager
def some_resource():
    print('Acquiring a resource')
    resource = acquire()
    try:
        yield resource
    except Exception as exc:
        print('Oops, this error was raised:', exc)
        raise
    finally:
        print('Releasing the resource')
        resource.release()

В этом случае, когда вы бежите:

with some_resource() [as foo]:
    # Your code
  1. some_resource () выполняется до доход утверждение

  2. Ваш код выполняется, имея ресурс, связанный с именем foo Если указано.

  3. Остальная часть some_resource () выполнено.

Я лично предпочитаю этот метод для написания простых менеджеров. Для более сложных, как правило, лучше использовать класс.

Пример реального мира

Недавно я был в сценарии автоматизации Python, который работал на экземпляре Amazon EC2, и в какой -то момент он должен выполнить awscli Команда с использованием другой учетной записи AWS, чем у виртуальной машины. Одним из способов временного использования другой учетной записи является получение учетных данных этой учетной записи и хранение их в двух переменных среды, AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY .

Так; Мне нужно было установить эти переменные среды, запустить команду CLI и отложить переменные, чтобы я мог продолжать использовать свою исходную учетную запись. Звучит идеально для менеджера контекста!

from os import environ, unsetenv
from contextlib import contextmanager

@contextmanager
def aws_credentials(access_key: str, secret_key: str):
    environ['AWS_ACCESS_KEY_ID'] = access_key
    environ['AWS_SECRET_ACCESS_KEY'] = secret_key
    try:
        yield
    finally:
        unsetenv('AWS_ACCESS_KEY_ID')
        unsetenv('AWS_SECRET_ACCESS_KEY')

Обратите внимание, что использование урожай само по себе эквивалентно написанию не допустить .

Как вы видете; Этот менеджер контекста устанавливает учетные данные среды, дает так, чтобы код внутри с Заявление выполняется, а затем выявляет учетные данные. Его можно использовать следующим образом:

access_key, secret_key = securely_get_credentials()
with aws_credentials(access_key, secret_key):
    # Run cli command

Вывод

Контекстные менеджеры – это очень простые инструменты, которые позволяют вам писать код, который намного более чистый и более краткий. И теперь, когда вы их знаете, вы будете удивлены тем, как часто они появляются!

Это был мой первый пост на Dev.to, надеюсь, вам понравилось. Если у вас есть какие -либо отзывы, не стесняйтесь сообщить мне 😄.

Ваше здоровье!

Оригинал: “https://dev.to/pablopaglilla/clean-resource-management-in-python-using-context-managers-36a2”