Make conditional "with" preciner
Any advice on what is the correct "pythonic" way for the next function. Should I split it into two functions?
def readSomething(fp=None):
if fp:
return fp.read(100)
else:
with open('default.txt', 'r') as fp:
return fp.read(100)
I need something like this because a function readSomething
can be called from another function that may or may not open the same file.
For example, it might be named like this in some places:
def doSomethingWithSameFile():
with open('default.txt') as fp:
preample = fp.read(10)
more_data = readSomething(fb)
...
or as elsewhere:
def init():
data = readSomething()
...
source to share
I don't think this is the right solution, but I think this is what you want.
import contextlib
def readSomething(fp=None):
with contextlib.ExitStack() as stack:
if not fp:
fp = stack.enter_context(open('default.txt'))
return fp.read(100)
I get the impression that you are going to duplicate this logic across many functions such as readSomething()
, so I would recommend putting the ExitStack code in a decorator and wrap the functions where you need this behavior.
You can also use a decorator. I am not using this code, so the syntax below is almost certainly incomplete, but the general idea is worth it:
import functools
def fallback_to_default(fn):
@functools.wraps(fn)
def new_fn(fp=None, *args, **kwargs):
with contextlib.ExitStack() as stack:
if not fp:
fp = stack.enter_context(open('default.txt'))
return fn(fp, *args, **kwargs)
return new_fn
@fallback_to_default
def readSomething(fp=None):
return fp.read(100)
source to share
To summarize the problem in plain language:
- You may be given a handle to an open file, after which you want to leave it open, since it is the responsibility of the caller to close that resource.
- You may need to open your own resource and it is your responsibility to close it.
This is a problem with accepting heterogeneous argument types in Python. You can do this, but it can make your code a little more ugly at times.
Context managers are just syntactic sugar for try / finally:
def readSomething(fp=None):
close_fp = False
if fp is None:
fp = open('default.txt')
close_fp = True
try:
return fp.read(100)
finally:
if close_fp:
fp.close()
To make it prettier than that, consider changing the interfaces so that you don't have to handle both read data and resource management from the same function — refactoring so that your functions have one responsibility.
source to share
You can define a custom context manager that only does something if None is passed to it, but that might be overkill:
class ContextOrNone(object):
def __init__(self, obj, fn, *args, **kwargs):
if obj is not None:
self.obj = obj
self.cleanup = False
else:
self.obj = fn(*args, **kwargs)
self.cleanup = True
def __enter__(self):
return self.obj
def __exit__(self, ex_type, ex_val, traceback):
if self.cleanup:
self.obj.__exit__(ex_type, ex_val, traceback)
Or, using contextlib.contextmanager
:
from contextlib import contextmanager
@contextmanager
def ContextOrNone(obj, fn, *args, **kwargs):
was_none = obj is None
try:
if was_none:
obj = fn(*args, **kwargs)
yield obj
finally:
if was_none:
obj.__exit__()
Once you have defined this, you can define it readSomething
as:
def readSomething(fp=None):
with ContextOrNone(fp, open, 'default.txt', 'r') as fp:
return fp.read(100)
source to share
Honestly, the most Pythonic version of your code is probably exactly what you already have, other than a slightly cleaned up one:
def readSomething(fp=None):
if fp:
return fp.read(100)
with open('default.txt') as fp:
return fp.read(100)
This retains your original intent and functionality. It's clear and easy to read. Of course this has a bit of repetition. If your example has been simplified to the point that the repetitive part is too grotesque for you, then hoist it into your own function:
def complicatedStuff(buf, sz):
# Obviously more code will go here.
return buf.read(sz)
def readSomething(fp=None):
if fp:
return complicatedStuff(fp, 100)
with open('default.txt') as fp:
return complicatedStuff(fp, 100)
It's not Pythonic to jump over a lot of hoops so as not to repeat a bit.
source to share