• +43 660 1453541
  • contact@germaniumhq.com

Why I Still Do Non Pythonic Checks


Why I Still Do Non Pythonic Checks

Sometimes things in life are simple. Python in particular allows checking if iterables or strings are empty, and if values are False, with just the not operator. Sometimes life is not that simple :).

Most of the time this works as you expect:

if not elements_found:
    raise Exception("No elements were found")

Where it gets tricky in my opinion is when you have optional arguments that default. Let’s assume we have a remove folder on an object that deletes the path received as an argument, or the current pwd if no argument is present:

class Executor:
    def __init__(self) -> None:
        self.pwd = ...

    def rm(self,
           path: str = "") -> None:
        if not path:
            remove(self.pwd)
            return

        remove(os.path.join(self.pwd, path))

The usage is generally straightforward:

executor.rm("some/subpath")
# or
executor.rm()

The problem that I see is what happens if the values are computed. For example what if we wrongly compute the subpath as ""? Then not path matches just like before, and removes our self.pwd.

Thus I prefer having the optional arguments initialized to None (it also simplifies things when having parameters that are iterables), then explicitly check for them in the initialization, and fail for wrong values:

def rm(self,
       path: Optional[str] = None) -> None:
    if path is None:
        remove(self.pwd)
        return

    if not path:
        raise Exception("Invalid path sent for removal")

    remove(os.path.join(self.pwd, path))

The argument can be made that it’s still possible to send None values into the function, but my experience is that it’s harder to do that. In general no one programs with None values, this is not Java where references are magically initialized to null, so checking explicitly for None and using them for optional parameters seems to me the way to go.