aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Doc/deprecations/pending-removal-in-3.15.rst7
-rw-r--r--Doc/library/annotationlib.rst140
-rw-r--r--Doc/reference/compound_stmts.rst8
-rw-r--r--Doc/whatsnew/3.14.rst27
-rw-r--r--Doc/whatsnew/3.15.rst26
-rw-r--r--Lib/asyncio/graph.py6
-rw-r--r--Lib/test/test_dict.py2
-rw-r--r--Lib/test/test_typing.py127
-rw-r--r--Lib/typing.py61
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst1
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst2
-rw-r--r--Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst3
-rw-r--r--Modules/_ctypes/_ctypes.c18
-rw-r--r--Modules/_ctypes/clinic/_ctypes.c.h12
-rw-r--r--Modules/_zstd/_zstdmodule.c3
-rw-r--r--Modules/_zstd/compressor.c3
-rw-r--r--Modules/_zstd/decompressor.c183
-rw-r--r--Modules/_zstd/zstddict.c3
-rw-r--r--Objects/dictobject.c10
-rw-r--r--Python/stackrefs.c2
20 files changed, 284 insertions, 360 deletions
diff --git a/Doc/deprecations/pending-removal-in-3.15.rst b/Doc/deprecations/pending-removal-in-3.15.rst
index 7b32275ad86..c80588b27b6 100644
--- a/Doc/deprecations/pending-removal-in-3.15.rst
+++ b/Doc/deprecations/pending-removal-in-3.15.rst
@@ -85,6 +85,13 @@ Pending removal in Python 3.15
has been deprecated since Python 3.13.
Use the class-based syntax or the functional syntax instead.
+ * When using the functional syntax of :class:`~typing.TypedDict`\s, failing
+ to pass a value to the *fields* parameter (``TD = TypedDict("TD")``) or
+ passing ``None`` (``TD = TypedDict("TD", None)``) has been deprecated
+ since Python 3.13.
+ Use ``class TD(TypedDict): pass`` or ``TD = TypedDict("TD", {})``
+ to create a TypedDict with zero field.
+
* The :func:`typing.no_type_check_decorator` decorator function
has been deprecated since Python 3.13.
After eight years in the :mod:`typing` module,
diff --git a/Doc/library/annotationlib.rst b/Doc/library/annotationlib.rst
index ff9578b6088..41c9ce479ff 100644
--- a/Doc/library/annotationlib.rst
+++ b/Doc/library/annotationlib.rst
@@ -127,16 +127,27 @@ Classes
Values are the result of evaluating the annotation expressions.
- .. attribute:: FORWARDREF
+ .. attribute:: VALUE_WITH_FAKE_GLOBALS
:value: 2
+ Special value used to signal that an annotate function is being
+ evaluated in a special environment with fake globals. When passed this
+ value, annotate functions should either return the same value as for
+ the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
+ to signal that they do not support execution in this environment.
+ This format is only used internally and should not be passed to
+ the functions in this module.
+
+ .. attribute:: FORWARDREF
+ :value: 3
+
Values are real annotation values (as per :attr:`Format.VALUE` format)
for defined values, and :class:`ForwardRef` proxies for undefined
values. Real objects may contain references to :class:`ForwardRef`
proxy objects.
.. attribute:: STRING
- :value: 3
+ :value: 4
Values are the text string of the annotation as it appears in the
source code, up to modifications including, but not restricted to,
@@ -144,17 +155,6 @@ Classes
The exact values of these strings may change in future versions of Python.
- .. attribute:: VALUE_WITH_FAKE_GLOBALS
- :value: 4
-
- Special value used to signal that an annotate function is being
- evaluated in a special environment with fake globals. When passed this
- value, annotate functions should either return the same value as for
- the :attr:`Format.VALUE` format, or raise :exc:`NotImplementedError`
- to signal that they do not support execution in this environment.
- This format is only used internally and should not be passed to
- the functions in this module.
-
.. versionadded:: 3.14
.. class:: ForwardRef
@@ -485,3 +485,117 @@ annotations from the class and puts them in a separate attribute:
typ.classvars = classvars # Store the ClassVars in a separate attribute
return typ
+
+Limitations of the ``STRING`` format
+------------------------------------
+
+The :attr:`~Format.STRING` format is meant to approximate the source code
+of the annotation, but the implementation strategy used means that it is not
+always possible to recover the exact source code.
+
+First, the stringifier of course cannot recover any information that is not present in
+the compiled code, including comments, whitespace, parenthesization, and operations that
+get simplified by the compiler.
+
+Second, the stringifier can intercept almost all operations that involve names looked
+up in some scope, but it cannot intercept operations that operate fully on constants.
+As a corollary, this also means it is not safe to request the ``STRING`` format on
+untrusted code: Python is powerful enough that it is possible to achieve arbitrary
+code execution even with no access to any globals or builtins. For example:
+
+.. code-block:: pycon
+
+ >>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
+ ...
+ >>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
+ Hello world
+ {'x': 'None'}
+
+.. note::
+ This particular example works as of the time of writing, but it relies on
+ implementation details and is not guaranteed to work in the future.
+
+Among the different kinds of expressions that exist in Python,
+as represented by the :mod:`ast` module, some expressions are supported,
+meaning that the ``STRING`` format can generally recover the original source code;
+others are unsupported, meaning that they may result in incorrect output or an error.
+
+The following are supported (sometimes with caveats):
+
+* :class:`ast.BinOp`
+* :class:`ast.UnaryOp`
+
+ * :class:`ast.Invert` (``~``), :class:`ast.UAdd` (``+``), and :class:`ast.USub` (``-``) are supported
+ * :class:`ast.Not` (``not``) is not supported
+
+* :class:`ast.Dict` (except when using ``**`` unpacking)
+* :class:`ast.Set`
+* :class:`ast.Compare`
+
+ * :class:`ast.Eq` and :class:`ast.NotEq` are supported
+ * :class:`ast.Lt`, :class:`ast.LtE`, :class:`ast.Gt`, and :class:`ast.GtE` are supported, but the operand may be flipped
+ * :class:`ast.Is`, :class:`ast.IsNot`, :class:`ast.In`, and :class:`ast.NotIn` are not supported
+
+* :class:`ast.Call` (except when using ``**`` unpacking)
+* :class:`ast.Constant` (though not the exact representation of the constant; for example, escape
+ sequences in strings are lost; hexadecimal numbers are converted to decimal)
+* :class:`ast.Attribute` (assuming the value is not a constant)
+* :class:`ast.Subscript` (assuming the value is not a constant)
+* :class:`ast.Starred` (``*`` unpacking)
+* :class:`ast.Name`
+* :class:`ast.List`
+* :class:`ast.Tuple`
+* :class:`ast.Slice`
+
+The following are unsupported, but throw an informative error when encountered by the
+stringifier:
+
+* :class:`ast.FormattedValue` (f-strings; error is not detected if conversion specifiers like ``!r``
+ are used)
+* :class:`ast.JoinedStr` (f-strings)
+
+The following are unsupported and result in incorrect output:
+
+* :class:`ast.BoolOp` (``and`` and ``or``)
+* :class:`ast.IfExp`
+* :class:`ast.Lambda`
+* :class:`ast.ListComp`
+* :class:`ast.SetComp`
+* :class:`ast.DictComp`
+* :class:`ast.GeneratorExp`
+
+The following are disallowed in annotation scopes and therefore not relevant:
+
+* :class:`ast.NamedExpr` (``:=``)
+* :class:`ast.Await`
+* :class:`ast.Yield`
+* :class:`ast.YieldFrom`
+
+
+Limitations of the ``FORWARDREF`` format
+----------------------------------------
+
+The :attr:`~Format.FORWARDREF` format aims to produce real values as much
+as possible, with anything that cannot be resolved replaced with
+:class:`ForwardRef` objects. It is affected by broadly the same Limitations
+as the :attr:`~Format.STRING` format: annotations that perform operations on
+literals or that use unsupported expression types may raise exceptions when
+evaluated using the :attr:`~Format.FORWARDREF` format.
+
+Below are a few examples of the behavior with unsupported expressions:
+
+.. code-block:: pycon
+
+ >>> from annotationlib import get_annotations, Format
+ >>> def zerodiv(x: 1 / 0): ...
+ >>> get_annotations(zerodiv, format=Format.STRING)
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: division by zero
+ >>> get_annotations(zerodiv, format=Format.FORWARDREF)
+ Traceback (most recent call last):
+ ...
+ ZeroDivisionError: division by zero
+ >>> def ifexp(x: 1 if y else 0): ...
+ >>> get_annotations(ifexp, format=Format.STRING)
+ {'x': '1'}
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index f36ed3e122f..5d4298f70e0 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -1885,7 +1885,7 @@ expressions. The presence of annotations does not change the runtime semantics o
the code, except if some mechanism is used that introspects and uses the annotations
(such as :mod:`dataclasses` or :func:`functools.singledispatch`).
-By default, annotations are lazily evaluated in a :ref:`annotation scope <annotation-scopes>`.
+By default, annotations are lazily evaluated in an :ref:`annotation scope <annotation-scopes>`.
This means that they are not evaluated when the code containing the annotation is evaluated.
Instead, the interpreter saves information that can be used to evaluate the annotation later
if requested. The :mod:`annotationlib` module provides tools for evaluating annotations.
@@ -1898,6 +1898,12 @@ all annotations are instead stored as strings::
>>> f.__annotations__
{'param': 'annotation'}
+This future statement will be deprecated and removed in a future version of Python,
+but not before Python 3.13 reaches its end of life (see :pep:`749`).
+When it is used, introspection tools like
+:func:`annotationlib.get_annotations` and :func:`typing.get_type_hints` are
+less likely to be able to resolve annotations at runtime.
+
.. rubric:: Footnotes
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index d7b3bac8d85..11361289874 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -82,7 +82,7 @@ and improvements in user-friendliness and correctness.
.. PEP-sized items next.
-* :ref:`PEP 649: deferred evaluation of annotations <whatsnew314-pep649>`
+* :ref:`PEP 649 and 749: deferred evaluation of annotations <whatsnew314-pep649>`
* :ref:`PEP 741: Python Configuration C API <whatsnew314-pep741>`
* :ref:`PEP 750: Template strings <whatsnew314-pep750>`
* :ref:`PEP 758: Allow except and except* expressions without parentheses <whatsnew314-pep758>`
@@ -362,18 +362,19 @@ Check :pep:`758` for more details.
.. _whatsnew314-pep649:
-PEP 649: deferred evaluation of annotations
--------------------------------------------
+PEP 649 and 749: deferred evaluation of annotations
+---------------------------------------------------
The :term:`annotations <annotation>` on functions, classes, and modules are no
longer evaluated eagerly. Instead, annotations are stored in special-purpose
:term:`annotate functions <annotate function>` and evaluated only when
-necessary. This is specified in :pep:`649` and :pep:`749`.
+necessary (except if ``from __future__ import annotations`` is used).
+This is specified in :pep:`649` and :pep:`749`.
This change is designed to make annotations in Python more performant and more
usable in most circumstances. The runtime cost for defining annotations is
minimized, but it remains possible to introspect annotations at runtime.
-It is usually no longer necessary to enclose annotations in strings if they
+It is no longer necessary to enclose annotations in strings if they
contain forward references.
The new :mod:`annotationlib` module provides tools for inspecting deferred
@@ -409,7 +410,8 @@ writing annotations the same way you did with previous versions of Python.
You will likely be able to remove quoted strings in annotations, which are frequently
used for forward references. Similarly, if you use ``from __future__ import annotations``
to avoid having to write strings in annotations, you may well be able to
-remove that import. However, if you rely on third-party libraries that read annotations,
+remove that import once you support only Python 3.14 and newer.
+However, if you rely on third-party libraries that read annotations,
those libraries may need changes to support unquoted annotations before they
work as expected.
@@ -422,6 +424,11 @@ annotations. For example, you may want to use :func:`annotationlib.get_annotatio
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
module now does.
+The external :pypi:`typing_extensions` package provides partial backports of some of the
+functionality of the :mod:`annotationlib` module, such as the :class:`~annotationlib.Format`
+enum and the :func:`~annotationlib.get_annotations` function. These can be used to
+write cross-version code that takes advantage of the new behavior in Python 3.14.
+
Related changes
^^^^^^^^^^^^^^^
@@ -433,6 +440,10 @@ functions in the standard library, there are many ways in which your code may
not work in Python 3.14. To safeguard your code against future changes,
use only the documented functionality of the :mod:`annotationlib` module.
+In particular, do not read annotations directly from the namespace dictionary
+attribute of type objects. Use :func:`annotationlib.get_annotate_from_class_namespace`
+during class construction and :func:`annotationlib.get_annotations` afterwards.
+
``from __future__ import annotations``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -444,8 +455,10 @@ Python without deferred evaluation of annotations, reaches its end of life in 20
In Python 3.14, the behavior of code using ``from __future__ import annotations``
is unchanged.
+(Contributed by Jelle Zijlstra in :gh:`119180`; :pep:`649` was written by Larry Hastings.)
+
.. seealso::
- :pep:`649`.
+ :pep:`649` and :pep:`749`.
Improved error messages
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 6ce7f964020..8cf5238e6cc 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -121,10 +121,27 @@ Deprecated
Removed
=======
-module_name
------------
+sysconfig
+---------
+
+* Removed the *check_home* parameter of :func:`sysconfig.is_python_build`.
+ (Contributed by Filipe Laíns in :gh:`92897`.)
-* TODO
+
+typing
+------
+
+* The undocumented keyword argument syntax for creating
+ :class:`~typing.NamedTuple` classes (for example,
+ ``Point = NamedTuple("Point", x=int, y=int)``) is no longer supported.
+ Use the class-based syntax or the functional syntax instead.
+ (Contributed by Bénédikt Tran in :gh:`133817`.)
+
+* Using ``TD = TypedDict("TD")`` or ``TD = TypedDict("TD", None)`` to
+ construct a :class:`~typing.TypedDict` type with zero field is no
+ longer supported. Use ``class TD(TypedDict): pass``
+ or ``TD = TypedDict("TD", {})`` instead.
+ (Contributed by Bénédikt Tran in :gh:`133823`.)
Porting to Python 3.15
@@ -187,6 +204,7 @@ Removed C APIs
* :c:func:`!PyImport_ImportModuleNoBlock`: deprecated alias
of :c:func:`PyImport_ImportModule`.
+ (Contributed by Bénédikt Tran in :gh:`133644`.)
The following functions are removed in favor of :c:func:`PyConfig_Get`.
The |pythoncapi_compat_project| can be used to get :c:func:`!PyConfig_Get`
@@ -219,6 +237,8 @@ on Python 3.13 and older.
use :c:func:`PyConfig_Get("home") <PyConfig_Get>` or the
:envvar:`PYTHONHOME` environment variable instead.
+ (Contributed by Bénédikt Tran in :gh:`133644`.)
+
.. |pythoncapi_compat_project| replace:: |pythoncapi_compat_project_link|_
.. |pythoncapi_compat_project_link| replace:: pythoncapi-compat project
.. _pythoncapi_compat_project_link: https://github.com/python/pythoncapi-compat
diff --git a/Lib/asyncio/graph.py b/Lib/asyncio/graph.py
index d8df7c9919a..b5bfeb1630a 100644
--- a/Lib/asyncio/graph.py
+++ b/Lib/asyncio/graph.py
@@ -1,6 +1,7 @@
"""Introspection utils for tasks call graphs."""
import dataclasses
+import io
import sys
import types
@@ -16,9 +17,6 @@ __all__ = (
'FutureCallGraph',
)
-if False: # for type checkers
- from typing import TextIO
-
# Sadly, we can't re-use the traceback module's datastructures as those
# are tailored for error reporting, whereas we need to represent an
# async call graph.
@@ -270,7 +268,7 @@ def print_call_graph(
future: futures.Future | None = None,
/,
*,
- file: TextIO | None = None,
+ file: io.Writer[str] | None = None,
depth: int = 1,
limit: int | None = None,
) -> None:
diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index 69f1a098920..10a6d071b0f 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -1039,10 +1039,8 @@ class DictTest(unittest.TestCase):
a = C()
a.x = 1
d = a.__dict__
- before_resize = sys.getsizeof(d)
d[2] = 2 # split table is resized to a generic combined table
- self.assertGreater(sys.getsizeof(d), before_resize)
self.assertEqual(list(d), ['x', 2])
def test_iterator_pickling(self):
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 8c55ba4623e..bc03e6bf2d7 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -8080,78 +8080,13 @@ class NamedTupleTests(BaseTestCase):
self.assertIs(type(a), Group)
self.assertEqual(a, (1, [2]))
- def test_namedtuple_keyword_usage(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "Creating NamedTuple classes using keyword arguments is deprecated"
- ):
- LocalEmployee = NamedTuple("LocalEmployee", name=str, age=int)
-
- nick = LocalEmployee('Nick', 25)
- self.assertIsInstance(nick, tuple)
- self.assertEqual(nick.name, 'Nick')
- self.assertEqual(LocalEmployee.__name__, 'LocalEmployee')
- self.assertEqual(LocalEmployee._fields, ('name', 'age'))
- self.assertEqual(LocalEmployee.__annotations__, dict(name=str, age=int))
-
- with self.assertRaisesRegex(
- TypeError,
- "Either list of fields or keywords can be provided to NamedTuple, not both"
- ):
- NamedTuple('Name', [('x', int)], y=str)
-
- with self.assertRaisesRegex(
- TypeError,
- "Either list of fields or keywords can be provided to NamedTuple, not both"
- ):
- NamedTuple('Name', [], y=str)
-
- with self.assertRaisesRegex(
- TypeError,
- (
- r"Cannot pass `None` as the 'fields' parameter "
- r"and also specify fields using keyword arguments"
- )
- ):
- NamedTuple('Name', None, x=int)
-
- def test_namedtuple_special_keyword_names(self):
- with self.assertWarnsRegex(
- DeprecationWarning,
- "Creating NamedTuple classes using keyword arguments is deprecated"
- ):
- NT = NamedTuple("NT", cls=type, self=object, typename=str, fields=list)
-
- self.assertEqual(NT.__name__, 'NT')
- self.assertEqual(NT._fields, ('cls', 'self', 'typename', 'fields'))
- a = NT(cls=str, self=42, typename='foo', fields=[('bar', tuple)])
- self.assertEqual(a.cls, str)
- self.assertEqual(a.self, 42)
- self.assertEqual(a.typename, 'foo')
- self.assertEqual(a.fields, [('bar', tuple)])
-
def test_empty_namedtuple(self):
- expected_warning = re.escape(
- "Failing to pass a value for the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. `NT1 = NamedTuple('NT1', [])`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- NT1 = NamedTuple('NT1')
-
- expected_warning = re.escape(
- "Passing `None` as the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. `NT2 = NamedTuple('NT2', [])`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- NT2 = NamedTuple('NT2', None)
+ with self.assertRaisesRegex(TypeError, "missing.*required.*argument"):
+ BAD = NamedTuple('BAD')
- NT3 = NamedTuple('NT2', [])
+ NT1 = NamedTuple('NT1', {})
+ NT2 = NamedTuple('NT2', ())
+ NT3 = NamedTuple('NT3', [])
class CNT(NamedTuple):
pass # empty body
@@ -8166,16 +8101,18 @@ class NamedTupleTests(BaseTestCase):
def test_namedtuple_errors(self):
with self.assertRaises(TypeError):
NamedTuple.__new__()
+ with self.assertRaisesRegex(TypeError, "object is not iterable"):
+ NamedTuple('Name', None)
with self.assertRaisesRegex(
TypeError,
- "missing 1 required positional argument"
+ "missing 2 required positional arguments"
):
NamedTuple()
with self.assertRaisesRegex(
TypeError,
- "takes from 1 to 2 positional arguments but 3 were given"
+ "takes 2 positional arguments but 3 were given"
):
NamedTuple('Emp', [('name', str)], None)
@@ -8187,10 +8124,22 @@ class NamedTupleTests(BaseTestCase):
with self.assertRaisesRegex(
TypeError,
- "missing 1 required positional argument: 'typename'"
+ "got some positional-only arguments passed as keyword arguments"
):
NamedTuple(typename='Emp', name=str, id=int)
+ with self.assertRaisesRegex(
+ TypeError,
+ "got an unexpected keyword argument"
+ ):
+ NamedTuple('Name', [('x', int)], y=str)
+
+ with self.assertRaisesRegex(
+ TypeError,
+ "got an unexpected keyword argument"
+ ):
+ NamedTuple('Name', [], y=str)
+
def test_copy_and_pickle(self):
global Emp # pickle wants to reference the class by name
Emp = NamedTuple('Emp', [('name', str), ('cool', int)])
@@ -8904,39 +8853,27 @@ class TypedDictTests(BaseTestCase):
self.assertEqual(CallTypedDict.__orig_bases__, (TypedDict,))
def test_zero_fields_typeddicts(self):
- T1 = TypedDict("T1", {})
+ T1a = TypedDict("T1a", {})
+ T1b = TypedDict("T1b", [])
+ T1c = TypedDict("T1c", ())
class T2(TypedDict): pass
class T3[tvar](TypedDict): pass
S = TypeVar("S")
class T4(TypedDict, Generic[S]): pass
- expected_warning = re.escape(
- "Failing to pass a value for the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T5 = TypedDict('T5', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- T5 = TypedDict('T5')
-
- expected_warning = re.escape(
- "Passing `None` as the 'fields' parameter is deprecated "
- "and will be disallowed in Python 3.15. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. `T6 = TypedDict('T6', {})`."
- )
- with self.assertWarnsRegex(DeprecationWarning, fr"^{expected_warning}$"):
- T6 = TypedDict('T6', None)
-
- for klass in T1, T2, T3, T4, T5, T6:
+ for klass in T1a, T1b, T1c, T2, T3, T4:
with self.subTest(klass=klass.__name__):
self.assertEqual(klass.__annotations__, {})
self.assertEqual(klass.__required_keys__, set())
self.assertEqual(klass.__optional_keys__, set())
self.assertIsInstance(klass(), dict)
+ def test_errors(self):
+ with self.assertRaisesRegex(TypeError, "missing 1 required.*argument"):
+ TypedDict('TD')
+ with self.assertRaisesRegex(TypeError, "object is not iterable"):
+ TypedDict('TD', None)
+
def test_readonly_inheritance(self):
class Base1(TypedDict):
a: ReadOnly[int]
diff --git a/Lib/typing.py b/Lib/typing.py
index 2baf655256d..44f39e9672f 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -2968,7 +2968,7 @@ class NamedTupleMeta(type):
return nm_tpl
-def NamedTuple(typename, fields=_sentinel, /, **kwargs):
+def NamedTuple(typename, fields, /):
"""Typed version of namedtuple.
Usage::
@@ -2988,48 +2988,9 @@ def NamedTuple(typename, fields=_sentinel, /, **kwargs):
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
"""
- if fields is _sentinel:
- if kwargs:
- deprecated_thing = "Creating NamedTuple classes using keyword arguments"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "Use the class-based or functional syntax instead."
- )
- else:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif fields is None:
- if kwargs:
- raise TypeError(
- "Cannot pass `None` as the 'fields' parameter "
- "and also specify fields using keyword arguments"
- )
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
- example = f"`{typename} = NamedTuple({typename!r}, [])`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a NamedTuple class with 0 fields "
- "using the functional syntax, "
- "pass an empty list, e.g. "
- ) + example + "."
- elif kwargs:
- raise TypeError("Either list of fields or keywords"
- " can be provided to NamedTuple, not both")
- if fields is _sentinel or fields is None:
- import warnings
- warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
- fields = kwargs.items()
types = {n: _type_check(t, f"field {n} annotation must be a type")
for n, t in fields}
field_names = [n for n, _ in fields]
-
nt = _make_nmtuple(typename, field_names, _make_eager_annotate(types), module=_caller())
nt.__orig_bases__ = (NamedTuple,)
return nt
@@ -3198,7 +3159,7 @@ class _TypedDictMeta(type):
__instancecheck__ = __subclasscheck__
-def TypedDict(typename, fields=_sentinel, /, *, total=True):
+def TypedDict(typename, fields, /, *, total=True):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.
TypedDict creates a dictionary type such that a type checker will expect all
@@ -3253,24 +3214,6 @@ def TypedDict(typename, fields=_sentinel, /, *, total=True):
username: str # the "username" key can be changed
"""
- if fields is _sentinel or fields is None:
- import warnings
-
- if fields is _sentinel:
- deprecated_thing = "Failing to pass a value for the 'fields' parameter"
- else:
- deprecated_thing = "Passing `None` as the 'fields' parameter"
-
- example = f"`{typename} = TypedDict({typename!r}, {{{{}}}})`"
- deprecation_msg = (
- "{name} is deprecated and will be disallowed in Python {remove}. "
- "To create a TypedDict class with 0 fields "
- "using the functional syntax, "
- "pass an empty dictionary, e.g. "
- ) + example + "."
- warnings._deprecated(deprecated_thing, message=deprecation_msg, remove=(3, 15))
- fields = {}
-
ns = {'__annotations__': dict(fields)}
module = _caller()
if module is not None:
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst
new file mode 100644
index 00000000000..05bf6103314
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-05-10-17-12-27.gh-issue-133703.bVM-re.rst
@@ -0,0 +1 @@
+Fix hashtable in dict can be bigger than intended in some situations.
diff --git a/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst b/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst
new file mode 100644
index 00000000000..326e767de5f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-10-12-07-54.gh-issue-133817.4GMtKV.rst
@@ -0,0 +1,2 @@
+Remove support for creating :class:`~typing.NamedTuple` classes via the
+undocumented keyword argument syntax. Patch by Bénédikt Tran.
diff --git a/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
new file mode 100644
index 00000000000..67b44ac3ef3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-11-08-48-55.gh-issue-133823.F8udQy.rst
@@ -0,0 +1,3 @@
+Remove support for ``TD = TypedDict("TD")`` and ``TD = TypedDict("TD", None)``
+calls for constructing :class:`typing.TypedDict` objects with zero field.
+Patch by Bénédikt Tran.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index a6606381e49..7de6bb396b0 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -578,14 +578,13 @@ _ctypes_CType_Type___sizeof___impl(PyObject *self, PyTypeObject *cls)
/*[clinic input]
@getter
-@critical_section
_ctypes.CType_Type.__pointer_type__
[clinic start generated code]*/
static PyObject *
_ctypes_CType_Type___pointer_type___get_impl(PyObject *self)
-/*[clinic end generated code: output=718c9ff10b2b0012 input=ff7498aa6edf487c]*/
+/*[clinic end generated code: output=718c9ff10b2b0012 input=ad12dc835943ceb8]*/
{
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
StgInfo *info;
@@ -596,9 +595,12 @@ _ctypes_CType_Type___pointer_type___get_impl(PyObject *self)
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
return NULL;
}
-
- if (info->pointer_type) {
- return Py_NewRef(info->pointer_type);
+ PyObject *pointer_type;
+ STGINFO_LOCK(info);
+ pointer_type = Py_XNewRef(info->pointer_type);
+ STGINFO_UNLOCK();
+ if (pointer_type) {
+ return pointer_type;
}
PyErr_Format(PyExc_AttributeError,
@@ -609,14 +611,13 @@ _ctypes_CType_Type___pointer_type___get_impl(PyObject *self)
/*[clinic input]
@setter
-@critical_section
_ctypes.CType_Type.__pointer_type__
[clinic start generated code]*/
static int
_ctypes_CType_Type___pointer_type___set_impl(PyObject *self, PyObject *value)
-/*[clinic end generated code: output=6259be8ea21693fa input=9b2dc2400c388982]*/
+/*[clinic end generated code: output=6259be8ea21693fa input=a05055fc7f4714b6]*/
{
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
StgInfo *info;
@@ -627,8 +628,9 @@ _ctypes_CType_Type___pointer_type___set_impl(PyObject *self, PyObject *value)
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
return -1;
}
-
+ STGINFO_LOCK(info);
Py_XSETREF(info->pointer_type, Py_XNewRef(value));
+ STGINFO_UNLOCK();
return 0;
}
diff --git a/Modules/_ctypes/clinic/_ctypes.c.h b/Modules/_ctypes/clinic/_ctypes.c.h
index d9a2ab19661..cf2e3fa2107 100644
--- a/Modules/_ctypes/clinic/_ctypes.c.h
+++ b/Modules/_ctypes/clinic/_ctypes.c.h
@@ -47,13 +47,7 @@ _ctypes_CType_Type___pointer_type___get_impl(PyObject *self);
static PyObject *
_ctypes_CType_Type___pointer_type___get(PyObject *self, void *Py_UNUSED(context))
{
- PyObject *return_value = NULL;
-
- Py_BEGIN_CRITICAL_SECTION(self);
- return_value = _ctypes_CType_Type___pointer_type___get_impl(self);
- Py_END_CRITICAL_SECTION();
-
- return return_value;
+ return _ctypes_CType_Type___pointer_type___get_impl(self);
}
#if !defined(_ctypes_CType_Type___pointer_type___DOCSTR)
@@ -74,9 +68,7 @@ _ctypes_CType_Type___pointer_type___set(PyObject *self, PyObject *value, void *P
{
int return_value;
- Py_BEGIN_CRITICAL_SECTION(self);
return_value = _ctypes_CType_Type___pointer_type___set_impl(self, value);
- Py_END_CRITICAL_SECTION();
return return_value;
}
@@ -1050,4 +1042,4 @@ Simple_from_outparm(PyObject *self, PyTypeObject *cls, PyObject *const *args, Py
}
return Simple_from_outparm_impl(self, cls);
}
-/*[clinic end generated code: output=f4bc2a77ec073b8a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=536c9bcf4e05913e input=a9049054013a1b77]*/
diff --git a/Modules/_zstd/_zstdmodule.c b/Modules/_zstd/_zstdmodule.c
index bddef0d0b86..c3852fe8973 100644
--- a/Modules/_zstd/_zstdmodule.c
+++ b/Modules/_zstd/_zstdmodule.c
@@ -679,6 +679,9 @@ do { \
ADD_INT_CONST_TO_TYPE(mod_state->ZstdCompressor_type,
"FLUSH_FRAME", ZSTD_e_end);
+ /* Make ZstdCompressor immutable (set Py_TPFLAGS_IMMUTABLETYPE) */
+ PyType_Freeze(mod_state->ZstdCompressor_type);
+
#undef ADD_TYPE
#undef ADD_INT_MACRO
#undef ADD_ZSTD_COMPRESSOR_INT_CONST
diff --git a/Modules/_zstd/compressor.c b/Modules/_zstd/compressor.c
index e70eb637b29..355a27d2734 100644
--- a/Modules/_zstd/compressor.c
+++ b/Modules/_zstd/compressor.c
@@ -729,6 +729,9 @@ static PyType_Slot zstdcompressor_slots[] = {
PyType_Spec zstd_compressor_type_spec = {
.name = "compression.zstd.ZstdCompressor",
.basicsize = sizeof(ZstdCompressor),
+ // Py_TPFLAGS_IMMUTABLETYPE is not used here as several
+ // associated constants need to be added to the type.
+ // PyType_Freeze is called later to set the flag.
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.slots = zstdcompressor_slots,
};
diff --git a/Modules/_zstd/decompressor.c b/Modules/_zstd/decompressor.c
index 2ed88cd3f23..dc3e90a22d8 100644
--- a/Modules/_zstd/decompressor.c
+++ b/Modules/_zstd/decompressor.c
@@ -43,20 +43,11 @@ typedef struct {
PyObject *unused_data;
/* 0 if decompressor has (or may has) unconsumed input data, 0 or 1. */
- char needs_input;
-
- /* For decompress(), 0 or 1.
- 1 when both input and output streams are at a frame edge, means a
- frame is completely decoded and fully flushed, or the decompressor
- just be initialized. */
- char at_frame_edge;
+ bool needs_input;
/* For ZstdDecompressor, 0 or 1.
1 means the end of the first frame has been reached. */
- char eof;
-
- /* Used for fast reset above three variables */
- char _unused_char_for_align;
+ bool eof;
/* __init__ has been called, 0 or 1. */
bool initialized;
@@ -258,19 +249,13 @@ load:
return 0;
}
-typedef enum {
- TYPE_DECOMPRESSOR, // <D>, ZstdDecompressor class
- TYPE_ENDLESS_DECOMPRESSOR, // <E>, decompress() function
-} decompress_type;
-
/*
- Given the two types of decompressors (defined above),
- decompress implementation for <D>, <E>, pseudo code:
+ Decompress implementation in pseudo code:
initialize_output_buffer
while True:
decompress_data
- set_object_flag # .eof for <D>, .at_frame_edge for <E>.
+ set_object_flag # .eof
if output_buffer_exhausted:
if output_buffer_reached_max_length:
@@ -287,63 +272,19 @@ typedef enum {
flushing to do to complete current frame.
Note, decompressing "an empty input" in any case will make it > 0.
-
- <E> supports multiple frames, has an .at_frame_edge flag, it means both the
- input and output streams are at a frame edge. The flag can be set by this
- statement:
-
- .at_frame_edge = (zstd_ret == 0) ? 1 : 0
-
- But if decompressing "an empty input" at "a frame edge", zstd_ret will be
- non-zero, then .at_frame_edge will be wrongly set to false. To solve this
- problem, two AFE checks are needed to ensure that: when at "a frame edge",
- empty input will not be decompressed.
-
- // AFE check
- if (self->at_frame_edge && in->pos == in->size) {
- finish
- }
-
- In <E>, if .at_frame_edge is eventually set to true, but input stream has
- unconsumed data (in->pos < in->size), then the outer function
- stream_decompress() will set .at_frame_edge to false. In this case,
- although the output stream is at a frame edge, for the caller, the input
- stream is not at a frame edge, see below diagram. This behavior does not
- affect the next AFE check, since (in->pos < in->size).
-
- input stream: --------------|---
- ^
- output stream: ====================|
- ^
*/
static PyObject *
decompress_impl(ZstdDecompressor *self, ZSTD_inBuffer *in,
- Py_ssize_t max_length,
- Py_ssize_t initial_size,
- decompress_type type)
+ Py_ssize_t max_length)
{
size_t zstd_ret;
ZSTD_outBuffer out;
_BlocksOutputBuffer buffer = {.list = NULL};
PyObject *ret;
- /* The first AFE check for setting .at_frame_edge flag */
- if (type == TYPE_ENDLESS_DECOMPRESSOR) {
- if (self->at_frame_edge && in->pos == in->size) {
- return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
- }
- }
-
/* Initialize the output buffer */
- if (initial_size >= 0) {
- if (_OutputBuffer_InitWithSize(&buffer, &out, max_length, initial_size) < 0) {
- goto error;
- }
- }
- else {
- if (_OutputBuffer_InitAndGrow(&buffer, &out, max_length) < 0) {
- goto error;
- }
+ if (_OutputBuffer_InitAndGrow(&buffer, &out, max_length) < 0) {
+ goto error;
}
assert(out.pos == 0);
@@ -362,22 +303,11 @@ decompress_impl(ZstdDecompressor *self, ZSTD_inBuffer *in,
goto error;
}
- /* Set .eof/.af_frame_edge flag */
- if (type == TYPE_DECOMPRESSOR) {
- /* ZstdDecompressor class stops when a frame is decompressed */
- if (zstd_ret == 0) {
- self->eof = 1;
- break;
- }
- }
- else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
- /* decompress() function supports multiple frames */
- self->at_frame_edge = (zstd_ret == 0) ? 1 : 0;
-
- /* The second AFE check for setting .at_frame_edge flag */
- if (self->at_frame_edge && in->pos == in->size) {
- break;
- }
+ /* Set .eof flag */
+ if (zstd_ret == 0) {
+ /* Stop when a frame is decompressed */
+ self->eof = 1;
+ break;
}
/* Need to check out before in. Maybe zstd's internal buffer still has
@@ -415,8 +345,7 @@ error:
}
static void
-decompressor_reset_session(ZstdDecompressor *self,
- decompress_type type)
+decompressor_reset_session(ZstdDecompressor *self)
{
// TODO(emmatyping): use _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED here
// and ensure lock is always held
@@ -425,56 +354,28 @@ decompressor_reset_session(ZstdDecompressor *self,
self->in_begin = 0;
self->in_end = 0;
- if (type == TYPE_DECOMPRESSOR) {
- Py_CLEAR(self->unused_data);
- }
+ Py_CLEAR(self->unused_data);
/* Reset variables in one operation */
self->needs_input = 1;
- self->at_frame_edge = 1;
self->eof = 0;
- self->_unused_char_for_align = 0;
- /* Resetting session never fail */
+ /* Resetting session is guaranteed to never fail */
ZSTD_DCtx_reset(self->dctx, ZSTD_reset_session_only);
}
static PyObject *
-stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length,
- decompress_type type)
+stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length)
{
- Py_ssize_t initial_buffer_size = -1;
ZSTD_inBuffer in;
PyObject *ret = NULL;
int use_input_buffer;
- if (type == TYPE_DECOMPRESSOR) {
- /* Check .eof flag */
- if (self->eof) {
- PyErr_SetString(PyExc_EOFError, "Already at the end of a zstd frame.");
- assert(ret == NULL);
- goto success;
- }
- }
- else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
- /* Fast path for the first frame */
- if (self->at_frame_edge && self->in_begin == self->in_end) {
- /* Read decompressed size */
- uint64_t decompressed_size = ZSTD_getFrameContentSize(data->buf, data->len);
-
- /* These two zstd constants always > PY_SSIZE_T_MAX:
- ZSTD_CONTENTSIZE_UNKNOWN is (0ULL - 1)
- ZSTD_CONTENTSIZE_ERROR is (0ULL - 2)
-
- Use ZSTD_findFrameCompressedSize() to check complete frame,
- prevent allocating too much memory for small input chunk. */
-
- if (decompressed_size <= (uint64_t) PY_SSIZE_T_MAX &&
- !ZSTD_isError(ZSTD_findFrameCompressedSize(data->buf, data->len)) )
- {
- initial_buffer_size = (Py_ssize_t) decompressed_size;
- }
- }
+ /* Check .eof flag */
+ if (self->eof) {
+ PyErr_SetString(PyExc_EOFError, "Already at the end of a zstd frame.");
+ assert(ret == NULL);
+ return NULL;
}
/* Prepare input buffer w/wo unconsumed data */
@@ -561,30 +462,18 @@ stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length
assert(in.pos == 0);
/* Decompress */
- ret = decompress_impl(self, &in,
- max_length, initial_buffer_size,
- type);
+ ret = decompress_impl(self, &in, max_length);
if (ret == NULL) {
goto error;
}
/* Unconsumed input data */
if (in.pos == in.size) {
- if (type == TYPE_DECOMPRESSOR) {
- if (Py_SIZE(ret) == max_length || self->eof) {
- self->needs_input = 0;
- }
- else {
- self->needs_input = 1;
- }
+ if (Py_SIZE(ret) == max_length || self->eof) {
+ self->needs_input = 0;
}
- else if (type == TYPE_ENDLESS_DECOMPRESSOR) {
- if (Py_SIZE(ret) == max_length && !self->at_frame_edge) {
- self->needs_input = 0;
- }
- else {
- self->needs_input = 1;
- }
+ else {
+ self->needs_input = 1;
}
if (use_input_buffer) {
@@ -598,10 +487,6 @@ stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length
self->needs_input = 0;
- if (type == TYPE_ENDLESS_DECOMPRESSOR) {
- self->at_frame_edge = 0;
- }
-
if (!use_input_buffer) {
/* Discard buffer if it's too small
(resizing it may needlessly copy the current contents) */
@@ -634,16 +519,14 @@ stream_decompress(ZstdDecompressor *self, Py_buffer *data, Py_ssize_t max_length
}
}
- goto success;
+ return ret;
error:
/* Reset decompressor's states/session */
- decompressor_reset_session(self, type);
+ decompressor_reset_session(self);
Py_CLEAR(ret);
-success:
-
- return ret;
+ return NULL;
}
@@ -668,9 +551,6 @@ _zstd_ZstdDecompressor_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* needs_input flag */
self->needs_input = 1;
- /* at_frame_edge flag */
- self->at_frame_edge = 1;
-
/* Decompression context */
self->dctx = ZSTD_createDCtx();
if (self->dctx == NULL) {
@@ -837,7 +717,7 @@ _zstd_ZstdDecompressor_decompress_impl(ZstdDecompressor *self,
/* Thread-safe code */
Py_BEGIN_CRITICAL_SECTION(self);
- ret = stream_decompress(self, data, max_length, TYPE_DECOMPRESSOR);
+ ret = stream_decompress(self, data, max_length);
Py_END_CRITICAL_SECTION();
return ret;
}
@@ -902,6 +782,7 @@ static PyType_Slot ZstdDecompressor_slots[] = {
PyType_Spec zstd_decompressor_type_spec = {
.name = "compression.zstd.ZstdDecompressor",
.basicsize = sizeof(ZstdDecompressor),
- .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC,
.slots = ZstdDecompressor_slots,
};
diff --git a/Modules/_zstd/zstddict.c b/Modules/_zstd/zstddict.c
index 99976cef85a..47bc8a84ca2 100644
--- a/Modules/_zstd/zstddict.c
+++ b/Modules/_zstd/zstddict.c
@@ -278,6 +278,7 @@ static PyType_Slot zstddict_slots[] = {
PyType_Spec zstd_dict_type_spec = {
.name = "compression.zstd.ZstdDict",
.basicsize = sizeof(ZstdDict),
- .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC,
.slots = zstddict_slots,
};
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 32356f0634d..ce27e47dabf 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -547,13 +547,13 @@ static inline uint8_t
calculate_log2_keysize(Py_ssize_t minsize)
{
#if SIZEOF_LONG == SIZEOF_SIZE_T
- minsize = (minsize | PyDict_MINSIZE) - 1;
- return _Py_bit_length(minsize | (PyDict_MINSIZE-1));
+ minsize = Py_MAX(minsize, PyDict_MINSIZE);
+ return _Py_bit_length(minsize - 1);
#elif defined(_MSC_VER)
- // On 64bit Windows, sizeof(long) == 4.
- minsize = (minsize | PyDict_MINSIZE) - 1;
+ // On 64bit Windows, sizeof(long) == 4. We cannot use _Py_bit_length.
+ minsize = Py_MAX(minsize, PyDict_MINSIZE);
unsigned long msb;
- _BitScanReverse64(&msb, (uint64_t)minsize);
+ _BitScanReverse64(&msb, (uint64_t)minsize - 1);
return (uint8_t)(msb + 1);
#else
uint8_t log2_size;
diff --git a/Python/stackrefs.c b/Python/stackrefs.c
index 979a6b1c628..69d4e8b9431 100644
--- a/Python/stackrefs.c
+++ b/Python/stackrefs.c
@@ -1,4 +1,3 @@
-
#include "Python.h"
#include "pycore_object.h"
@@ -34,6 +33,7 @@ make_table_entry(PyObject *obj, const char *filename, int linenumber)
result->filename = filename;
result->linenumber = linenumber;
result->filename_borrow = NULL;
+ result->linenumber_borrow = 0;
return result;
}