Twice recently I've found myself wanting to write the following code: def fn(a_file=None): responsible_for_closing = False if a_file is None: a_file = open(a_default_location) responsible_for_closing = True do_stuff(a_file) if responsible_for_closing: a_file.close() which can be written slightly shorter I know, but it's still a tiny bit messy and repetitive. What I'd prefer to write is something more like: def fn(a_file=None): with contextlib.maybe(a_file, open, default) as a_file: do_stuff(a_file) where `maybe` takes an object and conditionally runs a context manager if a check fails. Implementation would be: @contextlib.contextmanager def maybe(got, contextfactory, *args, checkif=bool, **kwargs): if checkif(got): yield got else: with contextfactory(*args, **kwargs) as got: yield got It's hard to gauge utility for such simple functions (though contextlib already has closing(), so I figured it'd be worth asking at least). Would this be useful to others? Or perhaps I'm completely missing something and you've got suggestions on how to better have an API where an argument can be fetched if not provided but a context manager would preferably need to be run to do so.
On 12/11/2011 12:07 AM, Julian Berman wrote:
Twice recently I've found myself wanting to write the following code:
def fn(a_file=None): responsible_for_closing = False
if a_file is None: a_file = open(a_default_location) responsible_for_closing = True
do_stuff(a_file)
if responsible_for_closing: a_file.close()
which can be written slightly shorter I know, but it's still a tiny bit messy and repetitive. What I'd prefer to write is something more like:
def fn(a_file=None): with contextlib.maybe(a_file, open, default) as a_file: do_stuff(a_file)
Expecting contextlib to have such a specialized context manager that does exactly what you want is perhaps too much. However, you should be able to write a class yourself that keeps the flag and does the conditional opening and closing in the __enter__ and __exit__ methods. -- Terry Jan Reedy
Terry Reedy schrieb am So, 11. Dez 2011, um 01:02:30 -0500:
Expecting contextlib to have such a specialized context manager that does exactly what you want is perhaps too much.
I don't think this is a very specialised need. It would overcome a fundamental limitation of context managers compared to some clean-up constructs in other languages. An example is Go's 'defer', which is Go's replacement for try/finally blocks and context managers. It defers a function call to the time the current function returns, and guarantees the deferred call will be executed no matter how control leaves the current function -- just like a 'finally' clause. The advantage of 'defer' over 'with' and try/finally blocks is that it is not a compound statement, so you can "conditionally add a 'finally' clause". Returning to the example from the original post, let's see how it would look in Go: func Fn(a_file *os.File) (err os.Error) { if a_file == nil { a_file, err = os.Open(a_default_location, os.O_RDONLY, 0) if err != nil { return } defer a_file.Close() } // do stuff return } to achieve exactly what is desired. The line 'defer a_file.Close()' is only executed when we actually need to close the file. Note that the disadvantage of 'defer' is also that it is not a compound statement -- it is bound to the block defined by the current function and in this regard less flexible than 'with' blocks. We could add our own version of 'defer' to Python by offering a context manager 'Deferrer' with a 'push()' method to push a clean-up call-back on the Deferrer's stack. This design would offer the combined advantages of both approaches described above. I, for one, do think that this would make a very worthwhile addition to the 'contextlib' module. We discussed such a context manager less than two weeks ago on this list http://mail.python.org/pipermail/python-ideas/2011-October/012418.html and Jan Kaliszewski even provided an implementation: http://mail.python.org/pipermail/python-ideas/2011-October/012463.html What do you think? Cheers, Sven
On 12/11/2011 06:07 AM, Julian Berman wrote:
Twice recently I've found myself wanting to write the following code:
def fn(a_file=None): responsible_for_closing = False
if a_file is None: a_file = open(a_default_location) responsible_for_closing = True
do_stuff(a_file)
if responsible_for_closing: a_file.close()
What about this? def fn(a_file=None): if a_file is None: with open(a_default_location) as a_file: do_stuff(a_file) else: do_stuff(a_file) That's how I do it when this comes up.
which can be written slightly shorter I know, but it's still a tiny bit messy and repetitive. What I'd prefer to write is something more like:
def fn(a_file=None): with contextlib.maybe(a_file, open, default) as a_file: do_stuff(a_file)
where `maybe` takes an object and conditionally runs a context manager if a check fails. Implementation would be:
@contextlib.contextmanager def maybe(got, contextfactory, *args, checkif=bool, **kwargs): if checkif(got): yield got else: with contextfactory(*args, **kwargs) as got: yield got
It's hard to gauge utility for such simple functions (though contextlib already has closing(), so I figured it'd be worth asking at least). Would this be useful to others? Or perhaps I'm completely missing something and you've got suggestions on how to better have an API where an argument can be fetched if not provided but a context manager would preferably need to be run to do so.
On Fri, Dec 16, 2011 at 2:21 PM, Mathias Panzenböck <grosser.meister.morti@gmx.net> wrote:
On 12/11/2011 06:07 AM, Julian Berman wrote:
Twice recently I've found myself wanting to write the following code:
def fn(a_file=None): responsible_for_closing = False
if a_file is None: a_file = open(a_default_location) responsible_for_closing = True
do_stuff(a_file)
if responsible_for_closing: a_file.close()
What about this?
def fn(a_file=None): if a_file is None: with open(a_default_location) as a_file: do_stuff(a_file) else: do_stuff(a_file)
That's how I do it when this comes up.
With contextlib2.ContextStack [1] the problem of conditional release of resources can be handled as follows: def fn(a_file=None): with ContextStack() as stack: if a_file is None: # The stack will release the file when we're done a_file = stack.enter_context(open(a_default_location)) do_stuff(a_file) If you're interested in seeing this (or something like it) in the standard library for 3.3, grab the module of PyPI, play around with it and send me feedback on the BitBucket issue tracker [2]. [1] http://contextlib2.readthedocs.org/en/latest/index.html#contextlib2.ContextS... [2] https://bitbucket.org/ncoghlan/contextlib2/issues?status=new&status=open Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (5)
-
Julian Berman
-
Mathias Panzenböck
-
Nick Coghlan
-
Sven Marnach
-
Terry Reedy