From a12dfc36267ca908e9c21178717c25facdb61f78 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Tue, 19 May 2026 21:21:24 -0700 Subject: [PATCH 1/2] PEP 827: Split whether a param has a default out from "quals" Q/.quals becomes K/.kind, and we add D/.default to track whether there is a default. It should be the type of the default if it exists, otherwise `Never`. Tracking the type here allows specifying a `Literal` in order to track a specific value, similar to what we support for `Member`. There is a possible follow-up to use an enum for the `ParamKind` instead of string literals but I'm not certain about it yet. --- peps/pep-0827.rst | 66 ++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index 60706edc894..b61c75c918f 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -8,7 +8,7 @@ Status: Draft Type: Standards Track Topic: Typing Created: 27-Feb-2026 -Python-Version: 3.15 +Python-Version: 3.16 Post-History: 02-Mar-2026 @@ -376,18 +376,37 @@ this PEP. We introduce a ``Param`` type that contains all the information about a function param:: - class Param[N: str | None, T, Q: ParamQuals = typing.Never]: + class Param[ + N: str | None, + T, + K: ParamKind = typing.Never, + D = typing.Never, + ]: pass - ParamQuals = typing.Literal["*", "**", "default", "keyword"] + ParamKind = typing.Literal["*", "**", "keyword", "positional"] - type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]] - type PosDefaultParam[N: str | None, T] = Param[N, T, Literal["positional", "default"]] - type DefaultParam[N: str, T] = Param[N, T, Literal["default"]] + type PosParam[T] = Param[None, T, Literal["positional"]] + type PosDefaultParam[T] = Param[None, T, Literal["positional"], T] + type DefaultParam[N: str, T] = Param[N, T, typing.Never, T] type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]] - type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]] - type ArgsParam[T] = Param[Literal[None], T, Literal["*"]] - type KwargsParam[T] = Param[Literal[None], T, Literal["**"]] + type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword"], T] + type ArgsParam[T] = Param[None, T, Literal["*"]] + type KwargsParam[T] = Param[None, T, Literal["**"]] + + +The argument ``K``, of type ``ParamKind``, represents the parameter +kind of the parameter. The default "positional or named" kind of +argument is represented as ``Never``. It is an error to create +``Callable`` with a ``Param`` containing multiple kinds unioned +together. + +The argument ``D`` carries the type of the parameter's default, if one +exists, and is ``Never`` otherwise. When the default value is a +literal (e.g. ``None``, an int, a string, an enum member), ``D`` may +be a ``Literal`` carrying that value. (Having it be such a ``Literal`` +has no effect other than to make it available to introspection and +potentially for diagnostics.) We also introduce a ``Params`` type that wraps a sequence of ``Param`` types, serving as the first argument to ``Callable``:: @@ -419,17 +438,18 @@ as:: Params[ Param[Literal["a"], int, Literal["positional"]], Param[Literal["b"], int], - Param[Literal["c"], int, Literal["default"]], + Param[Literal["c"], int, typing.Never, Literal[0]], Param[None, int, Literal["*"]], Param[Literal["d"], int, Literal["keyword"]], - Param[Literal["e"], int, Literal["default", "keyword"]], + Param[Literal["e"], int, Literal["keyword"], Literal[0]], Param[None, int, Literal["**"]], ], int, ] -or, using the type abbreviations we provide:: +or, using the type abbreviations we provide (though this version will not track +specific values for the defaults):: Callable[ Params[ @@ -793,8 +813,9 @@ Callable inspection and creation ``Callable`` types always have their arguments exposed in the extended Callable format discussed above. -The names, type, and qualifiers share associated type names with -``Member`` (``.name``, ``.type``, and ``.quals``). +The name and type associated type names with ``Member`` (``.name`` and +``.type``). ``Param`` also has a ``.kind`` associated type, which +exposes ``K`` and a ``.default`` associated type, which exposes ``D``. .. _pep827-generic-callable: @@ -1101,13 +1122,10 @@ based on iterating over all attributes. p.name, p.type, # All arguments are keyword-only - # It takes a default if a default is specified in the class - Literal["keyword"] - if typing.IsAssignable[ - GetDefault[p.init], - Never, - ] - else Literal["keyword", "default"], + Literal["keyword"], + # GetDefault is Never when there's no default, so use it + # directly as D. + GetDefault[p.init], ] for p in typing.Iter[typing.Attrs[T]] ], @@ -1343,7 +1361,7 @@ functions with explicit generic annotations. For old-style generics, we'll probably have to try to evaluate it and then raise an error when we encounter a variable.) -With our real syntax, this look likes:: +With our real syntax, this looks like:: type Foo = NewProtocol[ Member[ @@ -1888,10 +1906,6 @@ Open Issues would be to mirror ``inspect.Signature`` more directly, and have an enum with names like ``ParamKind.POSITIONAL_OR_KEYWORD``. Would that be better? - A related potential change would be to fully separate the kind from whether - there is a default, and have whether there is a default represented in - an ``init`` field, like we do for class member initializers with ``Member``. - * :ref:`Members `: Should ``Members`` return all methods, even those without annotations? We excluded them out of the desire for some consistency with attributes, but it would not be From 554887a614a17459529aef2df77a6c3ba447c638 Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 22 May 2026 16:17:17 -0700 Subject: [PATCH 2/2] switch to using a positional_or_keyword string There is going to be a follow-up that makes all of these into enums. For reasons I now regret I decided to split the change. --- peps/pep-0827.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/peps/pep-0827.rst b/peps/pep-0827.rst index b61c75c918f..3f4bf714c3a 100644 --- a/peps/pep-0827.rst +++ b/peps/pep-0827.rst @@ -379,16 +379,18 @@ We introduce a ``Param`` type that contains all the information about a function class Param[ N: str | None, T, - K: ParamKind = typing.Never, + K: ParamKind = Literal["positional_or_keyword"], D = typing.Never, ]: pass - ParamKind = typing.Literal["*", "**", "keyword", "positional"] + ParamKind = typing.Literal[ + "*", "**", "keyword", "positional", "positional_or_keyword" + ] type PosParam[T] = Param[None, T, Literal["positional"]] type PosDefaultParam[T] = Param[None, T, Literal["positional"], T] - type DefaultParam[N: str, T] = Param[N, T, typing.Never, T] + type DefaultParam[N: str, T] = Param[N, T, Literal["positional_or_keyword"], T] type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]] type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword"], T] type ArgsParam[T] = Param[None, T, Literal["*"]] @@ -396,8 +398,8 @@ We introduce a ``Param`` type that contains all the information about a function The argument ``K``, of type ``ParamKind``, represents the parameter -kind of the parameter. The default "positional or named" kind of -argument is represented as ``Never``. It is an error to create +kind of the parameter, and defaults to the ordinary +``Literal["positional_or_keyword"]``. It is an error to create ``Callable`` with a ``Param`` containing multiple kinds unioned together. @@ -438,7 +440,7 @@ as:: Params[ Param[Literal["a"], int, Literal["positional"]], Param[Literal["b"], int], - Param[Literal["c"], int, typing.Never, Literal[0]], + Param[Literal["c"], int, Literal["positional_or_keyword"], Literal[0]], Param[None, int, Literal["*"]], Param[Literal["d"], int, Literal["keyword"]], Param[Literal["e"], int, Literal["keyword"], Literal[0]],