Decorate an objective function.

    Note
    ----
    For multi-class task, the y_pred is group by class_id first, then group by row_id.
    If you want to get i-th row y_pred in j-th class, the access way is y_pred[j * num_data + i]
    and you should group grad and hess in this way as well.

    Parameters
    ----------
    func : callable
        Expects a callable with signature ``func(y_true, y_pred)`` or ``func(y_true, y_pred, group):

            y_true : array-like of shape = [n_samples]
                The target values.
            y_pred : array-like of shape = [n_samples] or shape = [n_samples * n_classes] (for multi-class task)
                The predicted values.
            group : array-like
                Group/query data, used for ranking task.

    Returns
    -------
    new_func : callable
        The new objective function as expected by ``lightgbm.engine.train``.
        The signature is ``new_func(preds, dataset)``:

            preds : array-like of shape = [n_samples] or shape = [n_samples * n_classes] (for multi-class task)
                The predicted values.
            dataset : Dataset
                The training set from which the labels will be extracted using ``dataset.get_label()``.

def _objective_function_wrapper(func):
    """Decorate an objective function.

    Note
    ----
    For multi-class task, the y_pred is group by class_id first, then group by row_id.
    If you want to get i-th row y_pred in j-th class, the access way is y_pred[j * num_data + i]
    and you should group grad and hess in this way as well.

    Parameters
    ----------
    func : callable
        Expects a callable with signature ``func(y_true, y_pred)`` or ``func(y_true, y_pred, group):

            y_true : array-like of shape = [n_samples]
                The target values.
            y_pred : array-like of shape = [n_samples] or shape = [n_samples * n_classes] (for multi-class task)
                The predicted values.
            group : array-like
                Group/query data, used for ranking task.

    Returns
    -------
    new_func : callable
        The new objective function as expected by ``lightgbm.engine.train``.
        The signature is ``new_func(preds, dataset)``:

            preds : array-like of shape = [n_samples] or shape = [n_samples * n_classes] (for multi-class task)
                The predicted values.
            dataset : Dataset
                The training set from which the labels will be extracted using ``dataset.get_label()``.
    """
    def inner(preds, dataset):
        """Call passed function with appropriate arguments."""
        labels = dataset.get_label()
        argc = argc_(func)
        if argc == 2:
            grad, hess = func(labels, preds)
        elif argc == 3:
            grad, hess = func(labels, preds, dataset.get_group())
        else:
            raise TypeError("Self-defined objective function should have 2 or 3 arguments, got %d" % argc)
        """weighted for objective"""
        weight = dataset.get_weight()
        if weight is not None:
            """only one class"""
            if len(weight) == len(grad):
                grad = np.multiply(grad, weight)
                hess = np.multiply(hess, weight)
            else:
                num_data = len(weight)
                num_class = len(grad) // num_data
                if num_class * num_data != len(grad):
                    raise ValueError("Length of grad and hess should equal to num_class * num_data")
                for k in range_(num_class):
                    for i in range_(num_data):
                        idx = k * num_data + i
                        grad[idx] *= weight[i]
                        hess[idx] *= weight[i]
        return grad, hess
    return inner