bpo-46342: make @typing.final introspectable (GH-30530)

Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
This commit is contained in:
Jelle Zijlstra 2022-01-12 11:38:25 -08:00 committed by GitHub
parent e34c9367f8
commit 0bbf30e2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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