I just caught myself writing code like this:
def register(self, *, # * enforces keyword-only parameters
key_value_container:dict=None, # legal parameter set #1
key:str=None, value=None): # legal parameter set #2
# enforce one of the parameter sets
if not ((key_value_container is not None and
key is None and value is None) or
(key is not None and value is not None and
key_value_container is None)):
raise TypeError('provide (key_value_container) or (key/value')
# handle each legal parameter setf
if key_value_container is not None:
for _k, _s in key_value_container.items():
self.register(_k, _s)
else:
do_something_with(key, value)
My goal was to have two signatures of the method register()
: it should take a key
and a value
or some container with a number of keys and values e.g. a dict
.
The *
in the argument at least forces me to provide named arguments but there is no need to provide a given number of arguments or even a given set of arguments.
Of course I can (should) provide two methods with different names and signatures instead of one.
But in case I want (have) to provide one method/function with more than one parameter semantics - what's the best way to make this visible and enforce it?
In detail:
To avoid boilerplate code you could so something like this: write additional functions that specify the different signatures. Then have a redirect
helper function that sends parameters from the main function to the additional functions depending on which signature the parameters match.
It's still not particularly elegant. If generalising this you would also need to consider how to deal with cases where None
is a valid parameter you might want to pass.
from inspect import getargspec
class Eg(object):
def register(self, key_value_container=None, key=None, value=None):
return redirect(locals(), [self.register_dict, self.register_single])
def register_dict(self, key_value_container=None):
print('register dict', key_value_container)
def register_single(self, key=None, value=None):
print('register single', key, value)
def redirect(params, functions):
provided = {k for k, v in params.items() if v is not None}
for function in functions:
if set(getargspec(function).args) == provided:
return function(**{k: params[k] for k in provided if k != 'self'})
raise TypeError('wrong configuration provided')
# This error could be expanded to explain which signatures are allowed
a = Eg()
a.register(key='hi', value='there')
a.register(key_value_container={'hi': 'there'})