vivarium-core
vivarium-core copied to clipboard
Improve Handling of Process Parameters and Defaults
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:
- 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. - Similarly, we don't actually enforce the convention that all accepted parameters be present in
defaults
. For example,MyProcess
could have anext_update
that usesself.parameters['flag2']
even thoughMyProcess.defaults
does not containflag2
. - 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.
- Our approach is not the standard Python way of specifying parameters, which is confusing.
-
defaults
are not handled correctly when subclassing processes. For example, consider the processclass MyProcess2(MyProcess)
with a constructor that callsMyProcess.__init__(parameters)
. InsideMyProcess.__init__
,self.defaults
refers toMyProcess2.defaults
, notMyProcess.defaults
like you'd expect.
Functionality to Preserve
- Backwards compatibility. We don't want to have to rewrite all our libraries.
- 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:
-
Not sure how we can do this yet.
-
I think we can use
inspect
andlocals()
to do this. We would, inProcess.__init__()
, first useself.__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.