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
65 changes: 60 additions & 5 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2792,6 +2792,37 @@ types.
y: int
z: int

By default, a ``TypedDict`` is open, meaning that it may contain additional keys
at runtime beyond those defined in the class body. The *closed* class argument can
be used to control this; if ``closed=True``, the ``TypedDict`` cannot contain additional keys.

::

class ClosedPoint(TypedDict, closed=True):
x: int
y: int

class ClosedPoint3D(ClosedPoint): # type checker error: cannot add keys to a closed TypedDict
z: int

Setting ``closed=False`` explicitly requests the default open behavior. If the argument is not
passed, this state is inherited from the parent class.

In addition to being open or closed, a ``TypedDict`` can also be configured to have extra items.
If the *extra_items* class argument is set to a type, the ``TypedDict`` can contain arbitrary
additional keys, but the values of those keys must be of the specified type.

::

class ExtraItemsPoint(TypedDict, extra_items=int):
x: int
y: int

point: ExtraItemsPoint = {'x': 1, 'y': 2, 'anything': 3} # OK

The *extra_items* argument is also inherited through subclassing. It is unset
by default, and it may not be used together with the *closed* argument.

A ``TypedDict`` cannot inherit from a non-\ ``TypedDict`` class,
except for :class:`Generic`. For example::

Expand Down Expand Up @@ -2825,8 +2856,8 @@ types.
group: list[T]

A ``TypedDict`` can be introspected via annotations dicts
(see :ref:`annotations-howto` for more information on annotations best practices),
:attr:`__total__`, :attr:`__required_keys__`, and :attr:`__optional_keys__`.
(see :ref:`annotations-howto` for more information on annotations best practices)
and the following attributes:

.. attribute:: __total__

Expand Down Expand Up @@ -2896,8 +2927,6 @@ types.
``__required_keys__`` and ``__optional_keys__`` rely on may not work
properly, and the values of the attributes may be incorrect.

Support for :data:`ReadOnly` is reflected in the following attributes:

.. attribute:: __readonly_keys__

A :class:`frozenset` containing the names of all read-only keys. Keys
Expand All @@ -2912,6 +2941,14 @@ types.

.. versionadded:: 3.13

.. attribute:: __closed__

The value of the *closed* class argument. It can be ``True``, ``False``, or :data:`None`.

.. attribute:: __extra_items__

The value of the *extra_items* class argument. It can be a valid type or :data:`NoExtraItems`.

See the `TypedDict <https://typing.python.org/en/latest/spec/typeddict.html#typeddict>`_ section in the typing documentation for more examples and detailed rules.

.. versionadded:: 3.8
Expand All @@ -2931,7 +2968,10 @@ types.
Removed support for the keyword-argument method of creating ``TypedDict``\ s.

.. versionchanged:: 3.13
Support for the :data:`ReadOnly` qualifier was added.
Support for the :data:`ReadOnly` qualifier was added. See :pep:`705`.

.. versionchanged:: next
Support for the *closed* and *extra_items* class arguments was added. See :pep:`728`.


Protocols
Expand Down Expand Up @@ -3679,6 +3719,21 @@ Introspection helpers

.. versionadded:: 3.13

.. data:: NoExtraItems

A :class:`sentinel` object used to indicate that a :class:`TypedDict`
does not have the *extra_items* class argument.

.. doctest::

>>> from typing import TypedDict, NoExtraItems
>>> class Point(TypedDict):
... x: int
... y: int
...
>>> Point.__extra_items__ is NoExtraItems
True

Constant
--------

