Skip to content
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

bpo-46342: make @typing.final introspectable #30530

Merged
merged 4 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
9 changes: 9 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2536,10 +2536,47 @@ def test_no_isinstance(self):
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__"))


class CastTests(BaseTestCase):

Expand Down
11 changes: 10 additions & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2042,8 +2042,17 @@ class Leaf:
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
Fidget-Spinner marked this conversation as resolved.
Show resolved Hide resolved
except (AttributeError, TypeError):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be open to just doing just except Exception here, in case some type throws a different exception if you try to set an attribute. For precedent, note that @no_type_check catches only 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


Expand Down
Original file line number Diff line number Diff line change
@@ -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.