Skip to content

Commit 8f21089

Browse files
authored
PEP 827: Split whether a param has a default out from "quals" (#4979)
* 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. * 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.
1 parent 0d6797e commit 8f21089

1 file changed

Lines changed: 42 additions & 26 deletions

File tree

peps/pep-0827.rst

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Status: Draft
88
Type: Standards Track
99
Topic: Typing
1010
Created: 27-Feb-2026
11-
Python-Version: 3.15
11+
Python-Version: 3.16
1212
Post-History: 02-Mar-2026
1313

1414

@@ -376,18 +376,39 @@ this PEP.
376376

377377
We introduce a ``Param`` type that contains all the information about a function param::
378378

379-
class Param[N: str | None, T, Q: ParamQuals = typing.Never]:
379+
class Param[
380+
N: str | None,
381+
T,
382+
K: ParamKind = Literal["positional_or_keyword"],
383+
D = typing.Never,
384+
]:
380385
pass
381386

382-
ParamQuals = typing.Literal["*", "**", "default", "keyword"]
387+
ParamKind = typing.Literal[
388+
"*", "**", "keyword", "positional", "positional_or_keyword"
389+
]
383390

384-
type PosParam[N: str | None, T] = Param[N, T, Literal["positional"]]
385-
type PosDefaultParam[N: str | None, T] = Param[N, T, Literal["positional", "default"]]
386-
type DefaultParam[N: str, T] = Param[N, T, Literal["default"]]
391+
type PosParam[T] = Param[None, T, Literal["positional"]]
392+
type PosDefaultParam[T] = Param[None, T, Literal["positional"], T]
393+
type DefaultParam[N: str, T] = Param[N, T, Literal["positional_or_keyword"], T]
387394
type NamedParam[N: str, T] = Param[N, T, Literal["keyword"]]
388-
type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword", "default"]]
389-
type ArgsParam[T] = Param[Literal[None], T, Literal["*"]]
390-
type KwargsParam[T] = Param[Literal[None], T, Literal["**"]]
395+
type NamedDefaultParam[N: str, T] = Param[N, T, Literal["keyword"], T]
396+
type ArgsParam[T] = Param[None, T, Literal["*"]]
397+
type KwargsParam[T] = Param[None, T, Literal["**"]]
398+
399+
400+
The argument ``K``, of type ``ParamKind``, represents the parameter
401+
kind of the parameter, and defaults to the ordinary
402+
``Literal["positional_or_keyword"]``. It is an error to create
403+
``Callable`` with a ``Param`` containing multiple kinds unioned
404+
together.
405+
406+
The argument ``D`` carries the type of the parameter's default, if one
407+
exists, and is ``Never`` otherwise. When the default value is a
408+
literal (e.g. ``None``, an int, a string, an enum member), ``D`` may
409+
be a ``Literal`` carrying that value. (Having it be such a ``Literal``
410+
has no effect other than to make it available to introspection and
411+
potentially for diagnostics.)
391412

392413
We also introduce a ``Params`` type that wraps a sequence of ``Param``
393414
types, serving as the first argument to ``Callable``::
@@ -419,17 +440,18 @@ as::
419440
Params[
420441
Param[Literal["a"], int, Literal["positional"]],
421442
Param[Literal["b"], int],
422-
Param[Literal["c"], int, Literal["default"]],
443+
Param[Literal["c"], int, Literal["positional_or_keyword"], Literal[0]],
423444
Param[None, int, Literal["*"]],
424445
Param[Literal["d"], int, Literal["keyword"]],
425-
Param[Literal["e"], int, Literal["default", "keyword"]],
446+
Param[Literal["e"], int, Literal["keyword"], Literal[0]],
426447
Param[None, int, Literal["**"]],
427448
],
428449
int,
429450
]
430451

431452

432-
or, using the type abbreviations we provide::
453+
or, using the type abbreviations we provide (though this version will not track
454+
specific values for the defaults)::
433455

434456
Callable[
435457
Params[
@@ -793,8 +815,9 @@ Callable inspection and creation
793815
``Callable`` types always have their arguments exposed in the extended
794816
Callable format discussed above.
795817

796-
The names, type, and qualifiers share associated type names with
797-
``Member`` (``.name``, ``.type``, and ``.quals``).
818+
The name and type associated type names with ``Member`` (``.name`` and
819+
``.type``). ``Param`` also has a ``.kind`` associated type, which
820+
exposes ``K`` and a ``.default`` associated type, which exposes ``D``.
798821

799822
.. _pep827-generic-callable:
800823

@@ -1101,13 +1124,10 @@ based on iterating over all attributes.
11011124
p.name,
11021125
p.type,
11031126
# All arguments are keyword-only
1104-
# It takes a default if a default is specified in the class
1105-
Literal["keyword"]
1106-
if typing.IsAssignable[
1107-
GetDefault[p.init],
1108-
Never,
1109-
]
1110-
else Literal["keyword", "default"],
1127+
Literal["keyword"],
1128+
# GetDefault is Never when there's no default, so use it
1129+
# directly as D.
1130+
GetDefault[p.init],
11111131
]
11121132
for p in typing.Iter[typing.Attrs[T]]
11131133
],
@@ -1343,7 +1363,7 @@ functions with explicit generic annotations. For old-style generics,
13431363
we'll probably have to try to evaluate it and then raise an error when
13441364
we encounter a variable.)
13451365

1346-
With our real syntax, this look likes::
1366+
With our real syntax, this looks like::
13471367

13481368
type Foo = NewProtocol[
13491369
Member[
@@ -1888,10 +1908,6 @@ Open Issues
18881908
would be to mirror ``inspect.Signature`` more directly, and have an enum
18891909
with names like ``ParamKind.POSITIONAL_OR_KEYWORD``. Would that be better?
18901910

1891-
A related potential change would be to fully separate the kind from whether
1892-
there is a default, and have whether there is a default represented in
1893-
an ``init`` field, like we do for class member initializers with ``Member``.
1894-
18951911
* :ref:`Members <pep827-members>`: Should ``Members`` return all
18961912
methods, even those without annotations? We excluded them out of the
18971913
desire for some consistency with attributes, but it would not be

0 commit comments

Comments
 (0)