1

I have a set of slightly different operations I want to perform on a bunch of different files, some of which may or may not exist at any given time. In all cases, if a file doesn't exist (returning FileNotFoundError), I want it to just log that that particular file doesn't exist, then move on, i.e. identical exception handling for all FileNotFoundError cases.

The tricky part is that what I want to do with each set of file is slightly different. So I can't simply do:

for f in [f1, f2, f3, f4]:
    try:
        do_the_thing(f)
    except FileNotFoundError:
        print(f'{f} not found, moving on!')

Instead I want something like:

try:
    for f in [f_list_1]:
        do_thing_A(f)
    for f in [f_list_2]:
        do_thing_B(f)
    for f in [f_list_3]:
        do_thing_C(f)
except FileNotFoundError:
    print(f'{f} not found, moving on!')

But where each for block is tried regardless of success / failure of previous block.

Obviously I could use a whole lot of separate try-except sets:

for f in [f_list_1]:
    try:
        do_thing_A(f)
    except FileNotFoundError:
        print(f'{f} not found, moving on!')
for f in [f_list_2]:
    try:
        do_thing_B(f)
    except FileNotFoundError:
        print(f'{f} not found, moving on!')
for f in [f_list_3]:
    try:
        do_thing_C(f)
    except FileNotFoundError:
        print(f'{f} not found, moving on!')

But is there a more elegant or Pythonic way to do this, given that it's the same exception handling in each case?

TY Lim
  • 509
  • 1
  • 3
  • 11
  • 1
    What do you do inside the `do_the_thing_*` methods? Because your exception usage is always effectively ignoring it, so to me it feels like you can go for `os.path.isfile()` or `os.path.exists()` in an if condition. [This question](https://stackoverflow.com/questions/82831/how-do-i-check-whether-a-file-exists-without-exceptions) seems to have a lot of such approaches. If you are immediately opening the file inside those methods, maybe consider putting the try-except inside that method? – shriakhilc Jan 28 '22 at 15:10
  • Is the use of exceptions fundamental to this task or could you use `os.path.isfile()`? – JonSG Jan 28 '22 at 15:10
  • Use of exceptions is not fundamental - it is essentially ignoring it, yes, at most just flagging for reference. To clarify, @shriakhilc are you suggesting basically putting the `try`-`except` inside the various methods, having it try one of the various approaches to check for file existence as the first step and encapsulating the entire rest of the method in the `try` block? Various methods are just data cleaning and processing - first step is opening the file, last step is re-saving it - so I think something like that would work, but could you give an example? – TY Lim Jan 28 '22 at 15:18
  • Correction, I suppose it'd be more like encapsulating the rest of the method with `if file_exists:` (using one of the various `os` methods), and maybe `else: report_file_missing`? – TY Lim Jan 28 '22 at 15:23
  • 1
    IMHO codereview would be more suitable for this question. https://codereview.stackexchange.com/ – gonczor Jan 28 '22 at 15:48

1 Answers1

2

I think I would start with something like this that wraps your functions.

def do_thing_A(f):
    print(f"do_thing_A({f})")

def do_thing_B(f):
    print(f"do_thing_B({f})")

def do_by_thing(fn, file_path):
    try:
        fn(file_path)
    except FileNotFoundError:
        print(f'{file_path} not found, moving on!')

tasks = [
    (do_thing_A, ["hello", "word"]), #(function, list_of_paths)
    (do_thing_B, ["foo", "bar"]),
]

for fn, file_list in tasks:
    for file_path in file_list:
        do_by_thing(fn, file_path)

Having done so though I think this begs the question of decorators. Maybe something like:

import os

def if_file_exists(fn):
    def _inner(f):
        if not os.path.isfile(f):
            print(f'{f} not found, moving on!')
            return
        fn(f)
    return _inner

@if_file_exists
def do_thing_A(f):
    print(f"do_thing_A({f})")

@if_file_exists
def do_thing_B(f):
    print(f"do_thing_B({f})")

tasks = [
    (do_thing_A, ["./results.csv", "word"]), #(function, list_of_paths)
    (do_thing_B, ["./sample.txt", "bar"]),
]

for fn, file_list in tasks:
    for file_path in file_list:
        fn(file_path)

Of course, either option could be altered to use exceptions or os.path.isfile(). I just did one each way.

If you decided that wrappers and decorators was overkill, then you could also just simply do:

def do_thing_A(f):
    print(f"do_thing_A({f})")

def do_thing_B(f):
    print(f"do_thing_B({f})")

tasks = [
    (do_thing_A, ["hello", "word"]), #(function, list_of_paths)
    (do_thing_B, ["foo", "bar"]),
]

for fn, file_list in tasks:
    for file_path in file_list:
        try:
            fn(file_path)
        except FileNotFoundError:
            print(f'{file_path} not found, moving on!')
JonSG
  • 10,542
  • 2
  • 25
  • 36