I'm working to create a library in python that myself and a few colleagues will use. I'm struggling to conceptually understand how to best organize some code that feels like it doesn't cleanly fit into a class. None my ideas seem all that clean.
Right now I have several functions that all take the same input and they all do slightly different things with that input. Let's use these as examples:
(Approach 1)
from pathlib import Path
def find_files(directory: str):
return Path(directory).glob('some_pattern')
def foo(directory: str):
return
I don't like this implementation for two reasons:
- It doesn't validate the input. I'd like to at least check that
directory
is a directory that exists. And it appears that if I validate that parameter in every function---besides being a bad idea---breaks the SRP. - I get neuron activation seeing the same call signature in each function and thinking classes "bundle data and functionality together".
I could make this a class:
(Approach 2)
class Foo:
def __init__(self, directory: str):
self.dir = Path(directory)
# check that the input directory exists in a protected method
def find_files(self):
return self.dir.glob('some_pattern')
def foo(self):
return
I can now can do all the checks I want in the constructor to validate the inputs! But I'm left feeling that this structure is just an awkward collection of functions. They don't manipulate the state of the object (perhaps it's just me, but I find find_files(dir)
much cleaner than f = Foo(dir)
and then f.find_files()
) and I'd delete the object after one use.
I could also make Foo
protected and tell the user to never directly interact with it, instead using functions.
(Approach 3)
def find_files(directory: str):
f = _Foo(directory)
return f.find_files()
I now get all the checks I want since the input is routed through the constructor and the user doesn't have to work with this awkward object. But now I have to write a function for every corresponding method in Foo
---which doesn't seem efficient.
How should I structure this? Should I assume the user will provide good input (I think this as bad assumption, but I do see several large python libraries take this approach) and if not, which approach is better (or is it one I haven't thought of)?