Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b51e880

Browse files
thomasjpfanjjerphan
authored andcommitted
FIX Fix available_if with bounded methods (scikit-learn#23077)
1 parent 9069c3f commit b51e880

File tree

3 files changed

+27
-11
lines changed

3 files changed

+27
-11
lines changed

doc/whats_new/v1.1.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,9 @@ Changelog
10711071
- |Fix| :func:`utils.estimator_html_repr` has an improved visualization for nested
10721072
meta-estimators. :pr:`21310` by `Thomas Fan`_.
10731073

1074+
- |Fix| :func:`utils.metaestimators.available_if` correctly returns a bounded
1075+
method that can be pickled. :pr:`23077` by `Thomas Fan`_.
1076+
10741077
- |API| :func:`utils.metaestimators.if_delegate_has_method` is deprecated and will be
10751078
removed in version 1.3. Use :func:`utils.metaestimators.available_if` instead.
10761079
:pr:`22830` by :user:`Jérémie du Boisberranger <jeremiedbb>`.

sklearn/utils/metaestimators.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
# Andreas Mueller
44
# License: BSD
55
from typing import List, Any
6+
from types import MethodType
67
import warnings
8+
from functools import wraps
79

810
from abc import ABCMeta, abstractmethod
911
from operator import attrgetter
@@ -124,21 +126,17 @@ def __get__(self, obj, owner=None):
124126
# this is to allow access to the docstrings.
125127
if not self.check(obj):
126128
raise attr_err
129+
out = MethodType(self.fn, obj)
127130

128-
# lambda, but not partial, allows help() to work with update_wrapper
129-
out = lambda *args, **kwargs: self.fn(obj, *args, **kwargs) # noqa
130131
else:
131-
132-
def fn(*args, **kwargs):
132+
# This makes it possible to use the decorated method as an unbound method,
133+
# for instance when monkeypatching.
134+
@wraps(self.fn)
135+
def out(*args, **kwargs):
133136
if not self.check(args[0]):
134137
raise attr_err
135138
return self.fn(*args, **kwargs)
136139

137-
# This makes it possible to use the decorated method as an unbound method,
138-
# for instance when monkeypatching.
139-
out = lambda *args, **kwargs: fn(*args, **kwargs) # noqa
140-
# update the docstring of the returned function
141-
update_wrapper(out, self.fn)
142140
return out
143141

144142

sklearn/utils/tests/test_metaestimators.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import pytest
33
import warnings
44

5+
import pickle
6+
57
from sklearn.utils.metaestimators import if_delegate_has_method
68
from sklearn.utils.metaestimators import available_if
79

@@ -92,13 +94,14 @@ def test_if_delegate_has_method():
9294
class AvailableParameterEstimator:
9395
"""This estimator's `available` parameter toggles the presence of a method"""
9496

95-
def __init__(self, available=True):
97+
def __init__(self, available=True, return_value=1):
9698
self.available = available
99+
self.return_value = return_value
97100

98101
@available_if(lambda est: est.available)
99102
def available_func(self):
100103
"""This is a mock available_if function"""
101-
pass
104+
return self.return_value
102105

103106

104107
def test_available_if_docstring():
@@ -155,3 +158,15 @@ def test_if_delegate_has_method_deprecated():
155158
# Only when calling it
156159
with pytest.warns(FutureWarning, match="if_delegate_has_method was deprecated"):
157160
hasattr(MetaEst(HasPredict()), "predict")
161+
162+
163+
def test_available_if_methods_can_be_pickled():
164+
"""Check that available_if methods can be pickled.
165+
166+
Non-regression test for #21344.
167+
"""
168+
return_value = 10
169+
est = AvailableParameterEstimator(available=True, return_value=return_value)
170+
pickled_bytes = pickle.dumps(est.available_func)
171+
unpickled_func = pickle.loads(pickled_bytes)
172+
assert unpickled_func() == return_value

0 commit comments

Comments
 (0)