diff options
-rw-r--r-- | Doc/library/typing.rst | 9 | ||||
-rw-r--r-- | Lib/test/test_typing.py | 72 | ||||
-rw-r--r-- | Lib/typing.py | 11 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2022-01-11-04-28-09.bpo-46342.5QVEH1.rst | 2 |
4 files changed, 93 insertions, 1 deletions
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index de7aa08..cb14db9 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -1985,6 +1985,15 @@ Functions and decorators .. versionadded:: 3.8 + .. versionchanged:: 3.11 + The decorator will now set the ``__final__`` attribute to ``True`` + on the decorated object. Thus, a check like + ``if getattr(obj, "__final__", False)`` can be used at runtime + to determine whether an object ``obj`` has been marked as final. + If the decorated object does not support setting attributes, + the decorator returns the object unchanged without raising an exception. + + .. decorator:: no_type_check Decorator to indicate that annotations are not type hints. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index af5b1df..fd8237a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1,5 +1,7 @@ import contextlib import collections +from functools import lru_cache +import inspect import pickle import re import sys @@ -2536,10 +2538,80 @@ class FinalTests(BaseTestCase): with self.assertRaises(TypeError): issubclass(int, Final) + +class FinalDecoratorTests(BaseTestCase): def test_final_unmodified(self): def func(x): ... self.assertIs(func, final(func)) + def test_dunder_final(self): + @final + def func(): ... + @final + class Cls: ... + self.assertIs(True, func.__final__) + self.assertIs(True, Cls.__final__) + + class Wrapper: + __slots__ = ("func",) + def __init__(self, func): + self.func = func + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + # Check that no error is thrown if the attribute + # is not writable. + @final + @Wrapper + def wrapped(): ... + self.assertIsInstance(wrapped, Wrapper) + self.assertIs(False, hasattr(wrapped, "__final__")) + + class Meta(type): + @property + def __final__(self): return "can't set me" + @final + class WithMeta(metaclass=Meta): ... + self.assertEqual(WithMeta.__final__, "can't set me") + + # Builtin classes throw TypeError if you try to set an + # attribute. + final(int) + self.assertIs(False, hasattr(int, "__final__")) + + # Make sure it works with common builtin decorators + class Methods: + @final + @classmethod + def clsmethod(cls): ... + + @final + @staticmethod + def stmethod(): ... + + # The other order doesn't work because property objects + # don't allow attribute assignment. + @property + @final + def prop(self): ... + + @final + @lru_cache() + def cached(self): ... + + # Use getattr_static because the descriptor returns the + # underlying function, which doesn't have __final__. + self.assertIs( + True, + inspect.getattr_static(Methods, "clsmethod").__final__ + ) + self.assertIs( + True, + inspect.getattr_static(Methods, "stmethod").__final__ + ) + self.assertIs(True, Methods.prop.fget.__final__) + self.assertIs(True, Methods.cached.__final__) + class CastTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index d520f6b..972b8ba 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2042,8 +2042,17 @@ def final(f): class Other(Leaf): # Error reported by type checker ... - There is no runtime checking of these properties. + There is no runtime checking of these properties. The decorator + sets the ``__final__`` attribute to ``True`` on the decorated object + to allow runtime introspection. """ + try: + f.__final__ = True + except (AttributeError, TypeError): + # Skip the attribute silently if it is not writable. + # AttributeError happens if the object has __slots__ or a + # read-only property, TypeError if it's a builtin class. + pass return f diff --git a/Misc/NEWS.d/next/Library/2022-01-11-04-28-09.bpo-46342.5QVEH1.rst b/Misc/NEWS.d/next/Library/2022-01-11-04-28-09.bpo-46342.5QVEH1.rst new file mode 100644 index 0000000..31d484f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-11-04-28-09.bpo-46342.5QVEH1.rst @@ -0,0 +1,2 @@ +The ``@typing.final`` decorator now sets the ``__final__`` attribute on the +decorated object to allow runtime introspection. Patch by Jelle Zijlstra. |