<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Python-3.15 on Opensource internals</title><link>https://articles.9j.nz/tags/python-3.15/</link><description>Posts filed under Python-3.15 on Opensource internals.</description><generator>Hugo</generator><language>en-US</language><managingEditor>Jay Hubbert</managingEditor><lastBuildDate>Thu, 15 Oct 2026 09:00:00 +0000</lastBuildDate><atom:link href="https://articles.9j.nz/tags/python-3.15/index.xml" rel="self" type="application/rss+xml"/><item><title>What's New in Python 3.15</title><link>https://articles.9j.nz/posts/whats-new-in-python-3-15/</link><pubDate>Thu, 15 Oct 2026 09:00:00 +0000</pubDate><dc:creator>Jay Hubbert</dc:creator><guid isPermaLink="true">https://articles.9j.nz/posts/whats-new-in-python-3-15/</guid><category>python</category><category>cpython</category><category>python-3.15</category><description>A tour of Python 3.15's headline changes: lazy imports, frozendict, sentinel, UTF-8 by default, unpacking in comprehensions, the profiling package, Tachyon, and more.</description><content:encoded>&lt;p&gt;Python 3.15 was released on 9 October 2026. It is a &amp;ldquo;batteries included&amp;rdquo;&#10;release in the best sense: a handful of genuinely new language features,&#10;a new immutable mapping type, a new sentinel idiom, UTF-8 everywhere by&#10;default, and a serious upgrade to the profiling story. This post walks&#10;through the changes that matter most, with runnable examples verified&#10;against the CPython 3.15 source tree.&lt;/p&gt;&#10;&lt;h2 id="lazy-imports-pep-810"&gt;Lazy imports (PEP 810)&lt;/h2&gt;&#10;&lt;p&gt;Large applications pay for imports they never use. Python 3.15 adds an&#10;explicit &lt;code&gt;lazy&lt;/code&gt; soft keyword that defers loading until the name is first&#10;accessed, while keeping the import at the top of the file:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Starting up...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# neither module is loaded yet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;#34;key&amp;#34;: &amp;#34;value&amp;#34;}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# json loads here&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# pathlib loads here&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The &lt;code&gt;lazy&lt;/code&gt; keyword works with both &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;from ... import&lt;/code&gt;.&#10;Failure is deferred too: if a lazily imported module does not exist,&#10;the exception is raised at the point of first use, with a traceback that&#10;points at both the access site and the original import statement.&lt;/p&gt;&#10;&lt;p&gt;There are restrictions. Lazy imports are only allowed at module scope:&#10;inside a function, class body, or &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;/&lt;code&gt;finally&lt;/code&gt; block they&#10;raise &lt;code&gt;SyntaxError&lt;/code&gt;. Star imports and future imports cannot be lazy:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;module&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="c1"&gt;# SyntaxError&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt; &lt;span class="c1"&gt;# SyntaxError&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;For code that must stay compatible with older Pythons, a module can&#10;declare &lt;code&gt;__lazy_modules__&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;__lazy_modules__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pathlib&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt; &lt;span class="c1"&gt;# treated as lazy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt; &lt;span class="c1"&gt;# still eager&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Global control is available through &lt;code&gt;-X lazy_imports=all|normal&lt;/code&gt;, the&#10;&lt;code&gt;PYTHON_LAZY_IMPORTS&lt;/code&gt; environment variable, and&#10;&lt;code&gt;sys.set_lazy_imports()&lt;/code&gt; / &lt;code&gt;sys.get_lazy_imports()&lt;/code&gt;. A filter callable&#10;can decide per import:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;myapp_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromlist&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;imported&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;myapp.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_lazy_imports_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myapp_filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_lazy_imports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;The proxy object is exposed as &lt;code&gt;types.LazyImportType&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="frozendict-pep-814"&gt;&lt;code&gt;frozendict&lt;/code&gt; (PEP 814)&lt;/h2&gt;&#10;&lt;p&gt;A new immutable mapping joins the builtins. &lt;code&gt;frozendict&lt;/code&gt; is not a&#10;subclass of &lt;code&gt;dict&lt;/code&gt; &amp;ndash; it inherits directly from &lt;code&gt;object&lt;/code&gt; &amp;ndash; and is&#10;hashable when its keys and values are hashable:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;frozendict&amp;#39;&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="n"&gt;assignment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frozendict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Comparison is order-independent. &lt;code&gt;copy&lt;/code&gt;, &lt;code&gt;decimal&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;marshal&lt;/code&gt;,&#10;&lt;code&gt;pickle&lt;/code&gt;, &lt;code&gt;pprint&lt;/code&gt;, &lt;code&gt;xml.etree.ElementTree&lt;/code&gt; and others now accept&#10;&lt;code&gt;frozendict&lt;/code&gt;; &lt;code&gt;eval&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt; accept it for globals, and &lt;code&gt;type&lt;/code&gt; and&#10;&lt;code&gt;str.maketrans&lt;/code&gt; accept it for the dict argument.&lt;/p&gt;&#10;&lt;p&gt;Code that checks &lt;code&gt;isinstance(arg, dict)&lt;/code&gt; should be widened to&#10;&lt;code&gt;isinstance(arg, (dict, frozendict))&lt;/code&gt;, or to&#10;&lt;code&gt;isinstance(arg, collections.abc.Mapping)&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="sentinel-pep-661"&gt;&lt;code&gt;sentinel&lt;/code&gt; (PEP 661)&lt;/h2&gt;&#10;&lt;p&gt;A concise way to create unique sentinel values:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;builtins&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sentinel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MISSING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sentinel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MISSING&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Sentinel objects preserve identity under &lt;code&gt;copy.copy&lt;/code&gt;, can be used in type&#10;expressions with &lt;code&gt;|&lt;/code&gt;, and are picklable when reachable as a module&#10;attribute by name. They give a standard answer to the long-standing&#10;&amp;ldquo;how do I spell a unique default value&amp;rdquo; question.&lt;/p&gt;&#10;&lt;h2 id="utf-8-by-default-pep-686"&gt;UTF-8 by default (PEP 686)&lt;/h2&gt;&#10;&lt;p&gt;I/O without an explicit &lt;code&gt;encoding&lt;/code&gt; now uses UTF-8 instead of the locale&#10;encoding:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flying-circus.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# UTF-8, regardless of the system locale&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;For maximum compatibility across versions, keep passing &lt;code&gt;encoding=&lt;/code&gt;&#10;explicitly. The opt-in encoding warning (available since earlier&#10;versions) helps find affected call sites. To restore the old behaviour,&#10;set &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; or pass &lt;code&gt;-X utf8=0&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="unpacking-in-comprehensions-pep-798"&gt;Unpacking in comprehensions (PEP 798)&lt;/h2&gt;&#10;&lt;p&gt;List, set and dict comprehensions, plus generator expressions, now&#10;support &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;**&lt;/code&gt; unpacking:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;lists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;L&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# [1, 2, 3, 4, 5]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sets&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# {1, 2, 3, 4}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dicts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;b&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dicts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# {&amp;#34;a&amp;#34;: 3, &amp;#34;b&amp;#34;: 2}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;This is a direct alternative to nested comprehensions,&#10;&lt;code&gt;itertools.chain&lt;/code&gt; and &lt;code&gt;itertools.chain.from_iterable&lt;/code&gt;. Async generator&#10;expressions are supported too.&lt;/p&gt;&#10;&lt;h2 id="profiling-the-profiling-package-and-tachyon"&gt;Profiling: the &lt;code&gt;profiling&lt;/code&gt; package and Tachyon&lt;/h2&gt;&#10;&lt;p&gt;Python&amp;rsquo;s profiling tools moved under a single namespace (PEP 799):&#10;&lt;code&gt;profiling.tracing&lt;/code&gt; (the old &lt;code&gt;cProfile&lt;/code&gt;) and &lt;code&gt;profiling.sampling&lt;/code&gt;&#10;(Tachyon). &lt;code&gt;cProfile&lt;/code&gt; remains as a backwards-compatible alias; the&#10;&lt;code&gt;profile&lt;/code&gt; module is deprecated and will be removed in 3.17.&lt;/p&gt;&#10;&lt;p&gt;Tachyon is a statistical sampling profiler that attaches to a running&#10;process by PID without restarting it, with sampling rates up to 1,000,000&#10;Hz. It supports wall-clock, CPU, GIL-holding and exception-handling&#10;modes, thread-aware and async-aware profiling, and many output formats:&#10;pstats, collapsed stacks, interactive HTML flame graphs, Gecko format,&#10;heatmaps, and a live TUI.&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;bash&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m profiling.sampling run script.py --flamegraph&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m profiling.sampling attach &lt;span class="m"&gt;12345&lt;/span&gt; --mode cpu --collapsed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m profiling.sampling dump &lt;span class="m"&gt;12345&lt;/span&gt; --async-aware&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;h2 id="stable-abi-for-free-threaded-builds-pep-803-793-820"&gt;Stable ABI for free-threaded builds (PEP 803, 793, 820)&lt;/h2&gt;&#10;&lt;p&gt;C extensions targeting the stable ABI can now be compiled for&#10;free-threaded builds (&lt;code&gt;abi3t&lt;/code&gt;), making a single wheel compatible with&#10;free-threaded CPython. This needs API introduced in earlier versions&#10;(negative &lt;code&gt;basicsize&lt;/code&gt;, &lt;code&gt;PyObject_GetTypeData&lt;/code&gt;) and the new&#10;&lt;code&gt;PyModExport_*&lt;/code&gt; hook plus &lt;code&gt;PySlot&lt;/code&gt;. Build tools such as meson-python,&#10;scikit-build-core and Maturin already support &lt;code&gt;abi3t&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="frame-pointers-by-default-pep-831"&gt;Frame pointers by default (PEP 831)&lt;/h2&gt;&#10;&lt;p&gt;CPython is now built with &lt;code&gt;-fno-omit-frame-pointer&lt;/code&gt; and&#10;&lt;code&gt;-mno-omit-leaf-frame-pointer&lt;/code&gt; on supporting platforms, giving system&#10;profilers, debuggers and eBPF tools an unbroken native stack. The flags&#10;propagate through &lt;code&gt;sysconfig&lt;/code&gt;, so extension modules inherit them.&lt;/p&gt;&#10;&lt;h2 id="package-startup-files-pep-829"&gt;Package startup files (PEP 829)&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;.pth&lt;/code&gt; files&amp;rsquo; &lt;code&gt;import&lt;/code&gt; lines are silently deprecated. Python 3.15&#10;introduces &lt;code&gt;.start&lt;/code&gt; files containing &lt;code&gt;pkg.mod:callable&lt;/code&gt; entry points&#10;that are located and called at startup. When a matching &lt;code&gt;.start&lt;/code&gt; file&#10;exists, &lt;code&gt;import&lt;/code&gt; lines in &lt;code&gt;.pth&lt;/code&gt; files are ignored. &lt;code&gt;site.StartupState&lt;/code&gt;&#10;batches startup processing so all path extensions apply before any&#10;startup code runs.&lt;/p&gt;&#10;&lt;h2 id="improved-error-messages"&gt;Improved error messages&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;AttributeError&lt;/code&gt; now suggests attributes reachable through a member:&lt;/p&gt;&#10;&lt;div class="code-block"&gt;&#10; &lt;div class="code-block-head"&gt;&#10; &lt;span class="code-lang"&gt;python&lt;/span&gt;&#10; &lt;button class="code-copy" type="button" data-copy&gt;Copy&lt;/button&gt;&#10; &lt;/div&gt;&#10; &lt;div class="code-block-body"&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# AttributeError: &amp;#39;Container&amp;#39; object has no attribute &amp;#39;area&amp;#39;.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Did you mean &amp;#39;.inner.area&amp;#39; instead of &amp;#39;.area&amp;#39;?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;For builtin types, unknown names are matched against a table of common&#10;methods from other languages and the Python equivalent is suggested&#10;(&lt;code&gt;push&lt;/code&gt; -&amp;gt; &lt;code&gt;append&lt;/code&gt;, &lt;code&gt;toUpperCase&lt;/code&gt; -&amp;gt; &lt;code&gt;upper&lt;/code&gt;). &lt;code&gt;delattr&lt;/code&gt; failures&#10;get &amp;ldquo;did you mean&amp;rdquo; suggestions too.&lt;/p&gt;&#10;&lt;h2 id="other-notable-changes"&gt;Other notable changes&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;bytearray.take_bytes(n=None)&lt;/code&gt; moves bytes out of a buffer without&#10;copying, useful for protocol parsing and buffering.&lt;/li&gt;&#10;&lt;li&gt;New &lt;code&gt;math.integer&lt;/code&gt; module exposing integer mathematical functions.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;asyncio.TaskGroup.cancel()&lt;/code&gt; for early termination of a task group.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;slice&lt;/code&gt; is now a generic type; &lt;code&gt;memoryview&lt;/code&gt; supports complex types.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;base64&lt;/code&gt; gained &lt;code&gt;pad&lt;/code&gt;, &lt;code&gt;padded&lt;/code&gt;, &lt;code&gt;wrapcol&lt;/code&gt;, &lt;code&gt;ignorechars&lt;/code&gt; and&#10;&lt;code&gt;canonical&lt;/code&gt; parameters across its functions.&lt;/li&gt;&#10;&lt;li&gt;The interactive REPL has coloured tab completion and suggests module&#10;attributes in &lt;code&gt;from ... import&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Per-module import locks are now acquired hierarchically, fixing a&#10;classic &lt;code&gt;pkg.sub&lt;/code&gt; / &lt;code&gt;pkg.sub.mod&lt;/code&gt; deadlock.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="porting-notes"&gt;Porting notes&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;__cached__&lt;/code&gt; is no longer set or consulted by the import system;&#10;use &lt;code&gt;__spec__.cached&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;profile&lt;/code&gt; is deprecated; migrate to &lt;code&gt;profiling&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Relying on the locale default encoding is now a portability hazard:&#10;pass &lt;code&gt;encoding=&lt;/code&gt; explicitly or set &lt;code&gt;PYTHONUTF8=0&lt;/code&gt; during migration.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;&#10;&lt;p&gt;Python 3.15&amp;rsquo;s theme is &amp;ldquo;make the common cases cheaper and the hard&#10;cases possible&amp;rdquo;: deferred imports for startup, an immutable mapping for&#10;shared state, a standard sentinel idiom, UTF-8 as the unambiguous&#10;default, and a profiler you can attach to a live process. Upgrading is&#10;mostly a matter of running your test suite and fixing the handful of&#10;encoding and import assumptions the new defaults expose.&lt;/p&gt;&#10;</content:encoded></item></channel></rss>