def udf_signature(input_type, pin, klass):
    """Compute the appropriate signature for a
    :class:`~ibis.expr.operations.Node` from a list of input types
    `input_type`.

    Parameters
    ----------
    input_type : List[ibis.expr.datatypes.DataType]
        A list of :class:`~ibis.expr.datatypes.DataType` instances representing
        the signature of a UDF/UDAF.
    pin : Optional[int]
        If this is not None, pin the `pin`-th argument type to `klass`
    klass : Union[Type[pd.Series], Type[SeriesGroupBy]]
        The pandas object that every argument type should contain

    Returns
    -------
    Tuple[Type]
        A tuple of types appropriate for use in a multiple dispatch signature.

    Examples
    --------
    >>> from pprint import pprint
    >>> import pandas as pd
    >>> from pandas.core.groupby import SeriesGroupBy
    >>> import ibis.expr.datatypes as dt
    >>> input_type = [dt.string, dt.double]
    >>> sig = udf_signature(input_type, pin=None, klass=pd.Series)
    >>> pprint(sig)  # doctest: +ELLIPSIS
    ((<class '...Series'>, <... '...str...'>, <... 'NoneType'>),
     (<class '...Series'>,
      <... 'float'>,
      <... 'numpy.floating'>,
      <... 'NoneType'>))
    >>> not_nullable_types = [
    ...     dt.String(nullable=False), dt.Double(nullable=False)]
    >>> sig = udf_signature(not_nullable_types, pin=None, klass=pd.Series)
    >>> pprint(sig)  # doctest: +ELLIPSIS
    ((<class '...Series'>, <... '...str...'>),
     (<class '...Series'>,
      <... 'float'>,
      <... 'numpy.floating'>))
    >>> sig0 = udf_signature(input_type, pin=0, klass=SeriesGroupBy)
    >>> sig1 = udf_signature(input_type, pin=1, klass=SeriesGroupBy)
    >>> pprint(sig0)  # doctest: +ELLIPSIS
    (<class '...SeriesGroupBy'>,
     (<class '...SeriesGroupBy'>,
      <... 'float'>,
      <... 'numpy.floating'>,
      <... 'NoneType'>))
    >>> pprint(sig1)  # doctest: +ELLIPSIS
    ((<class '...SeriesGroupBy'>,
      <... '...str...'>,
      <... 'NoneType'>),
     <class '...SeriesGroupBy'>)
    """
    nargs = len(input_type)

    if not nargs:
        return ()

    if nargs == 1:
        r, = input_type
        result = (klass,) + rule_to_python_type(r) + nullable(r)
        return (result,)

    return tuple(
        klass
        if pin is not None and pin == i
        else ((klass,) + rule_to_python_type(r) + nullable(r))
        for i, r in enumerate(input_type)
    )
Compute the appropriate signature for a
    :class:`~ibis.expr.operations.Node` from a list of input types
    `input_type`.

    Parameters
    ----------
    input_type : List[ibis.expr.datatypes.DataType]
        A list of :class:`~ibis.expr.datatypes.DataType` instances representing
        the signature of a UDF/UDAF.
    pin : Optional[int]
        If this is not None, pin the `pin`-th argument type to `klass`
    klass : Union[Type[pd.Series], Type[SeriesGroupBy]]
        The pandas object that every argument type should contain

    Returns
    -------
    Tuple[Type]
        A tuple of types appropriate for use in a multiple dispatch signature.

    Examples
    --------
    >>> from pprint import pprint
    >>> import pandas as pd
    >>> from pandas.core.groupby import SeriesGroupBy
    >>> import ibis.expr.datatypes as dt
    >>> input_type = [dt.string, dt.double]
    >>> sig = udf_signature(input_type, pin=None, klass=pd.Series)
    >>> pprint(sig)  # doctest: +ELLIPSIS
    ((<class '...Series'>, <... '...str...'>, <... 'NoneType'>),
     (<class '...Series'>,
      <... 'float'>,
      <... 'numpy.floating'>,
      <... 'NoneType'>))
    >>> not_nullable_types = [
    ...     dt.String(nullable=False), dt.Double(nullable=False)]
    >>> sig = udf_signature(not_nullable_types, pin=None, klass=pd.Series)
    >>> pprint(sig)  # doctest: +ELLIPSIS
    ((<class '...Series'>, <... '...str...'>),
     (<class '...Series'>,
      <... 'float'>,
      <... 'numpy.floating'>))
    >>> sig0 = udf_signature(input_type, pin=0, klass=SeriesGroupBy)
    >>> sig1 = udf_signature(input_type, pin=1, klass=SeriesGroupBy)
    >>> pprint(sig0)  # doctest: +ELLIPSIS
    (<class '...SeriesGroupBy'>,
     (<class '...SeriesGroupBy'>,
      <... 'float'>,
      <... 'numpy.floating'>,
      <... 'NoneType'>))
    >>> pprint(sig1)  # doctest: +ELLIPSIS
    ((<class '...SeriesGroupBy'>,
      <... '...str...'>,
      <... 'NoneType'>),
     <class '...SeriesGroupBy'>)