Skip to content

Commit 0bbf30e

Browse files
bpo-46342: make @typing.final introspectable (GH-30530)
Co-authored-by: Ken Jin <[email protected]>
1 parent e34c936 commit 0bbf30e

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

Doc/library/typing.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,15 @@ Functions and decorators
19851985

19861986
.. versionadded:: 3.8
19871987

1988+
.. versionchanged:: 3.11
1989+
The decorator will now set the ``__final__`` attribute to ``True``
1990+
on the decorated object. Thus, a check like
1991+
``if getattr(obj, "__final__", False)`` can be used at runtime
1992+
to determine whether an object ``obj`` has been marked as final.
1993+
If the decorated object does not support setting attributes,
1994+
the decorator returns the object unchanged without raising an exception.
1995+
1996+
19881997
.. decorator:: no_type_check
19891998

19901999
Decorator to indicate that annotations are not type hints.

Lib/test/test_typing.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import contextlib
22
import collections
3+
from functools import lru_cache
4+
import inspect
35
import pickle
46
import re
57
import sys
@@ -2536,10 +2538,80 @@ def test_no_isinstance(self):
25362538
with self.assertRaises(TypeError):
25372539
issubclass(int, Final)
25382540

2541+
2542+
class FinalDecoratorTests(BaseTestCase):
25392543
def test_final_unmodified(self):
25402544
def func(x): ...
25412545
self.assertIs(func, final(func))
25422546

2547+
def test_dunder_final(self):
2548+
@final
2549+
def func(): ...
2550+
@final
2551+
class Cls: ...
2552+
self.assertIs(True, func.__final__)
2553+
self.assertIs(True, Cls.__final__)
2554+
2555+
class Wrapper:
2556+
__slots__ = ("func",)
2557+
def __init__(self, func):
2558+
self.func = func
2559+
def __call__(self, *args, **kwargs):
2560+
return self.func(*args, **kwargs)
2561+
2562+
# Check that no error is thrown if the attribute
2563+
# is not writable.
2564+
@final
2565+
@Wrapper
2566+
def wrapped(): ...
2567+
self.assertIsInstance(wrapped, Wrapper)
2568+
self.assertIs(False, hasattr(wrapped, "__final__"))
2569+
2570+
class Meta(type):
2571+
@property
2572+
def __final__(self): return "can't set me"
2573+
@final
2574+
class WithMeta(metaclass=Meta): ...
2575+
self.assertEqual(WithMeta.__final__, "can't set me")
2576+
2577+
# Builtin classes throw TypeError if you try to set an
2578+
# attribute.
2579+
final(int)
2580+
self.assertIs(False, hasattr(int, "__final__"))
2581+
2582+
# Make sure it works with common builtin decorators
2583+
class Methods:
2584+
@final
2585+
@classmethod
2586+
def clsmethod(cls): ...
2587+
2588+
@final
2589+
@staticmethod
2590+
def stmethod(): ...
2591+
2592+
# The other order doesn't work because property objects
2593+
# don't allow attribute assignment.
2594+
@property
2595+
@final
2596+
def prop(self): ...
2597+
2598+
@final
2599+
@lru_cache()
2600+
def cached(self): ...
2601+
2602+
# Use getattr_static because the descriptor returns the
2603+
# underlying function, which doesn't have __final__.
2604+
self.assertIs(
2605+
True,
2606+
inspect.getattr_static(Methods, "clsmethod").__final__
2607+
)
2608+
self.assertIs(
2609+
True,
2610+
inspect.getattr_static(Methods, "stmethod").__final__
2611+
)
2612+
self.assertIs(True, Methods.prop.fget.__final__)
2613+
self.assertIs(True, Methods.cached.__final__)
2614+
25432615

25442616
class CastTests(BaseTestCase):
25452617

Lib/typing.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2042,8 +2042,17 @@ class Leaf:
20422042
class Other(Leaf): # Error reported by type checker
20432043
...
20442044
2045-
There is no runtime checking of these properties.
2045+
There is no runtime checking of these properties. The decorator
2046+
sets the ``__final__`` attribute to ``True`` on the decorated object
2047+
to allow runtime introspection.
20462048
"""
2049+
try:
2050+
f.__final__ = True
2051+
except (AttributeError, TypeError):
2052+
# Skip the attribute silently if it is not writable.
2053+
# AttributeError happens if the object has __slots__ or a
2054+
# read-only property, TypeError if it's a builtin class.
2055+
pass
20472056
return f
20482057

20492058

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``@typing.final`` decorator now sets the ``__final__`` attribute on the
2+
decorated object to allow runtime introspection. Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)