-
-
Notifications
You must be signed in to change notification settings - Fork 25.9k
ENH Show a more informative error when accessing an attribute #31066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Authors: The scikit-learn developers | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
|
||
from dataclasses import dataclass, field | ||
|
||
|
||
@dataclass | ||
class FittedAttribute: | ||
"""Descriptor to raise a better error when attribute is not defined.""" | ||
|
||
condition: str = "" | ||
name: str = field(init=False) | ||
|
||
def __get__(self, instance, owner): | ||
if self.condition: | ||
msg = f"Call `fit` with {self.condition} to define '{self.name}'" | ||
else: | ||
msg = f"Call `fit` to define '{self.name}'" | ||
raise AttributeError(msg) | ||
|
||
def __set_name__(self, owner, name): | ||
self.name = name |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import pytest | ||
|
||
from sklearn.utils._attribute_validation import FittedAttribute | ||
|
||
|
||
class MyObj: | ||
coef_ = FittedAttribute() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Layering on the magic: is it possible to hide these attributes from tab completion when they haven't been fit yet? Right now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To answer my own question: class MyObj:
def __getattr__(self, name):
if name == "coef_": # could be `if name in self._list_of_potential_names`
raise AttributeError(f"{self.__class__.__name__} object has no attribute {name}!")
def fit(self):
self.coef_ = 123 This way |
||
cv_results_ = FittedAttribute("cv=None") | ||
|
||
def fit(self): | ||
self.coef_ = 123 | ||
self.cv_results_ = [1, 2, 3] | ||
|
||
|
||
def test_attribute_error(): | ||
my_obj = MyObj() | ||
|
||
msg = "Call `fit` to define 'coef_'" | ||
with pytest.raises(AttributeError, match=msg): | ||
my_obj.coef_ | ||
|
||
msg = "Call `fit` with cv=None to define 'cv_results_'" | ||
with pytest.raises(AttributeError, match=msg): | ||
my_obj.cv_results_ | ||
|
||
my_obj.fit() | ||
assert my_obj.coef_ == 123 | ||
assert my_obj.cv_results_ == [1, 2, 3] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my education, why use a dataclass instead of a plain class that implements
__get__
and a constructor? Taste?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the reason is NOT to have to implement the constructor 😅 Syntactic sugars are there to reduce boilerplate, that's a good enough of a reason for me to use them.