8000 API Wrap __new__ instead of __init__ with the deprecated decorator by jeremiedbb · Pull Request #25733 · scikit-learn/scikit-learn · GitHub
[go: up one dir, main page]

Skip to content

API Wrap __new__ instead of __init__ with the deprecated decorator #25733

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

Merged
merged 5 commits into from
Mar 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/whats_new/v1.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ Changelog
during `transform` with no prior call to `fit` or `fit_transform`.
:pr:`25190` by :user:`Vincent Maladière <Vincent-Maladiere>`.

- |API| A `FutureWarning` is now raised when instantiating a class which inherits from
a deprecated base class (i.e. decorated by :class:`utils.deprecated`) and which
overrides the `__init__` method.
:pr:`25733` by :user:`Brigitta Sipőcz <bsipocz>` and
:user:`Jérémie du Boisberranger <jeremiedbb>`.

:mod:`sklearn.semi_supervised`
..............................

Expand Down
9 changes: 4 additions & 5 deletions sklearn/tests/test_docstring_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,11 @@ def test_docstring_parameters():
"Error for __init__ of %s in %s:\n%s" % (cls, name, w[0])
)

cls_init = getattr(cls, "__init__", None)

if _is_deprecated(cls_init):
# Skip checks on deprecated classes
if _is_deprecated(cls.__new__):
continue
elif cls_init is not None:
this_incorrect += check_docstring_parameters(cls.__init__, cdoc)

this_incorrect += check_docstring_parameters(cls.__init__, cdoc)

for method_name in cdoc.methods:
method = getattr(cls, method_name)
Expand Down
15 changes: 8 additions & 7 deletions sklearn/utils/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,18 @@ def _decorate_class(self, cls):
if self.extra:
msg += "; %s" % self.extra

# FIXME: we should probably reset __new__ for full generality
init = cls.__init__
new = cls.__new__

def wrapped(*args, **kwargs):
def wrapped(cls, *args, **kwargs):
warnings.warn(msg, category=FutureWarning)
return init(*args, **kwargs)
if new is object.__new__:
return object.__new__(cls)
return new(cls, *args, **kwargs)

cls.__init__ = wrapped
cls.__new__ = wrapped

wrapped.__name__ = "__init__"
wrapped.deprecated_original = init
wrapped.__name__ = "__new__"
wrapped.deprecated_original = new

return cls

Expand Down
23 changes: 22 additions & 1 deletion sklearn/utils/tests/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ class MockClass4:
pass


class MockClass5(MockClass1):
"""Inherit from deprecated class but does not call super().__init__."""

def __init__(self, a):
self.a = a


@deprecated("a message")
class MockClass6:
"""A deprecated class that overrides __new__."""

def __new__(cls, *args, **kwargs):
assert len(args) > 0
return super().__new__(cls)


@deprecated()
def mock_function():
return 10
Expand All @@ -48,6 +64,10 @@ def test_deprecated():
MockClass2().method()
with pytest.warns(FutureWarning, match="deprecated"):
MockClass3()
with pytest.warns(FutureWarning, match="qwerty"):
MockClass5(42)
with pytest.warns(FutureWarning, match="a message"):
MockClass6(42)
with pytest.warns(FutureWarning, match="deprecated"):
val = mock_function()
assert val == 10
Expand All @@ -56,10 +76,11 @@ def test_deprecated():
def test_is_deprecated():
# Test if _is_deprecated helper identifies wrapping via deprecated
# NOTE it works only for class methods and functions
assert _is_deprecated(MockClass1.__init__)
assert _is_deprecated(MockClass1.__new__)
assert _is_deprecated(MockClass2().method)
assert _is_deprecated(MockClass3.__init__)
assert not _is_deprecated(MockClass4.__init__)
assert _is_deprecated(MockClass5.__new__)
assert _is_deprecated(mock_function)


Expand Down
0