8000 MAINT Clean-up deprecated if_delegate_has_method for 1.3 (#25879) · scikit-learn/scikit-learn@9260f51 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9260f51

Browse files
authored
MAINT Clean-up deprecated if_delegate_has_method for 1.3 (#25879)
1 parent 1a9257c commit 9260f51

File tree

5 files changed

+6
-258
lines changed

5 files changed

+6
-258
lines changed

doc/modules/classes.rst

-9
Original file line numberDiff line numberDiff line change
@@ -1681,12 +1681,3 @@ Utilities from joblib:
16811681

16821682
Recently deprecated
16831683
===================
1684-
1685-
To be removed in 1.3
1686-
--------------------
1687-
1688-
.. autosummary::
1689-
:toctree: generated/
1690-
:template: function.rst
1691-
1692-
utils.metaestimators.if_delegate_has_method

sklearn/utils/estimator_checks.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,7 @@ def check_fit_score_takes_y(name, estimator_orig):
17161716
func(X, y)
17171717
args = [p.name for p in signature(func).parameters.values()]
17181718
if args[0] == "self":
1719-
# if_delegate_has_method makes methods into functions
1719+
# available_if makes methods into functions
17201720
# with an explicit "self", so need to shift arguments
17211721
args = args[1:]
17221722
assert args[1] in ["y", "Y"], (

sklearn/utils/metaestimators.py

+2-80
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33
# Andreas Mueller
44
# License: BSD
55
from typing import List, Any
6-
import warnings
76

87
from abc import ABCMeta, abstractmethod
9-
from operator import attrgetter
108
import numpy as np
119
from contextlib import suppress
1210

1311
from ..utils import _safe_indexing
1412
from ..utils._tags import _safe_tags
1513
from ..base import BaseEstimator
16-
from ._available_if import available_if, _AvailableIfDescriptor
14+
from ._available_if import available_if
1715

18-
__all__ = ["available_if", "if_delegate_has_method"]
16+
__all__ = ["available_if"]
1917

2018

2119
class _BaseComposition(BaseEstimator, metaclass=ABCMeta):
@@ -96,82 +94,6 @@ def _validate_names(self, names):
9694
)
9795

9896

99-
# TODO(1.3) remove
100-
class _IffHasAttrDescriptor(_AvailableIfDescriptor):
101-
"""Implements a conditional property using the descriptor protocol.
102-
103-
Using this class to create a decorator will raise an ``AttributeError``
104-
if none of the delegates (specified in ``delegate_names``) is an attribute
105-
of the base object or the first found delegate does not have an attribute
106-
``attribute_name``.
107-
108-
This allows ducktyping of the decorated method based on
109-
``delegate.attribute_name``. Here ``delegate`` is the first item in
110-
``delegate_names`` for which ``hasattr(object, delegate) is True``.
111-
112-
See https://docs.python.org/3/howto/descriptor.html for an explanation of
113-
descriptors.
114-
"""
115-
116-
def __init__(self, fn, delegate_names, attribute_name):
117-
super().__init__(fn, self._check, attribute_name)
118-
self.delegate_names = delegate_names
119-
120-
def _check(self, obj):
121-
warnings.warn(
122-
"if_delegate_has_method was deprecated in version 1.1 and will be "
123-
"removed in version 1.3. Use available_if instead.",
124-
FutureWarning,
125-
)
126-
127-
delegate = None
128-
for delegate_name in self.delegate_names:
129-
try:
130-
delegate = attrgetter(delegate_name)(obj)
131-
break
132-
except AttributeError:
133-
continue
134-
135-
if delegate is None:
136-
return False
137-
# raise original AttributeError
138-
getattr(delegate, self.attribute_name)
139-
140-
return True
141-
142-
143-
# TODO(1.3) remove
144-
def if_delegate_has_method(delegate):
145-
"""Create a decorator for methods that are delegated to a sub-estimator.
146-
147-
.. deprecated:: 1.3
148-
`if_delegate_has_method` is deprecated in version 1.1 and will be removed in
149-
version 1.3. Use `available_if` instead.
150-
151-
This enables ducktyping by hasattr returning True according to the
152-
sub-estimator.
153-
154-
Parameters
155-
----------
156-
delegate : str, list of str or tuple of str
157-
Name of the sub-estimator that can be accessed as an attribute of the
158-
base object. If a list or a tuple of names are provided, the first
159-
sub-estimator that is an attribute of the base object will be used.
160-
161-
Returns
162-
-------
163-
callable
164-
Callable makes the decorated method available if the delegate
165-
has a method with the same name as the decorated method.
166-
"""
167-
if isinstance(delegate, list):
168-
delegate = tuple(delegate)
169-
if not isinstance(delegate, tuple):
170-
delegate = (delegate,)
171-
172-
return lambda fn: _IffHasAttrDescriptor(fn, delegate, attribute_name=fn.__name__)
173-
174-
17597
def _safe_split(estimator, X, y, indices, train_indices=None):
17698
"""Create subset of dataset and properly handle kernels.
17799

sklearn/utils/tests/test_metaestimators.py

-109
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,10 @@
1-
import numpy as np
21
import pytest
3-
import warnings
42

53
import pickle
64

7-
from sklearn.utils.metaestimators import if_delegate_has_method
85
from sklearn.utils.metaestimators import available_if
96

107

11-
class Prefix:
12-
def func(self):
13-
pass
14-
15-
16-
class MockMetaEstimator:
17-
"""This is a mock meta estimator"""
18-
19-
a_prefix = Prefix()
20-
21-
@if_delegate_has_method(delegate="a_prefix")
22-
def func(self):
23-
"""This is a mock delegated function"""
24-
pass
25-
26-
27-
@pytest.mark.filterwarnings("ignore:if_delegate_has_method was deprecated")
28-
def test_delegated_docstring():
29-
assert "This is a mock delegated function" in str(
30-
MockMetaEstimator.__dict__["func"].__doc__
31-
)
32-
assert "This is a mock delegated function" in str(MockMetaEstimator.func.__doc__)
33-
assert "This is a mock delegated function" in str(MockMetaEstimator().func.__doc__)
34-
35-
36-
class MetaEst:
37-
"""A mock meta estimator"""
38-
39-
def __init__(self, sub_est, better_sub_est=None):
40-
self.sub_est = sub_est
41-
self.better_sub_est = better_sub_est
42-
43-
@if_delegate_has_method(delegate="sub_est")
44-
def predict(self):
45-
pass
46-
47-
48-
class MetaEstTestTuple(MetaEst):
49-
"""A mock meta estimator to test passing a tuple of delegates"""
50-
51-
@if_delegate_has_method(delegate=("sub_est", "better_sub_est"))
52-
def predict(self):
53-
pass
54-
55-
56 10000 -
class MetaEstTestList(MetaEst):
57-
"""A mock meta estimator to test passing a list of delegates"""
58-
59-
@if_delegate_has_method(delegate=["sub_est", "better_sub_est"])
60-
def predict(self):
61-
pass
62-
63-
64-
class HasPredict:
65-
"""A mock sub-estimator with predict method"""
66-
67-
def predict(self):
68-
pass
69-
70-
71-
class HasNoPredict:
72-
"""A mock sub-estimator with no predict method"""
73-
74-
pass
75-
76-
77-
class HasPredictAsNDArray:
78-
"""A mock sub-estimator where predict is a NumPy array"""
79-
80-
predict = np.ones((10, 2), dtype=np.int64)
81-
82-
83-
@pytest.mark.filterwarnings("ignore:if_delegate_has_method was deprecated")
84-
def test_if_delegate_has_method():
85-
assert hasattr(MetaEst(HasPredict()), "predict")
86-
assert not hasattr(MetaEst(HasNoPredict()), "predict")
87-
assert not hasattr(MetaEstTestTuple(HasNoPredict(), HasNoPredict()), "predict")
88-
assert hasattr(MetaEstTestTuple(HasPredict(), HasNoPredict()), "predict")
89-
assert not hasattr(MetaEstTestTuple(HasNoPredict(), HasPredict()), "predict")
90-
assert not hasattr(MetaEstTestList(HasNoPredict(), HasPredict()), "predict")
91-
assert hasattr(MetaEstTestList(HasPredict(), HasPredict()), "predict")
92-
93-
948
class AvailableParameterEstimator:
959
"""This estimator's `available` parameter toggles the presence of a method"""
9610

@@ -137,29 +51,6 @@ def test_available_if_unbound_method():
13751
AvailableParameterEstimator.available_func(est)
13852

13953

140-
@pytest.mark.filterwarnings("ignore:if_delegate_has_method was deprecated")
141-
def test_if_delegate_has_method_numpy_array():
142-
"""Check that we can check for an attribute that is a NumPy array.
143-
144-
This is a non-regression test for:
145-
https://github.com/scikit-learn/scikit-learn/issues/21144
146-
"""
147-
estimator = MetaEst(HasPredictAsNDArray())
148-
assert hasattr(estimator, "predict")
149-
150-
151-
def test_if_delegate_has_method_deprecated():
152-
"""Check the deprecation warning of if_delegate_has_method"""
153-
# don't warn when creating the decorator
154-
with warnings.catch_warnings():
155-
warnings.simplefilter("error", FutureWarning)
156-
_ = if_delegate_has_method(delegate="predict")
157-
158-
# Only when calling it
159-
with pytest.warns(FutureWarning, match="if_delegate_has_method was deprecated"):
160-
hasattr(MetaEst(HasPredict()), "predict")
161-
162-
16354
def test_available_if_methods_can_be_pickled():
16455
"""Check that available_if methods can be pickled.
16556

sklearn/utils/tests/test_testing.py

+3-59
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import pytest
1111

1212
from sklearn.utils.deprecation import deprecated
13-
from sklearn.utils.metaestimators import available_if, if_delegate_has_method
13+
from sklearn.utils.metaestimators import available_if
1414
from sklearn.utils._testing import (
1515
assert_raises,
1616
assert_no_warnings,
@@ -430,64 +430,7 @@ def fit(self, X, y):
430430
"""Incorrect docstring but should not be tested"""
431431

432432

433-
class MockMetaEstimatorDeprecatedDelegation:
434-
def __init__(self, delegate):
435-
"""MetaEstimator to check if doctest on delegated methods work.
436-
437-
Parameters
438-
---------
439-
delegate : estimator
440-
Delegated estimator.
441-
"""
442-
self.delegate = delegate
443-
444-
@if_delegate_has_method(delegate="delegate")
445-
def predict(self, X):
446-
"""This is available only if delegate has predict.
447-
448-
Parameters
449-
----------
450-
y : ndarray
451-
Parameter y
452-
"""
453-
return self.delegate.predict(X)
454-
455-
@if_delegate_has_method(delegate="delegate")
456-
@deprecated("Testing a deprecated delegated method")
457-
def score(self, X):
458-
"""This is available only if delegate has score.
459-
460-
Parameters
461-
---------
462-
y : ndarray
463-
Parameter y
464-
"""
465-
466-
@if_delegate_has_method(delegate="delegate")
467-
def predict_proba(self, X):
468-
"""This is available only if delegate has predict_proba.
469-
470-
Parameters
471-
---------
472-
X : ndarray
473-
Parameter X
474-
"""
475-
return X
476-
477-
@deprecated("Testing deprecated function with wrong params")
478-
def fit(self, X, y):
479-
"""Incorrect docstring but should not be tested"""
480-
481-
482-
@pytest.mark.filterwarnings("ignore:if_delegate_has_method was deprecated")
483-
@pytest.mark.parametrize(
484-
"mock_meta",
485-
[
486-
MockMetaEstimator(delegate=MockEst()),
487-
MockMetaEstimatorDeprecatedDelegation(delegate=MockEst()),
488-
],
489-
)
490-
def test_check_docstring_parameters(mock_meta):
433+
def test_check_docstring_parameters():
491434
pytest.importorskip(
492435
"numpydoc",
493436
reason="numpydoc is required to test the docstrings",
@@ -506,6 +449,7 @@ def test_check_docstring_parameters(mock_meta):
506449
check_docstring_parameters(Klass.f_bad_sections)
507450

508451
incorrect = check_docstring_parameters(f_check_param_definition)
452+
mock_meta = MockMetaEstimator(delegate=MockEst())
509453
mock_meta_name = mock_meta.__class__.__name__
510454
assert incorrect == [
511455
"sklearn.utils.tests.test_testing.f_check_param_definition There "

0 commit comments

Comments
 (0)
0