typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

Using typing.NoDefault instead of Ellipsis [3.13]

Open yoann9344 opened this issue 1 year ago • 3 comments

ParamSpec, TypeVar, TypeVarTuple have a __default__ attribute, which is evaluated to NoDefault, when no default is provided in constructor :

from typing import ParamSpec, TypeVar, TypeVarTuple, Any

print(f'{TypeVar("Plop").__default__ = }')
...

# ❯ python3.13 /tmp/tmpe9xlgjg8.py
TypeVar("Plop").__default__ = typing.NoDefault
TypeVarTuple("Plop").__default__ = typing.NoDefault
ParamSpec("Plop").__default__ = typing.NoDefault

TypeVar("Plop", default=Any).__default__ = typing.Any
TypeVarTuple("Plop", default=Any).__default__ = typing.Any
ParamSpec("Plop", default=Any).__default__ = typing.Any

But this behaviour is not implemented in stubs, Ellipsis is used instead of typing.NoDefault which is already in stubs. I guess it could be fixed without breaking risks.

diff --git a/stdlib/typing.pyi b/stdlib/typing.pyi
index f04b2d858..a84a517cc 100644
--- a/stdlib/typing.pyi
+++ b/stdlib/typing.pyi
@@ -161,7 +161,7 @@ class TypeVar:
             contravariant: bool = False,
             covariant: bool = False,
             infer_variance: bool = False,
-            default: Any = ...,
+            default: Any = NoDefault,
         ) -> None: ...
     elif sys.version_info >= (3, 12):
         def __init__(
@@ -231,7 +231,7 @@ if sys.version_info >= (3, 11):
             def __default__(self) -> Any: ...
             def has_default(self) -> bool: ...
         if sys.version_info >= (3, 13):
-            def __init__(self, name: str, *, default: Any = ...) -> None: ...
+            def __init__(self, name: str, *, default: Any = NoDefault) -> None: ...
         else:
             def __init__(self, name: str) -> None: ...
 
@@ -279,7 +279,7 @@ if sys.version_info >= (3, 10):
                 contravariant: bool = False,
                 covariant: bool = False,
                 infer_variance: bool = False,
-                default: Any = ...,
+                default: Any = NoDefault,
             ) -> None: ...
         elif sys.version_info >= (3, 12):
             def __init__(

Ps :

print(f'{TypeVar("Plop").__default__ is TypeVarTuple("Plop").__default__ is ParamSpec("Plop").__default__ = }')
❯ python3.13 /tmp/tmpe9xlgjg8.py
TypeVar("Plop").__default__ is TypeVarTuple("Plop").__default__ is ParamSpec("Plop").__default__ = True
TypeVar("Plop").__default__ is Ellipsis = False

yoann9344 avatar Jun 06 '24 09:06 yoann9344

#11990

yoann9344 avatar Jun 07 '24 09:06 yoann9344

Sounds right, PR welcome!

JelleZijlstra avatar Jun 07 '24 11:06 JelleZijlstra

Unfortunately flake8 complains with this suggested change:

stdlib/typing.pyi:164:28: Y011 Only simple default values allowed for typed arguments
stdlib/typing.pyi:164:28: F821 undefined name 'NoDefault'
stdlib/typing.pyi:234:61: Y011 Only simple default values allowed for typed arguments
stdlib/typing.pyi:234:61: F821 undefined name 'NoDefault'
stdlib/typing.pyi:282:32: Y011 Only simple default values allowed for typed arguments
stdlib/typing.pyi:282:32: F821 undefined name 'NoDefault'

The F821 errors can be fixed by just moving the definition for NoDefault in the typing.pyi stub above the definition for TypeVar. For Y011, we could possibly carve out a special case over at flake8-pyi, which the typeshed team maintains -- though I don't know if it's worth it for three default arguments in typing.pyi.

AlexWaygood avatar Jun 08 '24 17:06 AlexWaygood