Fix: Replace dead error-handling code in AdaBoost.feature_importances_ with check_is_fitted

Fixed scikit-learn/scikit-learn#34472 — 1 line bug-fix replacing unreachable guard code.

The Bug

Repo: scikit-learn/scikit-learn Issue: #34472 Status: PR-merged PR: https://github.com/scikit-learn/scikit-learn/pull/34478

Description: The feature_importances_ property on AdaBoostClassifier and AdaBoostRegressor had a dead/unreachable error-handling guard block that could never execute.

Fix scope: 1 line changed in sklearn/ensemble/_weight_boosting.py

Root Cause

The guard block in feature_importances_ looked like this:

if self.estimators_ is None or len(self.estimators_) == 0:
    raise ValueError(
        "Estimator not fitted, call `fit` before `feature_importances_`."
    )

This code was dead in both code paths:

  • Before fit(): self.estimators_ doesn’t exist as an attribute at all, so Python raises AttributeError before the if check is even reached. The guard never executes.
  • After fit(): self.estimators_ is always set to a list of fitted estimators. It’s never None and it’s never empty — AdaBoost.fit() guarantees at least one estimator. So the guard never triggers on this path either.

The net result: anyone calling feature_importances_ before fitting got AttributeError: 'AdaBoostClassifier' object has no attribute 'estimators_' — a confusing, unhelpful error — instead of the intended informative ValueError about not being fitted.

Impact: Low severity for production users who always fit before accessing attributes, but bad for developer experience. Newcomers to scikit-learn who try model.feature_importances_ before fitting would see an opaque AttributeError rather than a clear NotFittedError.

The Fix

Replaced the dead guard with a single call to check_is_fitted(self):

-        if self.estimators_ is None or len(self.estimators_) == 0:
-            raise ValueError(
-                "Estimator not fitted, call `fit` before `feature_importances_`."
-            )
+        check_is_fitted(self)

check_is_fitted() is the standard scikit-learn utility for this purpose. It raises a NotFittedError with a consistent, helpful message. This matches the pattern used by other ensemble estimators like RandomForestClassifier and GradientBoostingClassifier.

Pattern & Takeaways

Pattern: Dead code that looks correct at first glance but can never execute. The guard checked for None/empty, but the attribute itself doesn’t exist before fitting — Python’s attribute lookup semantics mean the guard is unreachable. This is a common bug in Python codebases where developers add defensive checks without verifying the control flow leading to them.

Key insight: Always test defensive code paths explicitly. If your code has a guard like if attr is None, verify that attr actually exists and could be None at that point. Unit tests that call the property before fit() would have caught this immediately — but the existing tests only called feature_importances_ after fitting.

Transfer Potential

Medium — the specific bug is straightforward, but the pattern of dead defensive code appears in many codebases. The general lesson: any code behind a guard that depends on an attribute’s value needs to verify the attribute exists first. Adding hasattr checks or using getattr(self, 'attr', None) can make such guards robust. Using framework-standard utilities like check_is_fitted also ensures consistent error messages across an entire project.


Auto-generated from PR #34478. View all patches on GitHub.

References

[1] scikit-learn/scikit-learn [2] #34472 [3] https://github.com/scikit-learn/scikit-learn/pull/34478