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 raisesAttributeErrorbefore theifcheck is even reached. The guard never executes. - After
fit():self.estimators_is always set to a list of fitted estimators. It’s neverNoneand 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