# Buggy code
def _fit(self, X, y, groups, parameter_iterable):
    """Actual fitting,  performing the search over parameters."""

    estimator = self.estimator
    cv = check_cv(self.cv, y, classifier=is_classifier(estimator))
    self.scorer_ = check_scoring(self.estimator, scoring=self.scoring)

    X, y, groups = indexable(X, y, groups)
    n_splits = cv.get_n_splits(X, y, groups)
    if self.verbose > 0 and isinstance(parameter_iterable, Sized):
        n_candidates = len(parameter_iterable)
        print(
            "Fitting {0} folds for each of {1} candidates, totalling {2} fits".format(
                n_splits, n_candidates, n_candidates * n_splits
            )
        )

    base_estimator = clone(self.estimator)
    pre_dispatch = self.pre_dispatch

    out = Parallel(n_jobs=self.n_jobs, verbose=self.verbose, pre_dispatch=pre_dispatch)(
        delayed(_fit_and_score)(
            clone(base_estimator),
            X,
            y,
            self.scorer_,
            train,
            test,
            self.verbose,
            parameters,
            fit_params=self.fit_params,
            return_train_score=self.return_train_score,
            return_n_test_samples=True,
            return_times=True,
            return_parameters=True,
            error_score=self.error_score,
        )
        for parameters in parameter_iterable
        for train, test in cv.split(X, y, groups)
    )

    # if one choose to see train score, "out" will contain train score info
    if self.return_train_score:
        (
            train_scores,
            test_scores,
            test_sample_counts,
            fit_time,
            score_time,
            parameters,
        ) = zip(*out)
    else:
        (test_scores, test_sample_counts, fit_time, score_time, parameters) = zip(*out)

    candidate_params = parameters[::n_splits]
    n_candidates = len(candidate_params)

    results = dict()

    def _store(key_name, array, weights=None, splits=False, rank=False):
        """A small helper to store the scores/times to the cv_results_"""
        array = np.array(array, dtype=np.float64).reshape(n_candidates, n_splits)
        if splits:
            for split_i in range(n_splits):
                results["split%d_%s" % (split_i, key_name)] = array[:, split_i]

        array_means = np.average(array, axis=1, weights=weights)
        results["mean_%s" % key_name] = array_means
        # Weighted std is not directly available in numpy
        array_stds = np.sqrt(
            np.average(
                (array - array_means[:, np.newaxis]) ** 2, axis=1, weights=weights
            )
        )
        results["std_%s" % key_name] = array_stds

        if rank:
            results["rank_%s" % key_name] = np.asarray(
                rankdata(-array_means, method="min"), dtype=np.int32
            )

    # Computed the (weighted) mean and std for test scores alone
    # NOTE test_sample counts (weights) remain the same for all candidates
    test_sample_counts = np.array(test_sample_counts[:n_splits], dtype=np.int)

    _store(
        "test_score",
        test_scores,
        splits=True,
        rank=True,
        weights=test_sample_counts if self.iid else None,
    )
    if self.return_train_score:
        _store("train_score", train_scores, splits=True)
    _store("fit_time", fit_time)
    _store("score_time", score_time)

    best_index = np.flatnonzero(results["rank_test_score"] == 1)[0]
    best_parameters = candidate_params[best_index]

    # Use one np.MaskedArray and mask all the places where the param is not
    # applicable for that candidate. Use defaultdict as each candidate may
    # not contain all the params
    param_results = defaultdict(
        partial(np.ma.masked_all, (n_candidates,), dtype=object)
    )
    for cand_i, params in enumerate(candidate_params):
        for name, value in params.items():
            # An all masked empty array gets created for the key
            # `"param_%s" % name` at the first occurence of `name`.
            # Setting the value at an index also unmasks that index
            param_results["param_%s" % name][cand_i] = value

    results.update(param_results)

    # Store a list of param dicts at the key 'params'
    results["params"] = candidate_params

    self.cv_results_ = results
    self.best_index_ = best_index
    self.n_splits_ = n_splits

    if self.refit:
        # fit the best estimator using the entire dataset
        # clone first to work around broken estimators
        best_estimator = clone(base_estimator).set_params(**best_parameters)
        if y is not None:
            best_estimator.fit(X, y, **self.fit_params)
        else:
            best_estimator.fit(X, **self.fit_params)
        self.best_estimator_ = best_estimator
    return self


# Traceback (TypeError)
Traceback (most recent call last):
File "./t.py", line 19, in <module>
model = pickle.load(fh)
File "/Users/dsc/miniconda3/envs/p3/lib/python3.5/site-packages/numpy/ma/core.py", line 5863, in __setstate__
super(MaskedArray, self).__setstate__((shp, typ, isf, raw))
TypeError: object pickle not returning list

