Skip to content
14 changes: 14 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2292,6 +2292,20 @@ without the dedicated syntax, as documented below.

.. versionadded:: 3.14

.. rubric:: Unpacking

Type aliases support star unpacking using the ``*Alias`` syntax.
This is equivalent to using ``Unpack[Alias]`` directly:

.. doctest::

>>> type Alias = tuple[int, str]
>>> type Unpacked = tuple[bool, *Alias]
>>> Unpacked.__value__
tuple[bool, typing.Unpack[Alias]]

.. versionadded:: next


Other special directives
""""""""""""""""""""""""
Expand Down
2 changes: 2 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,8 @@ typing
* Remove :class:`!typing.ByteString`. It had previously raised a
:exc:`DeprecationWarning` since Python 3.12.

* :class:`typing.TypeAliasType` now supports star unpacking.

urllib
------

Expand Down
13 changes: 12 additions & 1 deletion Lib/test/test_type_aliases.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from test.typinganndata import mod_generics_cache

from typing import (
Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, get_args,
Callable, TypeAliasType, TypeVar, TypeVarTuple, ParamSpec, Unpack, get_args,
)


Expand Down Expand Up @@ -317,6 +317,17 @@ def test_module(self):
self.assertEqual(mod_generics_cache.OldStyle.__module__,
mod_generics_cache.__name__)

def test_unpack(self):
type Alias = tuple[int, int]
unpacked = (*Alias,)[0]
self.assertEqual(unpacked, Unpack[Alias])

class Foo[*Ts]:
pass

x = Foo[str, *Alias]
self.assertEqual(x.__args__, (str, Unpack[Alias]))


# All these type aliases are used for pickling tests:
T = TypeVar('T')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:class:`typing.TypeAliasType` now supports star unpacking.
16 changes: 10 additions & 6 deletions Objects/typevarobject.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TypeVar, TypeVarTuple, and ParamSpec
// TypeVar, TypeVarTuple, TypeAlias and ParamSpec
#include "Python.h"
#include "pycore_object.h" // _PyObject_GC_TRACK/UNTRACK, PyAnnotateFormat
#include "pycore_typevarobject.h"
Expand Down Expand Up @@ -59,6 +59,9 @@ typedef struct {

#include "clinic/typevarobject.c.h"

#define typevartuple_iter unpack_iter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need these defines, just use unpack_iter directly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a suggestion from @ZeroIntensity to keep the naming more consistent. I can remove them though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you remove them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was complaining originally about the naming inconsistency, but in hindsight, it's probably better than the extra #define.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove them (and fix the conflict) later today :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!

#define typealias_iter unpack_iter

/* NoDefault is a marker object to indicate that a parameter has no default. */

static PyObject *
Expand Down Expand Up @@ -385,7 +388,7 @@ caller(void)
}

static PyObject *
typevartuple_unpack(PyObject *tvt)
unpack(PyObject *self)
{
PyObject *typing = PyImport_ImportModule("typing");
if (typing == NULL) {
Expand All @@ -396,7 +399,7 @@ typevartuple_unpack(PyObject *tvt)
Py_DECREF(typing);
return NULL;
}
PyObject *unpacked = PyObject_GetItem(unpack, tvt);
PyObject *unpacked = PyObject_GetItem(unpack, self);
Py_DECREF(typing);
Py_DECREF(unpack);
return unpacked;
Expand Down Expand Up @@ -431,7 +434,7 @@ unpack_typevartuples(PyObject *params)
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *param = PyTuple_GET_ITEM(params, i);
if (Py_IS_TYPE(param, tp)) {
PyObject *unpacked = typevartuple_unpack(param);
PyObject *unpacked = unpack(param);
if (unpacked == NULL) {
Py_DECREF(new_params);
return NULL;
Expand Down Expand Up @@ -1505,9 +1508,9 @@ typevartuple_dealloc(PyObject *self)
}

static PyObject *
typevartuple_iter(PyObject *self)
unpack_iter(PyObject *self)
{
PyObject *unpacked = typevartuple_unpack(self);
PyObject *unpacked = unpack(self);
if (unpacked == NULL) {
return NULL;
}
Expand Down Expand Up @@ -2134,6 +2137,7 @@ PyTypeObject _PyTypeAlias_Type = {
.tp_new = typealias_new,
.tp_free = PyObject_GC_Del,
.tp_traverse = (traverseproc)typealias_traverse,
.tp_iter = typealias_iter,
.tp_clear = (inquiry)typealias_clear,
.tp_repr = typealias_repr,
.tp_as_number = &typealias_as_number,
Expand Down
Loading