So I'm writing a class for the purpose of doing data analysis on a signal that I am measuring. There is many ways that I can process the signal and other optional metadata that can be associated with each trial that I measure the signal. I guess my questions boils down to the best way in which I can handle multiple keyword arguments in a way my class can auto-detect the relevant arguments that isn't just a bunch of if-else statements, I guess similar to how you can add many optional keywords to matplotlib plots?
For example, lets say I have this hypothetical class that looks like this:
class Signal:
def __init__(self, filepath, **kwargs):
self.filepath = filepath
self.signal_df = pd.read_csv(self.filepath)
for k,v in kwargs.items():
setattr(self, key, value)
After the initial construction of the objects there would then be pertinent methods dependent on what keyword arguments have been passed. Thus I could easily create the two following objects with ease:
signal_1 = Signal('filepath_0', **{'foo':1, 'bar':'9.2'})
signal_2 = Signal('filepath_1', **{'foo':12, 'baz':'red'})
To try and solve this I've pretty much just implemented statements in the init() method such that I'm doing something like this:
class Signal:
def __init__(self, filepath, **kwargs):
self.filepath = filepath
self.signal_df = pd.read_csv(self.filepath)
for k,v in kwargs.items():
setattr(self, key, value)
if hasattr(self, 'foo'):
self.method_0(self.foo) # generic method that takes foo as argument
if hasattr(self, 'bar'):
self.method_1(self.bar) # generic method that takes bar as argument
else:
self.method_2(1.0) # alternate method if bar is not there
This just seems like a really clunky way of doing things and was hoping there may be a better solution. I appreciate any and all help!
CodePudding user response:
I wouldn't use **kwargs
here at all. Just define keyword-only parameters with the same kinds of sentinel defaults that you would use with positional parameters.
class Signal:
def __init__(self, filepath, *, foo=None, bar=None)):
self.filepath = filepath
self.signal_df = pd.read_csv(self.filepath)
if self.foo is not None:
self.foo = foo
self.method_0(foo)
if self.bar is not None:
self.bar = bar
self.method_1(bar)
else:
self.method_2(1.0)
Use **kwargs
when you need to accept arguments you really know nothing about, except that they need to be passed on to some other method.
CodePudding user response:
I think this is making a class too generic, IMO. First, any optional kwargs that you are searching for in this fashion should just have a default attached:
class Signal:
def __init__(
self,
filepath,
foo=None,
bar=None,
**kwargs
):
self.filepath = filepath
self.foo = foo
self.bar = bar
By selecting a suitable default, you can now check:
~snip~
def some_method(self):
if self.foo is not None:
# do something with foo
elif self.bar is not None:
# do something with bar
else:
# do something with 1
Otherwise, it might be better to use different classes if the attributes need to differ in this manner.