Proposal: Support Unpacked `TypeVarTuple` and `tuple` in `Concatenate`
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
- 難度
- 5/5
- 預估耗時
- 一週以上
- 新手友好度
- 35/100
- Issue 類型
- 功能
- 描述清晰度
- 基本清楚
- 活躍度
- 冷清
- 技術堆疊
- python
研究方向
先從連結的 typing 規範中關於有效使用位置的部分開始,然後將提議的語法和語意與此處描述的 PEP 646 和 PEP 612 行為進行比較。完成的標準是選擇並記錄一條用於拆分 ParamSpec P 的解包前綴的規則,其中包括未錨定和無界的情況。
由索引模型根據 Issue 內容生成。
描述
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.
- 主要語言
- Python
- 星號
- 1.8k
- 分支
- 302
- 平均合併
- 4 小時 16 分鐘
- 30 天內合併 PR
- 7
環境準備
這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
python/typing 的其他 Issue
-
topic: typing spec
難度 2/5 1-3 小時 新手友好度 72/100
python/typing#2356 · 2 則留言 · 1 個 reaction ·
維護者通常 1 天內回覆
-
topic: typing spec
難度 2/5 1-3 小時 新手友好度 75/100
維護者通常 1 天內回覆
-
Update reference/generics.html with PEP695 type parameter syntax可能已有人在做 @BHUVANSH855 於 181 天前認領。 未關閉topic: documentation
難度 2/5 1-3 小時 新手友好度 76/100
維護者通常 1 天內回覆
-
topic: documentation
難度 2/5 1-3 小時 新手友好度 65/100
python/typing#2150 · 2 則留言 · 2 個 reaction ·
維護者通常 1 天內回覆
-
Finals and Literals: Spec contradiction可能已有人在做 @srittau 於 34 天前認領。 未關閉topic: conformance tests topic: typing spec
難度 3/5 1-2 天 新手友好度 72/100
維護者通常 1 天內回覆
相似的 Issue
-
jobs restart --verify: hotlane restart timeout (10s) shorter than its shutdown (~30s) → false exit 1未關閉
難度 2/5 1-3 小時 新手友好度 85/100
EtanHey/brainlayer#1072 ·
維護者通常 1 天內回覆
-
難度 1/5 1 小時以內 新手友好度 88/100
維護者通常 1 天內回覆
-
_apply_doc_content: a cancel during the shielded image gather still sends the content batchUpdate未關閉enhancement
難度 2/5 1 小時以內 新手友好度 80/100
khuisman/mcp-gee-sweet#904 ·
維護者通常 1 天內回覆
-
難度 1/5 1 小時以內 新手友好度 70/100
-
CORE-HIST-005/006 multi-turn history tests send three different messages with the same messageId未關閉
難度 2/5 1-3 小時 新手友好度 85/100
a2aproject/a2a-tck#248 ·