Outputs

Parameters are typically used for the inputs to a Parameterized objects, declaring as precisely as possible which inputs are allowed. You can also declare what outputs are generated by a Parameterized, using @param.output. At present, Param itself does not use output declarations internally, but they can be queried by Param-based programs to allow safe chaining of Parameterized objects into pipelines, such as for the boxflow dataflow programming system or the multi-page pipelines in Panel.

@param.output annotates a method on a Parameterized class to declare that it returns one or more outputs of a specified type. As a simple example, this declaration indicates that the given function returns a number:

import param

class P(param.Parameterized):
    a = param.Number(default=5, bounds=(0, 10))
    b = param.Number(default=5, bounds=(0, 10))

    @param.output(param.Number)
    def product(self):
        return self.a * self.b
    
p = P(a=2, b=3)
p.product()
6

If a program wants to know if the output from object p is suitable for connecting to an input of some other object q, it can query this output type:

p.param.outputs()
{'product': (<param.Number at 0x7fe3e41027a0>,
  <bound method P.product of P(a=2, b=3, name='P00002')>,
  None)}

This return value is of the form name: (type, method, index), which here indicates that:

  • object p generates one output called product

  • product is of type param.Number

  • product can be generated by invoking method p.product()

  • product will be returned directly as a single value by the method (indicated by an index of None; otherwise the index would indicate the position of this particular output in a tuple returned by the method)

An automated program could use this information to decide whether the output from one Parameterized is suitable for connecting to the input of another.

The above example is typical, but let’s review the other output declarations accepted by @param.output. The simplest declaration declares the method returns an object of the same name as the method, without any type guarantees:

@param.output()
def product(self): return self.a * self.b

More commonly, a parameter type will be specified as above, indicating that this method returns a value of that type, again defaulting to the method name:

@param.output(param.Number())
def product2(self): return self.a * self.b

The output name can be declared explicitly as a keyword argument if desired, e.g. if the method name is not a suitable output name:

@param.output(result=param.Number())
def __call__(self): return self.a * self.b

Multiple outputs may be declared using keywords mapping from output name to the type (Python >= 3.6 only):

@param.output(prod_num=param.Number(), prod_str=param.String())
def products(self): 
    prod = self.a * self.b
    return prod, str(prod)

@param.output also accepts Python object types, which will be upgraded to a ClassSelector:

@param.output(int)
def int_product(self): return int(self.a * self.b)

We can see these various options in action:

class Q(param.Parameterized):
    a = param.Number(default=5, bounds=(0, 10))
    b = param.Number(default=5, bounds=(0, 10))

    @param.output()
    def product(self): return self.a * self.b

    @param.output(param.Number())
    def product2(self): return self.a * self.b

    @param.output(result=param.Number())
    def __call__(self): return self.a * self.b

    @param.output(prod_num=param.Number(), prod_str=param.String())
    def products(self): 
        prod = self.a * self.b
        return prod, str(prod)

    @param.output(int)
    def int_product(self): return int(self.a * self.b)

q=Q()
q
Q(a=5, b=5, name='Q00003')
q.param.outputs()
{'result': (<param.Number at 0x7fe3e4102bb0>,
  <bound method Q.__call__ of Q(a=5, b=5, name='Q00003')>,
  None),
 'int_product': (<param.ClassSelector at 0x7fe3e408f730>,
  <bound method Q.int_product of Q(a=5, b=5, name='Q00003')>,
  None),
 'product': (<param.parameterized.Parameter at 0x7fe3e411fb90>,
  <bound method Q.product of Q(a=5, b=5, name='Q00003')>,
  None),
 'product2': (<param.Number at 0x7fe3e4102940>,
  <bound method Q.product2 of Q(a=5, b=5, name='Q00003')>,
  None),
 'prod_num': (<param.Number at 0x7fe3e4102c80>,
  <bound method Q.products of Q(a=5, b=5, name='Q00003')>,
  0),
 'prod_str': (<param.parameterized.String at 0x7fe3e408f3c0>,
  <bound method Q.products of Q(a=5, b=5, name='Q00003')>,
  1)}

Here, you can see that there are two outputs from prod_str(), one of type Number and one of type String, and that they are in the order (number, string) in the tuple. The other outputs are all a single result returned directly from that method, with the indicated types (defaulting to Parameter) and names. Annotating outputs in this way can help you build large, flexible systems for connecting Parameterized objects together into larger data or computational structures.