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?

Re: Best practice for method with large number of parameters 80 80

Maybe some reason not to, but what about a name/value pair array. The caller puts in the array only the name/value pairs they want to specify. You can validate this and assign to an object using a case statement.

If you'd rather do this from an object level, create an object that handles the universe of name/value pairs but after instantiation which sets the default values for name/value pairs, the user calls an update method that accepts the name and its value as the parameters. Now your validation is encapsulated and from the user's point of view, they are only making a bunch of update method calls.

Bottom line is to obfuscate the complexity from the user. Just a thought.

Re: Best practice for method with large number of parameters 80 80

Following on from fearless' suggestion...
I'm not sure about Python, but this looks like a using a factory, Typically that's for a constructor, but there's no reason not to use it for any complex method call.
Eg: suppose it's a car search where you can supply manufacturur, engine size, fuel, number of seats, age, min price, max price etc and all those are optional. Use a search builder that starts with defaults for everything, has methods to override each default, and an execute method. The execute method calls the real search with all the possible parameters set.
All the methods(except execute) should return the search object so they can be chained,
Using it looks like (pseudo code):
result= new CarSearch().fuel(electric).maxPrice(20000).execute();
or
result= new CarSearch().manufacturer(Ford).seats(7).execute();
Personally I'm a big fan of this approach.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of 1.18 million developers, IT pros, digital marketers, and technology enthusiasts learning and sharing knowledge.