Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Doc/howto/free-threading-extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,9 @@ C API extensions need to be built specifically for the free-threaded build.
The wheels, shared libraries, and binaries are indicated by a ``t`` suffix.

* `pypa/manylinux <https://github.com/pypa/manylinux>`_ supports the
free-threaded build, with the ``t`` suffix, such as ``python3.13t``.
* `pypa/cibuildwheel <https://github.com/pypa/cibuildwheel>`_ supports the
free-threaded build on Python 3.13 and 3.14. On Python 3.14, free-threaded
wheels will be built by default. On Python 3.13, you will need to set
`CIBW_ENABLE to cpython-freethreading <https://cibuildwheel.pypa.io/en/stable/options/#enable>`_.
free-threaded build, with the ``t`` suffix, such as ``python3.14t``.
* `pypa/cibuildwheel <https://github.com/pypa/cibuildwheel>`_ supports
building wheels for the free-threaded build of Python 3.14 and newer.

Limited C API and Stable ABI
............................
Expand Down
8 changes: 4 additions & 4 deletions Doc/library/dataclasses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ Module contents
Converts the dataclass *obj* to a dict (by using the
factory function *dict_factory*). Each dataclass is converted
to a dict of its fields, as ``name: value`` pairs. dataclasses, dicts,
lists, and tuples are recursed into. Other objects are copied with
:func:`copy.deepcopy`.
frozendicts, lists, and tuples are recursed into. Other objects are copied
with :func:`copy.deepcopy`.

Example of using :func:`!asdict` on nested dataclasses::

Expand Down Expand Up @@ -402,8 +402,8 @@ Module contents

Converts the dataclass *obj* to a tuple (by using the
factory function *tuple_factory*). Each dataclass is converted
to a tuple of its field values. dataclasses, dicts, lists, and
tuples are recursed into. Other objects are copied with
to a tuple of its field values. dataclasses, dicts, frozendicts, lists,
and tuples are recursed into. Other objects are copied with
:func:`copy.deepcopy`.

Continuing from the previous example::
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/re.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1953,7 +1953,7 @@ successive matches::

class Token(NamedTuple):
type: str
value: str
value: int | float | str
line: int
column: int

Expand Down
34 changes: 19 additions & 15 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1174,7 +1174,8 @@ These can be used as types in annotations. They all support subscription using
or transforms parameters of another
callable. Usage is in the form
``Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable]``. ``Concatenate``
is currently only valid when used as the first argument to a :ref:`Callable <annotating-callables>`.
is valid when used in :ref:`Callable <annotating-callables>` type hints
and when instantiating user-defined generic classes with :class:`ParamSpec` parameters.
The last parameter to ``Concatenate`` must be a :class:`ParamSpec` or
ellipsis (``...``).

Expand Down Expand Up @@ -3452,13 +3453,13 @@ Functions and decorators
Introspection helpers
---------------------

.. function:: get_type_hints(obj, globalns=None, localns=None, include_extras=False)
.. function:: get_type_hints(obj, globalns=None, localns=None, include_extras=False, *, format=Format.VALUE)

Return a dictionary containing type hints for a function, method, module,
class object, or other callable object.

This is often the same as ``obj.__annotations__``, but this function makes
the following changes to the annotations dictionary:
This is often the same as :func:`annotationlib.get_annotations`, but this
function makes the following changes to the annotations dictionary:

* Forward references encoded as string literals or :class:`ForwardRef`
objects are handled by evaluating them in *globalns*, *localns*, and
Expand All @@ -3472,29 +3473,28 @@ Introspection helpers
annotations from ``C``'s base classes with those on ``C`` directly. This
is done by traversing :attr:`C.__mro__ <type.__mro__>` and iteratively
combining
``__annotations__`` dictionaries. Annotations on classes appearing
earlier in the :term:`method resolution order` always take precedence over
annotations on classes appearing later in the method resolution order.
:term:`annotations <variable annotation>` of each base class. Annotations
on classes appearing earlier in the :term:`method resolution order` always
take precedence over annotations on classes appearing later in the method
resolution order.
* The function recursively replaces all occurrences of
``Annotated[T, ...]``, ``Required[T]``, ``NotRequired[T]``, and ``ReadOnly[T]``
with ``T``, unless *include_extras* is set to ``True`` (see
:class:`Annotated` for more information).

See also :func:`annotationlib.get_annotations`, a lower-level function that
returns annotations more directly.

.. caution::

This function may execute arbitrary code contained in annotations.
See :ref:`annotationlib-security` for more information.

.. note::

If any forward references in the annotations of *obj* are not resolvable
or are not valid Python code, this function will raise an exception
such as :exc:`NameError`. For example, this can happen with imported
:ref:`type aliases <type-aliases>` that include forward references,
or with names imported under :data:`if TYPE_CHECKING <TYPE_CHECKING>`.
If :attr:`Format.VALUE <annotationlib.Format.VALUE>` is used and any
forward references in the annotations of *obj* are not resolvable, a
:exc:`NameError` exception is raised. For example, this can happen
with names imported under :data:`if TYPE_CHECKING <TYPE_CHECKING>`.
More generally, any kind of exception can be raised if an annotation
contains invalid Python code.

.. note::

Expand All @@ -3512,6 +3512,10 @@ Introspection helpers
if a default value equal to ``None`` was set.
Now the annotation is returned unchanged.

.. versionchanged:: 3.14
Added the ``format`` parameter. See the documentation on
:func:`annotationlib.get_annotations` for more information.

