Clean way to open/close an optional file in python?
Question:
My code:
fh = None
if args.optional_input_file_path is not None:
fh = open(args.optional_input_file_path)
my_function(foo, bar, fh)
if args.optional_input_file_path is not None:
fh.close()
I don’t like how I need to write if args.optional_input_file is not None:
twice.
Moving the conditional logic inside my_function
also isn’t ideal because I’m intentionally passing in IO
objects to the function instead of file paths to make it easier to test.
Is there a cleaner way to achieve the same thing?
I want to be able to write my_function(foo, bar, fh)
exactly once, and have fh
be either an IO
object or None
, depending on the value of args.optional_input_file_path
.
Answers:
Try this
my_function(foo, bar, open(args.file_path) if args.file_path else None)
As there is no reference to file open, it will auto close in the next GC
Use a custom context manager that propagates None
but otherwise behaves like open
:
import contextlib
@contextlib.contextmanager
def open_not_none(file, *args, **kwargs):
if file is not None:
with open(file, *args, **kwargs) as f:
yield f
else:
yield None
which can be used as
with open_not_none(args.optional_input_file_path, "r") as fh:
my_function(foo, bar, fh)
This lets you keep all the benefits of opening the file in a with statement while avoiding duplicating the call to my_function
for the None
and not-None
cases. You may also richly type hint it with typing.overload
if you so choose.
edit: after seeing Frank Yellin’s answer, I realized that this could be implemented more simply using contextlib.nullcontext
as
def open_not_none(file, *args, **kwargs):
if file is None:
return contextlib.nullcontext()
return open(file, *args, **kwargs)
The usage is exactly the same.
The original approach works by creating and activating a new a context manager that either wraps the open
-context-manager or does nothing. In this second approach, we get rid of that extra layer of context-manager indirection, and substitute with either returning the open
-context-manager directly or returning a context manager that does nothing.
The documentation for contextlib suggests the following for handling optional contexts:
def process_file(filename):
if filename is not None:
cm = open(filename)
else:
cm = nullcontext()
with cm as file: # file will be None if no filename
my_function(...., file)
My code:
fh = None
if args.optional_input_file_path is not None:
fh = open(args.optional_input_file_path)
my_function(foo, bar, fh)
if args.optional_input_file_path is not None:
fh.close()
I don’t like how I need to write if args.optional_input_file is not None:
twice.
Moving the conditional logic inside my_function
also isn’t ideal because I’m intentionally passing in IO
objects to the function instead of file paths to make it easier to test.
Is there a cleaner way to achieve the same thing?
I want to be able to write my_function(foo, bar, fh)
exactly once, and have fh
be either an IO
object or None
, depending on the value of args.optional_input_file_path
.
Try this
my_function(foo, bar, open(args.file_path) if args.file_path else None)
As there is no reference to file open, it will auto close in the next GC
Use a custom context manager that propagates None
but otherwise behaves like open
:
import contextlib
@contextlib.contextmanager
def open_not_none(file, *args, **kwargs):
if file is not None:
with open(file, *args, **kwargs) as f:
yield f
else:
yield None
which can be used as
with open_not_none(args.optional_input_file_path, "r") as fh:
my_function(foo, bar, fh)
This lets you keep all the benefits of opening the file in a with statement while avoiding duplicating the call to my_function
for the None
and not-None
cases. You may also richly type hint it with typing.overload
if you so choose.
edit: after seeing Frank Yellin’s answer, I realized that this could be implemented more simply using contextlib.nullcontext
as
def open_not_none(file, *args, **kwargs):
if file is None:
return contextlib.nullcontext()
return open(file, *args, **kwargs)
The usage is exactly the same.
The original approach works by creating and activating a new a context manager that either wraps the open
-context-manager or does nothing. In this second approach, we get rid of that extra layer of context-manager indirection, and substitute with either returning the open
-context-manager directly or returning a context manager that does nothing.
The documentation for contextlib suggests the following for handling optional contexts:
def process_file(filename):
if filename is not None:
cm = open(filename)
else:
cm = nullcontext()
with cm as file: # file will be None if no filename
my_function(...., file)