ParameterizedFunctions

Parameterized classes and objects are full-featured substitues for Python objects, providing rich support and control for how attributes behave. What if you need similar features, but for functions rather than objects?

Python functions don’t directly support the various language features like descriptors that Param builds on, but you can instead make a Python class or object that behaves like a function, while still supporting Parameters. To make that easier, Param provides an abstract class ParameterizedFunction that you can use as a superclass for any function-like object you want to write. A ParameterizedFunction automatically invokes its __call__ method whenever it is instantiated. So, all you need to do is implement the __call__ method with the implementation of your function. For example:

from param import Parameter, ParameterizedFunction, ParamOverrides

class multiply(ParameterizedFunction):
    "Function to multiply two arguments."

    left  = Parameter(2, doc="Left-hand-side argument")
    right = Parameter(4, doc="Right-hand-side argument")

    def __call__(self, **params):
        p = ParamOverrides(self, params)
        return p.left * p.right
    
multiply()
8
multiply(left=3, right=7)
21
multiply.left = 7
multiply(right = 10)
70

Here you can see that multiply acts like any other function that takes keyword arguments, but the arguments are now documented, potentially type checked, and have default values.

This implementation depends on the separate object param.ParamOverrides, which provides two-level lookup of parameter values: first on the arguments provided to the call, and then (if not provided) on the ParameterizedFunction instance. This way a user can choose to provide any or none of the arguments when the function (really, function object) is invoked.

The __call__ method can also take positional arguments, but in that case the class author would need to handle any mapping from those arguments to parameters there might be. __call__ can also take extra keyword arguments beyond parameter values, but if so, you’ll need to construct ParamOverrides as p = ParamOverrides(self, params, allow_extra_keywords=True), then access the extra (non-Parameter) keywords in p.extra_keywords and process those explicitly.

.instance()

Usually, with a Parameterized object, you can modify values on the instance level, in addition to the class level shown above. Here, however, there is no instance to grab, because the ParameterizedFunction is called and evaluated, returning a value rather than the function object. If you want to grab an instance where you can set a value and then call the instance, you can use the .instance() method of a ParameterizedFunction:

multiply_by_10 = multiply.instance(right=10)
multiply_by_10(left=8)
80