[Python-checkins] gh-104050: Improve some typing around `default`s and sentinel values (#104626)

AlexWaygood webhook-mailer at python.org
Thu May 18 17:58:49 EDT 2023


https://github.com/python/cpython/commit/1c55e8d00728ceabd97cd1a5bd4906c9875a80c6
commit: 1c55e8d00728ceabd97cd1a5bd4906c9875a80c6
branch: main
author: Alex Waygood <Alex.Waygood at Gmail.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-05-18T21:58:42Z
summary:

gh-104050: Improve some typing around `default`s and sentinel values (#104626)

- Convert `unspecified` and `unknown` to be members of a `Sentinels` enum, rather than instances of bespoke classes.
  - An enum feels more idiomatic here, and works better with type checkers.
  - Convert some `==` and `!=` checks for these values to identity checks, which are more idiomatic with sentinels.
  - _Don't_ do the same for `Null`, as this needs to be a distinct type due to its usage in `clinic.py`.
- Use `object` as the annotation for `default` across `clinic.py`. `default` can be literally any object, so `object` is the correct annotation here.

---

Co-authored-by: Erlend E. Aasland <erlend.aasland at protonmail.com>

files:
M Tools/clinic/clinic.py

diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py
index ebee97829394..42aac7eb17f2 100755
--- a/Tools/clinic/clinic.py
+++ b/Tools/clinic/clinic.py
@@ -12,6 +12,7 @@
 import contextlib
 import copy
 import cpp
+import enum
 import functools
 import hashlib
 import inspect
@@ -28,7 +29,7 @@
 
 from collections.abc import Callable
 from types import FunctionType, NoneType
-from typing import Any, NamedTuple, NoReturn, Literal, overload
+from typing import Any, Final, NamedTuple, NoReturn, Literal, overload
 
 # TODO:
 #
@@ -58,25 +59,26 @@
     "return_value",
 }
 
-class Unspecified:
+
+class Sentinels(enum.Enum):
+    unspecified = "unspecified"
+    unknown = "unknown"
+
     def __repr__(self) -> str:
-        return '<Unspecified>'
+        return f"<{self.value.capitalize()}>"
+
 
-unspecified = Unspecified()
+unspecified: Final = Sentinels.unspecified
+unknown: Final = Sentinels.unknown
 
 
+# This one needs to be a distinct class, unlike the other two
 class Null:
     def __repr__(self) -> str:
         return '<Null>'
 
-NULL = Null()
-
 
-class Unknown:
-    def __repr__(self) -> str:
-        return '<Unknown>'
-
-unknown = Unknown()
+NULL = Null()
 
 sig_end_marker = '--'
 
@@ -2600,7 +2602,7 @@ class CConverter(metaclass=CConverterAutoRegister):
     # Or the magic value "unknown" if this value is a cannot be evaluated
     # at Argument-Clinic-preprocessing time (but is presumed to be valid
     # at runtime).
-    default: bool | Unspecified = unspecified
+    default: object = unspecified
 
     # If not None, default must be isinstance() of this type.
     # (You can also specify a tuple of types.)
@@ -2686,11 +2688,11 @@ def __init__(self,
              name: str,
              py_name: str,
              function,
-             default=unspecified,
+             default: object = unspecified,
              *,  # Keyword only args:
              c_default: str | None = None,
              py_default: str | None = None,
-             annotation: str | Unspecified = unspecified,
+             annotation: str | Literal[Sentinels.unspecified] = unspecified,
              unused: bool = False,
              **kwargs
     ):
@@ -2699,7 +2701,10 @@ def __init__(self,
         self.unused = unused
 
         if default is not unspecified:
-            if self.default_type and not isinstance(default, (self.default_type, Unknown)):
+            if (self.default_type
+                and default is not unknown
+                and not isinstance(default, self.default_type)
+            ):
                 if isinstance(self.default_type, type):
                     types_str = self.default_type.__name__
                 else:
@@ -2713,7 +2718,7 @@ def __init__(self,
         if py_default:
             self.py_default = py_default
 
-        if annotation != unspecified:
+        if annotation is not unspecified:
             fail("The 'annotation' parameter is not currently permitted.")
 
         # this is deliberate, to prevent you from caching information
@@ -3967,7 +3972,7 @@ class CReturnConverter(metaclass=CReturnConverterAutoRegister):
 
     # The Python default value for this parameter, as a Python value.
     # Or the magic value "unspecified" if there is no default.
-    default = None
+    default: object = None
 
     def __init__(self, *, py_default=None, **kwargs):
         self.py_default = py_default
@@ -4767,7 +4772,7 @@ def bad_node(self, node):
                     # but at least make an attempt at ensuring it's a valid expression.
                     try:
                         value = eval(default)
-                        if value == unspecified:
+                        if value is unspecified:
                             fail("'unspecified' is not a legal default value!")
                     except NameError:
                         pass # probably a named constant



More information about the Python-checkins mailing list