Expand Down
57 changes: 45 additions & 12 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,19 @@ Summary -- Release highlights
profiling tools <whatsnew315-profiling-package>`
* :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler
<whatsnew315-sampling-profiler>`
* :pep:`831`: :ref:`Frame pointers are enabled by default for improved
system-level observability <whatsnew315-pep831>`
* :pep:`798`: :ref:`Unpacking in comprehensions
<whatsnew315-unpacking-in-comprehensions>`
* :pep:`686`: :ref:`Python now uses UTF-8 as the default encoding
<whatsnew315-utf8-default>`
* :pep:`728`: ``TypedDict`` with typed extra items
* :pep:`728`: :ref:`TypedDict with typed extra items <whatsnew315-typeddict>`
* :pep:`747`: :ref:`Annotating type forms with TypeForm
<whatsnew315-typeform>`
* :pep:`800`: Disjoint bases in the type system
* :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
<whatsnew315-pybyteswriter>`
* :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
* :pep:`831`: :ref:`Frame pointers everywhere <whatsnew315-frame-pointers>`
* :ref:`The JIT compiler has been significantly upgraded <whatsnew315-jit>`
* :ref:`Improved error messages <whatsnew315-improved-error-messages>`
* :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter
Expand Down Expand Up @@ -376,6 +377,39 @@ available output formats, profiling modes, and configuration options.
(Contributed by Pablo Galindo and László Kiss Kollár in :gh:`135953` and :gh:`138122`.)


.. _whatsnew315-pep831:

:pep:`831`: Frame pointers enabled by default
---------------------------------------------

CPython is now built with frame pointers by default on platforms that support
them. This uses the compiler flags ``-fno-omit-frame-pointer`` and
``-mno-omit-leaf-frame-pointer``, making native stack unwinding faster and
more reliable for system profilers, debuggers, crash analysis tools, and
eBPF-based observability tools.

The flags are exposed through :mod:`sysconfig`, so extension modules built by
tools that consume Python's build configuration inherit frame pointers by
default. This propagation is intentional: mixed Python/native profiling needs
an unbroken frame-pointer chain through the interpreter, extension modules,
embedding applications, and native libraries.

.. important::

Third-party build backends and native build systems should preserve these
flags when they consume Python's :mod:`sysconfig` values. Build systems
that compile C, C++, Rust, or other native code without inheriting Python's
compiler flags should enable equivalent frame-pointer flags themselves. A
single native component built without frame pointers can break stack
unwinding for the whole Python process.

.. seealso:: :pep:`831` for further details.

(Contributed by Pablo Galindo Salgado and Savannah Ostrowski in
:gh:`149201`; PEP 831 written by Pablo Galindo Salgado, Ken Jin, and
Savannah Ostrowski.)


.. _whatsnew315-unpacking-in-comprehensions:

:pep:`798`: Unpacking in Comprehensions
Expand Down Expand Up @@ -1487,6 +1521,15 @@ typing

(Contributed by Jelle Zijlstra in :gh:`145033`.)

.. _whatsnew315-typeddict:

* :pep:`728`: Add support in :class:`~typing.TypedDict` for the *closed*
and *extra_items* class arguments. A closed :class:`~typing.TypedDict`
does not allow extra keys beyond those specified in the class body, while
a :class:`~typing.TypedDict` with ``extra_items`` allows arbitrary extra
items where the values are of the specified type. (Contributed by Angela
Liss in :gh:`137840`.)

* Code like ``class ExtraTypeVars(P1[S], Protocol[T, T2]): ...`` now raises
a :exc:`TypeError`, because ``S`` is not listed in ``Protocol`` parameters.
(Contributed by Nikita Sobolev in :gh:`137191`.)
Expand Down Expand Up @@ -2378,16 +2421,6 @@ Build changes
and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode <debug-build>`.
(Contributed by Donghee Na in :gh:`141770`.)

.. _whatsnew315-frame-pointers:

* CPython is now built with frame pointers enabled by default
(:pep:`831`). Pass :option:`--without-frame-pointers` to opt out.
Authors of C extensions and native libraries built with custom build
systems should add ``-fno-omit-frame-pointer`` and
``-mno-omit-leaf-frame-pointer`` to their own ``CFLAGS`` to keep the
unwind chain intact.
(Contributed by Pablo Galindo Salgado and Savannah Ostrowski in :gh:`149201`.)

.. _whatsnew315-windows-tail-calling-interpreter:

* 64-bit builds using Visual Studio 2026 (MSVC 18) may now use the new
Expand Down
81 changes: 61 additions & 20 deletions Lib/_pyrepl/_module_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from dataclasses import dataclass
from itertools import chain
from tokenize import TokenInfo
from .fancycompleter import safe_getattr

TYPE_CHECKING = False

Expand Down Expand Up @@ -71,41 +72,69 @@ def __init__(self, namespace: Mapping[str, Any] | None = None) -> None:
self._curr_sys_path: list[str] = sys.path[:]
self._stdlib_path = os.path.dirname(importlib.__path__[0])

