Conversation
|
I realized we could do something smarter: inject |
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
src/typing_extensions.py
Outdated
| @functools.wraps(original_init_subclass) | ||
| def __init_subclass__(*args, **kwargs): | ||
| warnings.warn(msg, category=category, stacklevel=stacklevel + 1) | ||
| return original_init_subclass(*args, **kwargs) | ||
|
|
||
| arg.__init_subclass__ = __init_subclass__ | ||
| arg.__deprecated__ = __new__.__deprecated__ = msg | ||
| __init_subclass__.__deprecated__ = msg |
There was a problem hiding this comment.
This doesn't always do the right thing, because of the fact that __init_subclass__ is an implicit classmethod. For example:
Python 3.11.5 (tags/v3.11.5:cce6ba9, Aug 24 2023, 14:38:34) [MSC v.1936 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing_extensions as t
>>> @t.deprecated('DEPRECATED')
... class Foo:
... ATTR = 1
... def __init_subclass__(cls, *args, **kwargs):
... print(f"{cls=}, {cls.ATTR=}")
...
>>> class Bar:
... ATTR = 2
...
>>> class Baz(Bar, Foo): ...
...
<stdin>:1: DeprecationWarning: DEPRECATED
cls=<class '__main__.Foo'>, cls.ATTR=1
>>> Baz.ATTR
2You can fix it by doing this (diff is relative to your PR branch):
diff --git a/src/typing_extensions.py b/src/typing_extensions.py
index 6a0015a..37ef575 100644
--- a/src/typing_extensions.py
+++ b/src/typing_extensions.py
@@ -2337,7 +2337,7 @@ else:
return arg
elif isinstance(arg, type):
original_new = arg.__new__
- original_init_subclass = arg.__init_subclass__
+ original_init_subclass = arg.__init_subclass__.__func__
@functools.wraps(original_new)
def __new__(cls, *args, **kwargs):
@@ -2358,7 +2358,7 @@ else:
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
return original_init_subclass(*args, **kwargs)
- arg.__init_subclass__ = __init_subclass__
+ arg.__init_subclass__ = classmethod(__init_subclass__)
arg.__deprecated__ = __new__.__deprecated__ = msg
__init_subclass__.__deprecated__ = msg
return argWith this change, we get the correct behaviour:
>>> import typing_extensions as t
>>> @t.deprecated('DEPRECATED')
... class Foo:
... ATTR = 1
... def __init_subclass__(cls, *args, **kwargs):
... print(f"{cls=}, {cls.ATTR=}")
...
>>> class Bar:
... ATTR = 2
...
>>> class Baz(Bar, Foo): ...
...
<stdin>:1: DeprecationWarning: DEPRECATED
cls=<class '__main__.Baz'>, cls.ATTR=2There was a problem hiding this comment.
Thanks for catching this. Your solution isn't quite right as it doesn't work for classes without a Python __init_subclass__, but I got something to work.
There was a problem hiding this comment.
Ah right, thanks -- I checked my solution worked in the REPL, but didn't actually run the test suite with my changes :) My bad!
| @functools.wraps(original_init_subclass) | ||
| def __init_subclass__(*args, **kwargs): | ||
| warnings.warn(msg, category=category, stacklevel=stacklevel + 1) | ||
| return original_init_subclass(*args, **kwargs) | ||
|
|
||
| arg.__init_subclass__ = __init_subclass__ |
There was a problem hiding this comment.
Not sure how often we hit this branch? Is it only when original_init_subclass is object.__init_subclass__? But I have no actual problem to point out here, I guess I'm just slightly queasy about how well it's tested right now :)
There was a problem hiding this comment.
Quite a lot, I think five or so tests failed before I implemented this.
There was a problem hiding this comment.
Yeah I know — my (poorly expressed) point was that they were all failing for the same reason (object.__init_subclass__ not being a classmethod) — I was curious whether there were any other realistic situations that would lead to us ending up in this branch :)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Fixes #251