pf = pfunc(func)

    Returns a function that can be called just like func; however its arguments may be
    PyMC objects or containers of PyMC objects, and its return value will be a deterministic.

    Example:

        >>> A = pymc.Normal('A',0,1,size=10)
        >>> pprod = pymc.pfunc(numpy.prod)
        >>> B = pprod(A, axis=0)
        >>> B
        <pymc.PyMCObjects.Deterministic 'prod(A_0)' at 0x3ce49b0>
        >>> B.value
        -0.0049333289649554912
        >>> numpy.prod(A.value)
        -0.0049333289649554912

def pfunc(func):
    """
    pf = pfunc(func)

    Returns a function that can be called just like func; however its arguments may be
    PyMC objects or containers of PyMC objects, and its return value will be a deterministic.

    Example:

        >>> A = pymc.Normal('A',0,1,size=10)
        >>> pprod = pymc.pfunc(numpy.prod)
        >>> B = pprod(A, axis=0)
        >>> B
        <pymc.PyMCObjects.Deterministic 'prod(A_0)' at 0x3ce49b0>
        >>> B.value
        -0.0049333289649554912
        >>> numpy.prod(A.value)
        -0.0049333289649554912
    """
    if isinstance(func, np.ufunc):
        return pufunc(func)
    elif not inspect.isfunction(func):
        if func.__name__ == '__call__':
            raise ValueError(
                'Cannot get argspec of call method. Is it builtin?')
        try:
            return pfunc(func.__call__)
        except:
            cls, inst, tb = sys.exc_info()
            inst = cls(
                'Failed to create pfunc wrapper from object %s. Original error message:\n\n%s' %
                (func, inst.message))
            six.reraise(cls, inst, tb)
    fargs, fdefaults = get_signature(func)
    n_fargs = len(fargs)

    def dtrm_generator(*args, **kwds):
        name = func.__name__ + '(' + '_'.join([str(arg)
                                               for arg in list(args) +
                                               list(kwds.values())]) + ')'
        doc_str = 'A deterministic returning %s(%s, %s)' % (
            func.__name__,
            ', '.join([str(arg) for arg in args]),
            ', '.join(['%s=%s' % (key,
                                  str(val)) for key,
                       val in six.iteritems(kwds)]))

        parents = {}
        varargs = []
        for kwd, val in six.iteritems(kwds):
            parents[kwd] = val
        for i in xrange(len(args)):
            if i < n_fargs:
                parents[fargs[i]] = args[i]
            else:
                varargs.append(args[i])

        if len(varargs) == 0:
            eval_fun = func
        else:
            parents['varargs'] = varargs

            def wrapper(**wkwds_in):
                wkwds = copy(wkwds_in)
                wargs = []
                for arg in fargs:
                    wargs.append(wkwds.pop(arg))
                wargs.extend(wkwds.pop('varargs'))
                return func(*wargs, **wkwds)
            eval_fun = wrapper

        return pm.Deterministic(
            eval_fun, doc_str, name, parents, trace=False, plot=False)
    dtrm_generator.__name__ = func.__name__ + '_deterministic_generator'
    dtrm_generator.__doc__ = """
Deterministic-generating wrapper for %s. Original docstring:
%s

%s
    """ % (func.__name__, '_' * 60, func.__doc__)
    return dtrm_generator