I'm writing a utility that will likely be called by a script that uses argparse
, but should be useable by any script. The engine object constructor needs a dozen or so parameters to tune its behavior. The question is: What is the best way to allow the caller to send the necessary parameters without requiring extra work setting params that have defaults.
First thought: Since some users will have an argparse.Namespace
instance already, I might make use of that. But other users would need to create a Namespace (or just a subclass of object
) which seems like a problem.
Second thought: Normal methods are written to just take a sequnce of (named) parameters, which is what programmers will expect. Better yet, Python allows **kwargs
in constructor's parameters, and vars(aNamespace)
is a map, so it seems we could call EngineObject(*args, **vars(an_argparse_namespace))
and have the best of all worlds. But, alas, if the caller's Namespace
instance has attributes that the Engine
constructor doesn't recognize, it won't run.
Third thought: But... if we write the constructor as __init__(self,arg1,arg2,arg3=def3,...argN=defN, **kwargs)
that final **kwargs
will eat all the extra attributes. I like this... But, as I found by accident, if I pass a needed ..., misspelled=value, ...
there is no warning that I got it wrong because that **kwargs
eats the misspelled parameter name just like it would eat other named parameters that we don't care about.
Current thought: I now have this code:
class Engine:
@classmethod
def clean_map(cls, amap):
"""return a map holding only valid keys/values from amap"""
...
return safe_map
def __init__(self, arg1, arg2,
arg3=default3,
...
argN=defaultN)
self.arg1 = arg1 # etc
...
This gives me a slightly awkward way to give every user a good experience: Users who are just calling the service can pass the parameters they care about... and they'll be warned if they misspell something. Users who have an argparse.Namespace
object can write this somewhat clunky code: engine = Engine(**Engine.clean _map(vars(myNamespace)))
An alternative would be to wrap the clean_map
method into an alternate constructor:
class Engine:
@classmethod
safe_construct(cls, amap):
...
return cls(**safe_map)
So: What would be the ideal way to handle this? Is my "current thought" the best way to be Pythonic? Do you prefer the "safe_construct" technique to what I'm now using? Something else?