Skip to content

Commit

Permalink
bpo-46342: make @typing.final introspectable (GH-30530)
Browse files Browse the repository at this point in the history
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
  • Loading branch information
JelleZijlstra and Fidget-Spinner authored Jan 12, 2022
1 parent e34c936 commit 0bbf30e
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 1 deletion.
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
72 changes: 72 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import contextlib
import collections
from functools import lru_cache
import inspect
import pickle
import re
import sys
Expand Down Expand Up @@ -2536,10 +2538,80 @@ 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__"))

# 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):

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
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


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.

0 comments on commit 0bbf30e

Please sign in to comment.