Design Considerations

options is not intended to replace every class’s or method’s parameter passing mechanisms–just the most highly-optioned ones that multiplex a package’s functionality to a range of use cases. These are generally the highest-level, most outward-facing classes, objects, and APIs. They will generally have at least four configuration variables (e.g. kwargs used to create, configure, and define each instance).

In general, classes will define a set of methods that are “outwards facing”–methods called by external code when consuming the class’s functionality. Those methods should generally expose their options through **kwargs, creating a local variable (say opts) that represents the sum of all options in use–the full stack of call, instance, and class options, including any present magical interpretations.

Internal class methods–the sort that are not generally called by external code, and that by Python convention are often prefixed by an underscore (_)–these generally do not need **kwargs. They should accept their options as a single variable (say opts again) that the externally-facing methods will provide.

For example, if options didn’t provide the nice formatting function attrs, we might have designed our own:

def _attrs(self, opts):
    nicekeys = [ k for k in opts.keys() if not k.startswith('_') ]
    return ', '.join([ "{}={}".format(k, repr(opts[k])) for k in nicekeys ])

def draw(self, **kwargs):
    opts = self.options.push(kwargs)
    print(self._attrs(opts))

draw(), being the outward-facing API, accepts general arguments and manages their stacking (by push``ing ``kwargs onto the instance options). When the internal _attrs() method is called, it is handed a pre-digested opts package of options.

A nice side-effect of making this distinction: Whenever you see a method with **kwargs, you know it’s outward-facing. When you see a method with just opts, you know it’s internal.

Objects defined with options make excellent “callables.” Define the __call__ method, and you have a very nice analog of function calls.

options has broad utility, but it’s not for every class or module. It best suits high-level front-end APIs that multiplex lots of potential functionality, and wish/need to do it in a clean/simple way. Classes for which the set of instance variables is small, or functions/methods for which the set of known/possible parameters is limited–these work just fine with classic Python calling conventions. For those, options is overkill. “Horses for courses.”