.. versionchanged:: 3.14
Calling :func:`get_type_hints` on instances is no longer supported.
Some instances were accepted in earlier versions as an undocumented
Expand Down
10 changes: 6 additions & 4 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1496,7 +1496,8 @@ class C:
If given, 'dict_factory' will be used instead of built-in dict.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
tuples, lists, dicts, and frozendicts. Other objects are copied
with 'copy.deepcopy()'.
"""
if not _is_dataclass_instance(obj):
raise TypeError("asdict() should be called on dataclass instances")
Expand Down Expand Up @@ -1552,7 +1553,7 @@ def _asdict_inner(obj, dict_factory):
return obj_type(*[_asdict_inner(v, dict_factory) for v in obj])
else:
return obj_type(_asdict_inner(v, dict_factory) for v in obj)
elif issubclass(obj_type, dict):
elif issubclass(obj_type, (dict, frozendict)):
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
# dict as it requires the default_factory as its first arg.
Expand Down Expand Up @@ -1587,7 +1588,8 @@ class C:
If given, 'tuple_factory' will be used instead of built-in tuple.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
tuples, lists, dicts, and frozendicts. Other objects are copied
with 'copy.deepcopy()'.
"""

if not _is_dataclass_instance(obj):
Expand Down Expand Up @@ -1616,7 +1618,7 @@ def _astuple_inner(obj, tuple_factory):
# generator (which is not true for namedtuples, handled
# above).
return type(obj)(_astuple_inner(v, tuple_factory) for v in obj)
elif isinstance(obj, dict):
elif isinstance(obj, (dict, frozendict)):
obj_type = type(obj)
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
Expand Down
8 changes: 4 additions & 4 deletions Lib/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@

from fractions import Fraction
from decimal import Decimal
from itertools import count, groupby, repeat
from itertools import compress, count, groupby, repeat
from bisect import bisect_left, bisect_right
from math import hypot, sqrt, fabs, exp, erfc, tau, log, fsum, sumprod
from math import isfinite, isinf, pi, cos, sin, tan, cosh, asin, atan, acos
Expand Down Expand Up @@ -195,9 +195,9 @@ def fmean(data, weights=None):
n = len(data)
except TypeError:
# Handle iterators that do not define __len__().
counter = count()
total = fsum(map(itemgetter(0), zip(data, counter)))
n = next(counter)
counter = count(1)
total = fsum(compress(data, counter))
n = next(counter) - 1
else:
total = fsum(data)

Expand Down
20 changes: 17 additions & 3 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1693,17 +1693,24 @@ class GroupTuple:
class GroupDict:
id: int
users: Dict[str, User]
@dataclass
class GroupFrozenDict:
id: int
users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(asdict(gl), {'id': 0, 'users': [{'name': 'Alice', 'id': 1},
{'name': 'Bob', 'id': 2}]})
self.assertEqual(asdict(gt), {'id': 0, 'users': ({'name': 'Alice', 'id': 1},
{'name': 'Bob', 'id': 2})})
self.assertEqual(asdict(gd), {'id': 0, 'users': {'first': {'name': 'Alice', 'id': 1},
'second': {'name': 'Bob', 'id': 2}}})
expected_dict = {'id': 0, 'users': {'first': {'name': 'Alice', 'id': 1},
'second': {'name': 'Bob', 'id': 2}}}
self.assertEqual(asdict(gd), expected_dict)
self.assertEqual(asdict(gfd), expected_dict)

def test_helper_asdict_builtin_object_containers(self):
@dataclass
Expand Down Expand Up @@ -1884,14 +1891,21 @@ class GroupTuple:
class GroupDict:
id: int
users: Dict[str, User]
@dataclass
class GroupFrozenDict:
id: int
users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(astuple(gl), (0, [('Alice', 1), ('Bob', 2)]))
self.assertEqual(astuple(gt), (0, (('Alice', 1), ('Bob', 2))))
self.assertEqual(astuple(gd), (0, {'first': ('Alice', 1), 'second': ('Bob', 2)}))
d = {'first': ('Alice', 1), 'second': ('Bob', 2)}
self.assertEqual(astuple(gd), (0, d))
self.assertEqual(astuple(gfd), (0, frozendict(d)))

def test_helper_astuple_builtin_object_containers(self):
@dataclass
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6888,6 +6888,24 @@ def test_get_type_hints_wrapped_decoratored_func(self):
self.assertEqual(gth(ForRefExample.func), expects)
self.assertEqual(gth(ForRefExample.nested), expects)

def test_get_type_hints_wrapped_cycle_self(self):
# gh-146553: __wrapped__ self-reference must raise ValueError,
# not loop forever.
def f(x: int) -> str: ...
f.__wrapped__ = f
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
get_type_hints(f)

def test_get_type_hints_wrapped_cycle_mutual(self):
# gh-146553: mutual __wrapped__ cycle (a -> b -> a) must raise
# ValueError, not loop forever.
def a(): ...
def b(): ...
a.__wrapped__ = b
b.__wrapped__ = a
with self.assertRaisesRegex(ValueError, 'wrapper loop'):
get_type_hints(a)

def test_get_type_hints_annotated(self):
def foobar(x: List['X']): ...
X = Annotated[int, (1, 10)]
Expand Down
4 changes: 4 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2486,8 +2486,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
else:
nsobj = obj
# Find globalns for the unwrapped object.
seen = {id(nsobj)}
while hasattr(nsobj, '__wrapped__'):
nsobj = nsobj.__wrapped__
if id(nsobj) in seen:
raise ValueError(f'wrapper loop when unwrapping {obj!r}')
seen.add(id(nsobj))
globalns = getattr(nsobj, '__globals__', {})
if localns is None:
localns = globalns
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for :class:`frozendict` in :meth:`dataclasses.asdict` and :meth:`dataclasses.astuple`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix infinite loop in :func:`typing.get_type_hints` when ``__wrapped__``
forms a cycle. Patch by Shamil Abdulaev.
Loading