def get_completions(self, line: str) -> tuple[list[str], CompletionAction | None] | None:
def get_completions(
self, line: str, *, include_values: bool = True
) -> tuple[list[str], list[Any], CompletionAction | None] | None:
"""Return the next possible import completions for 'line'.

For attributes completion, if the module to complete from is not
imported, also return an action (prompt + callback to run if the
user press TAB again) to import the module.

If *include_values* is false, the returned values list is empty and
attribute values are not resolved.
"""
result = ImportParser(line).parse()
if not result:
return None
try:
return self.complete(*result)
return self.complete(*result, include_values=include_values)
except Exception:
# Some unexpected error occurred, make it look like
# no completions are available
return [], None

def complete(self, from_name: str | None, name: str | None) -> tuple[list[str], CompletionAction | None]:
return [], [], None

def complete(
self,
from_name: str | None,
name: str | None,
*,
include_values: bool = True,
) -> tuple[list[str], list[Any], CompletionAction | None]:
if from_name is None:
# import x.y.z<tab>
assert name is not None
path, prefix = self.get_path_and_prefix(name)
modules = self.find_modules(path, prefix)
return [self.format_completion(path, module) for module in modules], None
names = [self.format_completion(path, module) for module in modules]
# These are always modules, use dummy values to get the right color
values = [sys] * len(names) if include_values else []
return names, values, None

if name is None:
# from x.y.z<tab>
path, prefix = self.get_path_and_prefix(from_name)
modules = self.find_modules(path, prefix)
return [self.format_completion(path, module) for module in modules], None
names = [self.format_completion(path, module) for module in modules]
# These are always modules, use dummy values to get the right color
values = [sys] * len(names) if include_values else []
return names, values, None

# from x.y import z<tab>
submodules = self.find_modules(from_name, name)
attributes, action = self.find_attributes(from_name, name)
return sorted({*submodules, *attributes}), action
attr_names, attr_module, action = self._find_attributes(from_name, name)
all_names = sorted({*submodules, *attr_names})
if not include_values:
return all_names, [], action

# Build values list matching the sorted order:
# submodules use `sys` as a dummy value so they get the 'module' color,
# attributes use their actual value.
attr_map = {}
if attr_module is not None:
attr_map = {n: safe_getattr(attr_module, n) for n in attr_names}
all_values = [attr_map[n] if n in attr_map else sys for n in all_names]
return all_names, all_values, action

def find_modules(self, path: str, prefix: str) -> list[str]:
"""Find all modules under 'path' that start with 'prefix'."""
Expand Down Expand Up @@ -166,31 +195,43 @@ def _is_stdlib_module(self, module_info: pkgutil.ModuleInfo) -> bool:
return (isinstance(module_info.module_finder, FileFinder)
and module_info.module_finder.path == self._stdlib_path)

def find_attributes(self, path: str, prefix: str) -> tuple[list[str], CompletionAction | None]:
def find_attributes(
self, path: str, prefix: str
) -> tuple[list[str], list[Any], CompletionAction | None]:
"""Find all attributes of module 'path' that start with 'prefix'."""
attributes, action = self._find_attributes(path, prefix)
# Filter out invalid attribute names
# (for example those containing dashes that cannot be imported with 'import')
return [attr for attr in attributes if attr.isidentifier()], action
attributes, module, action = self._find_attributes(path, prefix)
if module is not None:
values = [safe_getattr(module, attr) for attr in attributes]
else:
values = []
return attributes, values, action

def _find_attributes(self, path: str, prefix: str) -> tuple[list[str], CompletionAction | None]:
def _find_attributes(
self, path: str, prefix: str
) -> tuple[list[str], ModuleType | None, CompletionAction | None]:
path = self._resolve_relative_path(path) # type: ignore[assignment]
if path is None:
return [], None
return [], None, None

imported_module = sys.modules.get(path)
if not imported_module:
if path in self._failed_imports: # Do not propose to import again
return [], None
return [], None, None
imported_module = self._maybe_import_module(path)
if not imported_module:
return [], self._get_import_completion_action(path)
return [], None, self._get_import_completion_action(path)
try:
module_attributes = dir(imported_module)
except Exception:
module_attributes = []
return [attr_name for attr_name in module_attributes
if self.is_suggestion_match(attr_name, prefix)], None
# Filter out invalid attribute names, such as dashes that cannot be
# imported with 'import'.
names = [
attr_name for attr_name in module_attributes
if (self.is_suggestion_match(attr_name, prefix)
and attr_name.isidentifier())
]
return names, imported_module, None

def is_suggestion_match(self, module_name: str, prefix: str) -> bool:
if prefix:
Expand Down
Loading
Loading