7

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

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

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

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

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

Follow Up:

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

+2

Когда (и даже если) '__del__' получает вызов, он недетерминирован. Вы можете потерять данные в зависимости от '__del__' для очистки. – user2357112

+0

Возможный обман: http://stackoverflow.com/a/6772907/674039 – wim

ответ

7

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

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

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

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

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

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

В __enter__ и __exit__ методы формирования протокола менеджера контекста, и вы должны использовать только их, если вы на самом деле хотите, чтобы управлять контекстом. Целью менеджеров контекста является упрощение общих шаблонов try...finally и try...except, а не для управления временем жизни одного экземпляра. См PEP 343 – The "with" Statement:

Этот PEP добавляет новое заявление «с» на языке Python, чтобы сделать возможным вынесем стандартные способы использования Try /, наконец, заявления.

+0

Ваши первые два момента касаются инструкции 'with', а не входа и выхода. Я согласен, что «с» потрясающе, но в этом вопросе меня больше беспокоит лучший способ написать гибкий объект, который можно использовать внутри 'with' или нет. Я получаю точку '__del__'. – BobDoolittle

+2

@BobDoolittle: нет смысла в реализации '__enter__' и' __exit__' ** без ** оператора 'with'. Утверждение 'with' является причиной того, что мы имеем эти методы в первую очередь. –

+0

Конечно, есть. Это разница между сервером и клиентом. Между абонентом и службой. Я пишу объект. Кто-то другой может его использовать. Они могут использовать его в блоке 'with' или нет, и у меня нет способа принудительного использования. Я должен написать свой объект таким образом, чтобы он выполнялся правильно в любом случае. Подобно объектам 'File', я могу использовать их в инструкции' with' или нет, и они работают в любом случае. За пределами блока 'with' я должен вызвать close(), однако (но я подозреваю, что' __del__' тоже делает это). – BobDoolittle

2

del x не непосредственно вызывать x.__del__()

Вы не имеете никакого контроля над тем, когда .__del__ называется, или на самом деле whether it gets called at all.

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

+0

Я понимаю вашу мысль о '__del__'. Но не wrt '__init__'. '__init__' должен быть надежным внутри или вне контекстного управления. На данный момент я думаю, что лучший способ написать гибкий объект - поставить ваш код инициализации в '__init__', а не' __enter__', и сделать '__exit__' делать то же самое, что и' __del__' (при защите против дублирования исполнения). На самом деле, я подозреваю, что это именно то, что делают объекты File. – BobDoolittle

+1

@BobDoolittle: '__init__' для инициализации, поэтому, если вы хотите сделать инициализацию, сделайте это там. '__enter__' предназначен для работы, которая должна произойти специально при вводе оператора' with'; например, '__enter__' может заблокировать блокировку и' __exit__' разблокировать ее. – user2357112

0

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

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

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


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

Смежные вопросы