vivarium-core icon indicating copy to clipboard operation
vivarium-core copied to clipboard

Improve Handling of Process Parameters and Defaults

Open U8NWXD opened this issue 2 years ago • 2 comments

Status Quo

Current Approach

Currently, processes have a defaults class variable that specifies the parameters they expect. Then when calling a process, you can provide parameters in a dictionary to the parameters argument to override those defaults. For example:

class MyProcess(Process):

    defaults = {
        'flag': True,
    }

proc = MyProcess({'flag': False})

Problems with Current Approach

Our current approach for handling process parameters and defaults has a number of issues:

  1. Parameters do not have types, which prevents type checking from catching typing errors. For example, you could write MyProcess({'flag': 'test'}) without raising any type errors.
  2. Similarly, we don't actually enforce the convention that all accepted parameters be present in defaults. For example, MyProcess could have a next_update that uses self.parameters['flag2'] even though MyProcess.defaults does not contain flag2.
  3. Other Python tools don't understand our way of specifying parameters. This means that e.g. PyCharm won't suggest parameters when you initialize a process.
  4. Our approach is not the standard Python way of specifying parameters, which is confusing.
  5. defaults are not handled correctly when subclassing processes. For example, consider the process class MyProcess2(MyProcess) with a constructor that calls MyProcess.__init__(parameters). Inside MyProcess.__init__, self.defaults refers to MyProcess2.defaults, not MyProcess.defaults like you'd expect.

Functionality to Preserve

  1. Backwards compatibility. We don't want to have to rewrite all our libraries.
  2. Ability to save off parameters for serialization.

Possible Alternative

All the problems would be solved by using normal Python function arguments for process parameters. Here's how we could preserve the desired functionality:

  1. Not sure how we can do this yet.

  2. I think we can use inspect and locals() to do this. We would, in Process.__init__(), first use self.__class__ to get the subclass. Then we could use code like this to get the parameters passed to the subclass constructor:

    >>> def foobar(foo, bar, baz):
    ...     sig, foobar_locals = inspect.signature(foobar), locals()
    ...     return [foobar_locals[param.name] for param in sig.parameters.values()]
    ...
    >>> foobar(1, 2, 3)
    [1, 2, 3]
    

    Source: https://stackoverflow.com/a/10724602

    Note that the locals() call would have to happen in the subclass, and the result would need to be passed to the superclass constructor.

U8NWXD avatar Mar 18 '22 19:03 U8NWXD