On Nov 19, 2019, at 17:57, Oscar Benjamin email@example.com wrote:
Perhaps a less emotive way of distinguishing these classes of context managers would be as "eager" vs "lazy". An eager context manager jumps the gun and does whatever needs undoing or following up before its __enter__ method is called. A lazy context manager waits until __enter__ is called before committing itself.
I don't really want to give a sense of equality between eager and lazy though. To me it is clear that lazy context managers are preferable.
What you’re calling “lazy” context managers are better in that they work with tools like nested.
But “eager” context managers are better in that they can be used as resources when the context manager protocol doesn’t help, as when the releasing doesn’t happen in the same lexical scope as the acquiring. (Go back to my example with someone who opens a file, and then opens another file if that didn’t raise, to see why you can’t just use a lazy context manager anyway.)
You can go back and forth arguing that the first one is more important or that the second one is more important, but neither argument rebuts the fact that the other kind actually does have benefits.
So, where do you go from there?
Option 1 is to assume that context managers are, or can be, eager, and therefore tools like nested are bug magnets and can’t be put somewhere as prominent as contextlib. That’s where we are today, and you don’t like it.
Option 2 is to assume that context managers are lazy (maybe with some rare exceptions but those should be clearly called out), so file object and SQLite connections and dozens of other context managers, stdlib and third party, all need to be fixed. That’s what you’re suggesting, but I doubt it’s ever going to happen.
Is there a third option? Maybe. If we accept that both exist, we just need some way to distinguish the two, and to make tools like nested raise if fed an eager one, right? That sounds like a job for an ABC—one that classes have to manually opt in to via inheritance or registration, but a bunch of the stuff in contextlib (including the wrapper around generator functions) that people use directly or use to create their own context managers is definitely lazy and would opt in.
You can also create either a generic wrapper that wraps any eager context manager factory into a lazy one, or create specific lazy context managers or cm factories like opening, or both.
You can also do the reverse wrapper, and use that to try to convince people to always write their context managers lazy and wrap if they need an eager one.
Submit all of this to contextlib2 (or, if Nick rejects it, create your own PyPI project instead) and see if people use it. If so, you can propose merging it all into the stdlib, and maybe even shortcuts like making tuples act like nested.
In the long run, you can start pressuring people to write their tutorials and blog posts and python-list and StackOverflow answers to favor creating and using lazy context managers whenever possible (“because they work with the tuple syntax” seems like a good argument…).
And you can do all of this without needing to change the protocol.