# Fixed code
def _fit(self, X, y, groups, parameter_iterable):
    """Actual fitting,  performing the search over parameters."""

    estimator = self.estimator
    cv = check_cv(self.cv, y, classifier=is_classifier(estimator))
    self.scorer_ = check_scoring(self.estimator, scoring=self.scoring)

    X, y, groups = indexable(X, y, groups)
    n_splits = cv.get_n_splits(X, y, groups)
    if self.verbose > 0 and isinstance(parameter_iterable, Sized):
        n_candidates = len(parameter_iterable)
        print(
            "Fitting {0} folds for each of {1} candidates, totalling {2} fits".format(
                n_splits, n_candidates, n_candidates * n_splits
            )
        )

    base_estimator = clone(self.estimator)
    pre_dispatch = self.pre_dispatch

    out = Parallel(n_jobs=self.n_jobs, verbose=self.verbose, pre_dispatch=pre_dispatch)(
        delayed(_fit_and_score)(
            clone(base_estimator),
            X,
            y,
            self.scorer_,
            train,
            test,
            self.verbose,
            parameters,
            fit_params=self.fit_params,
            return_train_score=self.return_train_score,
            return_n_test_samples=True,
            return_times=True,
            return_parameters=True,
            error_score=self.error_score,
        )
        for parameters in parameter_iterable
        for train, test in cv.split(X, y, groups)
    )

    # if one choose to see train score, "out" will contain train score info
    if self.return_train_score:
        (
            train_scores,
            test_scores,
            test_sample_counts,
            fit_time,
            score_time,
            parameters,
        ) = zip(*out)
    else:
        (test_scores, test_sample_counts, fit_time, score_time, parameters) = zip(*out)

    candidate_params = parameters[::n_splits]
    n_candidates = len(candidate_params)

    results = dict()

    def _store(key_name, array, weights=None, splits=False, rank=False):
        """A small helper to store the scores/times to the cv_results_"""
        array = np.array(array, dtype=np.float64).reshape(n_candidates, n_splits)
        if splits:
            for split_i in range(n_splits):
                results["split%d_%s" % (split_i, key_name)] = array[:, split_i]

        array_means = np.average(array, axis=1, weights=weights)
        results["mean_%s" % key_name] = array_means
        # Weighted std is not directly available in numpy
        array_stds = np.sqrt(
            np.average(
                (array - array_means[:, np.newaxis]) ** 2, axis=1, weights=weights
            )
        )
        results["std_%s" % key_name] = array_stds

        if rank:
            results["rank_%s" % key_name] = np.asarray(
                rankdata(-array_means, method="min"), dtype=np.int32
            )

    # Computed the (weighted) mean and std for test scores alone
    # NOTE test_sample counts (weights) remain the same for all candidates
    test_sample_counts = np.array(test_sample_counts[:n_splits], dtype=np.int)

    _store(
        "test_score",
        test_scores,
        splits=True,
        rank=True,
        weights=test_sample_counts if self.iid else None,
    )
    if self.return_train_score:
        _store("train_score", train_scores, splits=True)
    _store("fit_time", fit_time)
    _store("score_time", score_time)

    best_index = np.flatnonzero(results["rank_test_score"] == 1)[0]
    best_parameters = candidate_params[best_index]

    # Use one MaskedArray and mask all the places where the param is not
    # applicable for that candidate. Use defaultdict as each candidate may
    # not contain all the params
    param_results = defaultdict(
        partial(
            MaskedArray,
            np.empty(
                n_candidates,
            ),
            mask=True,
            dtype=object,
        )
    )
    for cand_i, params in enumerate(candidate_params):
        for name, value in params.items():
            # An all masked empty array gets created for the key
            # `"param_%s" % name` at the first occurence of `name`.
            # Setting the value at an index also unmasks that index
            param_results["param_%s" % name][cand_i] = value

    results.update(param_results)

    # Store a list of param dicts at the key 'params'
    results["params"] = candidate_params

    self.cv_results_ = results
    self.best_index_ = best_index
    self.n_splits_ = n_splits

    if self.refit:
        # fit the best estimator using the entire dataset
        # clone first to work around broken estimators
        best_estimator = clone(base_estimator).set_params(**best_parameters)
        if y is not None:
            best_estimator.fit(X, y, **self.fit_params)
        else:
            best_estimator.fit(X, **self.fit_params)
        self.best_estimator_ = best_estimator
    return self