Proposal: Support Unpacked `TypeVarTuple` and `tuple` in `Concatenate`
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- python
- Ambito
- devtools, documentation
Direzione di ricerca
Inizia dalla sezione collegata della specifica di typing relativa alle posizioni d’uso valide, quindi confronta la grammatica e la semantica proposte con il comportamento di PEP 646 e PEP 612 descritto qui. Il lavoro è completato quando è stata selezionata e documentata una regola per suddividere il prefisso spacchettato di ParamSpec P, inclusi i casi non ancorato e non vincolato.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Abstract
PEP 612 introduced ParamSpec and Concatenate to prepend fixed positional parameters to a callable's signature. PEP 646 introduced TypeVarTuple for variadic positional typing in Callable[[*Ts], R]. Because the two PEPs were developed independently, the typing specification does not allow unpacked types (*Ts or *tuple[...]) inside Concatenate.
This proposal extends Concatenate to accept unpack_expressions in its prefix, enabling Callable[Concatenate[*Ts, P], R].
Motivation
Higher-order abstractions like partial application helpers and execution wrappers need to capture an arbitrary number of leading positional arguments while preserving the remaining signature (keyword-only params, defaults, **kwargs) via ParamSpec. Today, this requires repetitive overload ladders:
from typing import Any, Callable, Concatenate, overload
class Wrapper[**P, R]:
@overload
def __init__(self, fn: Callable[P, R]) -> None: ...
@overload
def __init__[G1](
self,
fn: Callable[Concatenate[G1, P], R],
__g1: G1,
/,
) -> None: ...
@overload
def __init__[G1, G2](
self,
fn: Callable[Concatenate[G1, G2, P], R],
__g1: G1,
__g2: G2,
/,
) -> None: ...
# Must repeat up to arbitrary maximum arity...
def __init__(self, fn: Callable[..., R], *args: Any) -> None:
self._fn = fn
self._args = args
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
return self._fn(*self._args, *args, **kwargs)
With this proposal, the entire ladder collapses to a single generic signature:
from __future__ import annotations
from typing import Callable, Concatenate
class Wrapper[**P, R, *Ts]:
def __init__(self, fn: Callable[Concatenate[*Ts, P], R], *args: *Ts) -> None:
self._fn = fn
self._args = args
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
return self._fn(*self._args, *args, **kwargs)
def f(a: str, b: int, *, flag: bool = False, x: float) -> bool: ...
# Ts = () -> P = (a: str, b: int, *, flag: bool = ..., x: float)
w0 = Wrapper(f)
r0 = w0("hello", 42, x=3.14, flag=True) # type: bool
# Ts = (str,) -> P = (b: int, *, flag: bool = ..., x: float)
w1 = Wrapper(f, "hello")
r1 = w1(42, x=3.14) # type: bool
# Ts = (str, int) -> P = (*, flag: bool = ..., x: float)
w2 = Wrapper(f, "hello", 42)
r2 = w2(x=3.14) # type: bool
Specification
Grammar
Update the Concatenate grammar in the typing specification from:
concatenate ::= "Concatenate" "[" type_expression ("," type_expression)* "," parameter_specification_variable "]"
to:
concatenate_prefix_item ::= type_expression | unpack_expression
concatenate ::= "Concatenate" "[" concatenate_prefix_item ("," concatenate_prefix_item)* "," parameter_specification_variable "]"
Semantics
Expansion follows existing PEP 646 semantics: when *Ts is bound to tuple[T1, T2, ..., Tn], Concatenate[*Ts, P] is equivalent to Concatenate[T1, T2, ..., Tn, P]. When *Ts is bound to tuple[()], Concatenate[*Ts, P] simplifies to P. Individual type expressions and unpack expressions may be freely combined in the prefix (e.g. Concatenate[LeadingArg, *Ts, P]).
Open Question: Splitting Boundary
The core design question is: how does a type checker determine the split between the prefix and ParamSpec P?
When the prefix length is statically known, splitting is unambiguous. This covers concrete bounded tuples (Concatenate[*tuple[int, str], P] — always length 2) and value-anchored TypeVarTuples where a companion *args: *Ts pins the length at the call site (the Wrapper example above). These are the primary use cases.
Ambiguity arises when the prefix length is not statically determined:
Case A — Unanchored *Ts (no companion *args: *Ts):
class TaskRunner[**P, R, *Ts]:
def __init__(self, fn: Callable[Concatenate[*Ts, P], R]) -> None: ...
def compute(user_id: int, query: str, *, timeout: float = 5.0) -> bool: ...
# How many positional params should *Ts capture vs. leave in P?
task = TaskRunner(compute)
Case B — Unbounded tuple (*tuple[T, ...]):
def strip_leading_ints[**P, R](
fn: Callable[Concatenate[*tuple[int, ...], P], R]
) -> Callable[P, R]: ...
def example(x: int, y: int, z: int, *, flag: bool = False) -> None: ...
# *tuple[int, ...] could match 0, 1, 2, or 3 leading int parameters.
wrapped = strip_leading_ints(example)
Options:
-
Option 1 — Greedy prefix: The prefix consumes all matching positional-capable parameters. In Case A,
*Ts = (int, str)andP = (*, timeout: float = 5.0). In Case B, all 3 ints are consumed, leavingP = (*, flag: bool = False). -
Option 2 — Restrict to fixed-length prefixes initially: Require the prefix length to be statically determined (concrete bounded tuples, companion
*args: *Ts, or explicit specialization). Reject unanchored/unbounded prefixes as ambiguous and defer them to a future extension.
- Lingua principale
- Python
- Stelle
- 1.8k
- Fork
- 302
- Merge medio
- 23h
- PR unite (30g)
- 8
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di python/typing
-
topic: typing spec
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
topic: typing spec
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
-
topic: documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
-
topic: documentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
-
topic: conformance tests topic: typing spec
Difficoltà 3/5 1-2 giorni Idoneità per principianti 72/100
Tutte le issue di python/typing
Issue simili
-
Add: hunch Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
AbdelStark/awesome-typesafe#104 ·
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
DiamondLightSource/dodal#2211 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
openml/openml-python#1749 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
sipyourdrink-ltd/bernstein#6191 ·