Python enter / exit vs init (или новый) / del

я искал, и я не могу придумать какой-либо веской причины использовать python __enter__ /__exit__, а не __init__ (или __new__ ?) / __del__ .

я понимаю, что __enter__ / __exit__ предназначены для использования с with заявление в качестве контекстных менеджеров и with утверждение великого. Но аналогом этого является то, что любой код в этих блоках является только выполнены в этом контексте. Используя их вместо __init__ / __del__ кажется, я создание неявного контракта с вызывающими абонентами, которые они должны использовать with, но нет никакого способа обеспечить соблюдение такого контракта, и контракт передается только через документацию (или чтение кода). Это кажется плохой идеей.

кажется, я получаю тот же эффект, используя __init__ / __del__ внутри with блок. Но, используя их, а не методы управления контекстом, мой объект также полезен в других сценариях.

так может кто-нибудь придумать убедительный причина, почему я бы когда-нибудь хотите использовать методы управления контекстом, а не методы конструктора/деструктора?

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

Дальнейшие Действия:

этот вопрос был основан на плохом (но, вероятно, распространенном) предположении, потому что я всегда использовал with создать новый объект, в таком случае __init__/__del__ очень близко к тому же поведению, что и __enter__/__exit__ (кроме того, что вы не можете контролировать, когда или если __del__ будет выполнен, это зависит от сборки мусора, и если процесс будет завершен первым, он никогда не будет вызван). Но если вы используете существующие объекты в with заявления они, конечно, совсем другой.

3 ответов


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

  • Context manager получает возможность предоставить новый объект только для блока, который вы выполняете. Некоторые менеджеры контекста просто возвращают self там (как и файловые объекты), но, например, объекты подключения к базе данных могут возвращать объект курсора, привязанный к текущей транзакции.

  • менеджеры контекста не только уведомляются о завершении контекста, но и если выход был вызвано исключение. Затем он может принять решение об обработке этого события или иначе реагировать по-разному во время выхода. Опять же, используя подключение к базе данных в качестве примера, на основе исключения можно зафиксировать или прервать транзакцию.

  • __del__ вызывается только, когда все ссылки на объект будут удалены. Это означает, что вы не можете полагаться на его вызов, если вам нужно иметь несколько ссылок на него, которые вы можете или не можете контролировать жизни. Однако выход context manager точно определен.

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

    это важно для блокировки потоков, например; вы есть сохранить состояние так, чтобы только один резьба может держать замок одновременно. Вы делаете это, создавая один заблокировать объект, затем использовать with lock: таким образом, разные потоки, выполняющие этот раздел, могут быть сделаны, чтобы ждать перед входом в этот контекст.

на __enter__ и __exit__ методы формирования контекст менеджер протоколом, и вы должны использовать их, только если вы действительно хотите управлять контекстом. Цель контекстных менеджеров-упростить common try...finally и try...except шаблоны, не управление временем жизни одного экземпляра. См.PEP 343 -"С" заявление:

этот PEP добавляет новый оператор " с " на язык Python, чтобы можно было исключить стандартное использование операторов try/finally.


del x не вызывает напрямую x.__del__()

вы не можете контролировать, когда .__del__ называется, или на самом деле будет ли он вызван вообще.

, используя __init__/__del__ для управления контекстом не является надежным.

используя их вместо __init__ / __del__ Кажется, я создаю неявный контракт с абонентами, которые они должны использовать with, но нет никакого способа исполнения такого договора

у вас есть договор в любом случае. Если пользователи используют ваш объект, не понимая, что он требует очистки после использования, они испортят все, независимо от того, как вы реализуете очистку. Они могут навсегда сохранить ссылку на ваш объект, например, предотвращая __del__ от бегущий.

если у вас есть объект, требующий специальной очистки, вы должны сделать это требование явным. Вам нужно дать пользователям with функциональность и явную close или аналогичный метод, чтобы позволить пользователям контролировать, когда происходит очистка. Вы не можете скрыть требование очистки внутри __del__ метод. Возможно, вы захотите реализовать __del__ тоже, как мера безопасности, но вы не можете использовать __del__ на месте with или явный close.


С учетом сказанного, Python не обещает, что __del__ будет работать, никогда. Стандартная реализация будет работать __del__ когда refcount объекта падает до 0, но это может не произойти, если ссылка сохраняется до конца сценария или если объект находится в цикле ссылок. Другие реализации не используют refcounting, making __del__ еще менее предсказуемой.