python-peps/pep-0712/index.html

476 lines
43 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 712 Adding a “converter” parameter to dataclasses.field | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0712/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 712 Adding a “converter” parameter to dataclasses.field | peps.python.org'>
<meta property="og:description" content="PEP 557 added dataclasses to the Python stdlib. PEP 681 added dataclass_transform() to help type checkers understand several common dataclass-like libraries, such as attrs, Pydantic, and object relational mapper (ORM) packages such as SQLAlchemy and Dja...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0712/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="PEP 557 added dataclasses to the Python stdlib. PEP 681 added dataclass_transform() to help type checkers understand several common dataclass-like libraries, such as attrs, Pydantic, and object relational mapper (ORM) packages such as SQLAlchemy and Dja...">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 712</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 712 Adding a “converter” parameter to dataclasses.field</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Joshua Cannon &lt;joshdcannon&#32;&#97;t&#32;gmail.com&gt;</dd>
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
<dd class="field-even">Eric V. Smith &lt;eric at trueblade.com&gt;</dd>
<dt class="field-odd">Discussions-To<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-712-adding-a-converter-parameter-to-dataclasses-field/26126">Discourse thread</a></dd>
<dt class="field-even">Status<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</abbr></dd>
<dt class="field-odd">Type<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-even">Created<span class="colon">:</span></dt>
<dd class="field-even">01-Jan-2023</dd>
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
<dd class="field-odd">3.13</dd>
<dt class="field-even">Post-History<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://mail.python.org/archives/list/typing-sig&#64;python.org/thread/NWZQIINJQZDOCZGO6TGCUP2PNW4PEKNY/" title="Typing-SIG thread">27-Dec-2022</a>,
<a class="reference external" href="https://discuss.python.org/t/add-converter-to-dataclass-field/22956" title="Discourse thread">19-Jan-2023</a>,
<a class="reference external" href="https://discuss.python.org/t/pep-712-adding-a-converter-parameter-to-dataclasses-field/26126" title="Discourse thread">23-Apr-2023</a></dd>
<dt class="field-odd">Resolution<span class="colon">:</span></dt>
<dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/pep-712-adding-a-converter-parameter-to-dataclasses-field/26126/98">Discourse message</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#new-converter-parameter">New <code class="docutils literal notranslate"><span class="pre">converter</span></code> parameter</a><ul>
<li><a class="reference internal" href="#example">Example</a></li>
</ul>
</li>
<li><a class="reference internal" href="#impact-on-typing">Impact on typing</a><ul>
<li><a class="reference internal" href="#type-checking-default-and-default-factory">Type-checking <code class="docutils literal notranslate"><span class="pre">default</span></code> and <code class="docutils literal notranslate"><span class="pre">default_factory</span></code></a></li>
<li><a class="reference internal" href="#converter-return-type">Converter return type</a></li>
</ul>
</li>
<li><a class="reference internal" href="#indirection-of-allowable-argument-types">Indirection of allowable argument types</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#just-adding-converter-to-typing-dataclass-transform-s-field-specifiers">Just adding “converter” to <code class="docutils literal notranslate"><span class="pre">typing.dataclass_transform</span></code>s <code class="docutils literal notranslate"><span class="pre">field_specifiers</span></code></a></li>
<li><a class="reference internal" href="#not-converting-default-values">Not converting default values</a></li>
<li><a class="reference internal" href="#automatic-conversion-using-the-field-s-type">Automatic conversion using the fields type</a></li>
<li><a class="reference internal" href="#deducing-the-attribute-type-from-the-return-type-of-the-converter">Deducing the attribute type from the return type of the converter</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<section id="rejection-notice">
<h2><a class="toc-backref" href="#rejection-notice" role="doc-backlink">Rejection Notice</a></h2>
<p>The reasons for the 2024 Steering Council rejection include:</p>
<ul class="simple">
<li>We did not find evidence of a strong consensus that this feature was needed in the standard library, despite some proponents arguing in favor in order to reduce their dependence on third party packages. For those who need such functionality, we think those existing third party libraries such as attrs and Pydantic (which the PEP references) are acceptable alternatives.</li>
<li>This feature seems to us like an accumulation of what could be considered more cruft in the standard library, leading us ever farther away from the “simple” use cases that dataclasses are ideal for.</li>
<li>Reading the “How to Teach This” section of the PEP gives us pause that the pitfalls and gotchas are significant, with a heightened confusion and complexity outweighing any potential benefits.</li>
<li>The PEP seems more focused toward helping type checkers than people using the library.</li>
</ul>
</section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p><a class="pep reference internal" href="../pep-0557/" title="PEP 557 Data Classes">PEP 557</a> added <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a> to the Python stdlib. <a class="pep reference internal" href="../pep-0681/" title="PEP 681 Data Class Transforms">PEP 681</a> added
<a class="reference external" href="https://docs.python.org/3.11/library/typing.html#typing.dataclass_transform" title="(in Python v3.11)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclass_transform()</span></code></a> to help type checkers understand
several common dataclass-like libraries, such as attrs, Pydantic, and object
relational mapper (ORM) packages such as SQLAlchemy and Django.</p>
<p>A common feature other libraries provide over the standard library
implementation is the ability for the library to convert arguments given at
initialization time into the types expected for each field using a
user-provided conversion function.</p>
<p>Therefore, this PEP adds a <code class="docutils literal notranslate"><span class="pre">converter</span></code> parameter to <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.field" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclasses.field()</span></code></a>
(along with the requisite changes to <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.Field" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">dataclasses.Field</span></code></a> and
<a class="reference external" href="https://docs.python.org/3.11/library/typing.html#typing.dataclass_transform" title="(in Python v3.11)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclass_transform()</span></code></a>) to specify the function to use to
convert the input value for each field to the representation to be stored in
the dataclass.</p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>There is no existing, standard way for <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a> or third-party
dataclass-like libraries to support argument conversion in a type-checkable
way. To work around this limitation, library authors/users are forced to choose
to:</p>
<ul class="simple">
<li>Opt-in to a custom Mypy plugin. These plugins help Mypy understand the
conversion semantics, but not other tools.</li>
<li>Shift conversion responsibility onto the caller of the dataclass
constructor. This can make constructing certain dataclasses unnecessarily
verbose and repetitive.</li>
<li>Provide a custom <code class="docutils literal notranslate"><span class="pre">__init__</span></code> which declares “wider” parameter types and
converts them when setting the appropriate attribute. This not only duplicates
the typing annotations between the converter and <code class="docutils literal notranslate"><span class="pre">__init__</span></code>, but also opts
the user out of many of the features <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a> provides.</li>
<li>Provide a custom <code class="docutils literal notranslate"><span class="pre">__init__</span></code> but without meaningful type annotations
for the parameter types requiring conversion.</li>
</ul>
<p>None of these choices are ideal.</p>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>Adding argument conversion semantics is useful and beneficial enough that most
dataclass-like libraries provide support. Adding this feature to the standard
library means more users are able to opt-in to these benefits without requiring
third-party libraries. Additionally third-party libraries are able to clue
type-checkers into their own conversion semantics through added support in
<a class="reference external" href="https://docs.python.org/3.11/library/typing.html#typing.dataclass_transform" title="(in Python v3.11)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclass_transform()</span></code></a>, meaning users of those libraries
benefit as well.</p>
</section>
<section id="specification">
<h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2>
<section id="new-converter-parameter">
<h3><a class="toc-backref" href="#new-converter-parameter" role="doc-backlink">New <code class="docutils literal notranslate"><span class="pre">converter</span></code> parameter</a></h3>
<p>This specification introduces a new parameter named <code class="docutils literal notranslate"><span class="pre">converter</span></code> to the
<a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.field" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclasses.field()</span></code></a> function. If provided, it represents a single-argument
callable used to convert all values when assigning to the associated attribute.</p>
<p>For frozen dataclasses, the converter is only used inside a <code class="docutils literal notranslate"><span class="pre">dataclass</span></code>-synthesized
<code class="docutils literal notranslate"><span class="pre">__init__</span></code> when setting the attribute. For non-frozen dataclasses, the converter
is used for all attribute assignment (E.g. <code class="docutils literal notranslate"><span class="pre">obj.attr</span> <span class="pre">=</span> <span class="pre">value</span></code>), which includes
assignment of default values.</p>
<p>The converter is not used when reading attributes, as the attributes should already
have been converted.</p>
<p>Adding this parameter also implies the following changes:</p>
<ul class="simple">
<li>A <code class="docutils literal notranslate"><span class="pre">converter</span></code> attribute will be added to <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#dataclasses.Field" title="(in Python v3.13)"><code class="xref py py-class docutils literal notranslate"><span class="pre">dataclasses.Field</span></code></a>.</li>
<li><code class="docutils literal notranslate"><span class="pre">converter</span></code> will be added to <a class="reference external" href="https://docs.python.org/3.11/library/typing.html#typing.dataclass_transform" title="(in Python v3.11)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclass_transform()</span></code></a>s
list of supported field specifier parameters.</li>
</ul>
<section id="example">
<h4><a class="toc-backref" href="#example" role="doc-backlink">Example</a></h4>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">str_or_none</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">if</span> <span class="n">x</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="kc">None</span>
<span class="nd">@dataclasses</span><span class="o">.</span><span class="n">dataclass</span>
<span class="k">class</span> <span class="nc">InventoryItem</span><span class="p">:</span>
<span class="c1"># `converter` as a type (including a GenericAlias).</span>
<span class="nb">id</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span><span class="n">converter</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">skus</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">]</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span><span class="n">converter</span><span class="o">=</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="o">...</span><span class="p">])</span>
<span class="c1"># `converter` as a callable.</span>
<span class="n">vendor</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span><span class="n">converter</span><span class="o">=</span><span class="n">str_or_none</span><span class="p">))</span>
<span class="n">names</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="o">...</span><span class="p">]</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span>
<span class="n">converter</span><span class="o">=</span><span class="k">lambda</span> <span class="n">names</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">str</span><span class="o">.</span><span class="n">lower</span><span class="p">,</span> <span class="n">names</span><span class="p">))</span>
<span class="p">)</span> <span class="c1"># Note that lambdas are supported, but discouraged as they are untyped.</span>
<span class="c1"># The default value is also converted; therefore the following is not a</span>
<span class="c1"># type error.</span>
<span class="n">stock_image_path</span><span class="p">:</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">PurePosixPath</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span>
<span class="n">converter</span><span class="o">=</span><span class="n">pathlib</span><span class="o">.</span><span class="n">PurePosixPath</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s2">&quot;assets/unknown.png&quot;</span>
<span class="p">)</span>
<span class="c1"># Default value conversion extends to `default_factory`;</span>
<span class="c1"># therefore the following is also not a type error.</span>
<span class="n">shelves</span><span class="p">:</span> <span class="nb">tuple</span> <span class="o">=</span> <span class="n">dataclasses</span><span class="o">.</span><span class="n">field</span><span class="p">(</span>
<span class="n">converter</span><span class="o">=</span><span class="nb">tuple</span><span class="p">,</span> <span class="n">default_factory</span><span class="o">=</span><span class="nb">list</span>
<span class="p">)</span>
<span class="n">item1</span> <span class="o">=</span> <span class="n">InventoryItem</span><span class="p">(</span>
<span class="s2">&quot;1&quot;</span><span class="p">,</span>
<span class="p">[</span><span class="mi">234</span><span class="p">,</span> <span class="mi">765</span><span class="p">],</span>
<span class="kc">None</span><span class="p">,</span>
<span class="p">[</span><span class="s2">&quot;PYTHON PLUSHIE&quot;</span><span class="p">,</span> <span class="s2">&quot;FLUFFY SNAKE&quot;</span><span class="p">]</span>
<span class="p">)</span>
<span class="c1"># item1&#39;s repr would be (with added newlines for readability):</span>
<span class="c1"># InventoryItem(</span>
<span class="c1"># id=1,</span>
<span class="c1"># skus=(234, 765),</span>
<span class="c1"># vendor=None,</span>
<span class="c1"># names=(&#39;PYTHON PLUSHIE&#39;, &#39;FLUFFY SNAKE&#39;),</span>
<span class="c1"># stock_image_path=PurePosixPath(&#39;assets/unknown.png&#39;),</span>
<span class="c1"># shelves=()</span>
<span class="c1"># )</span>
<span class="c1"># Attribute assignment also participates in conversion.</span>
<span class="n">item1</span><span class="o">.</span><span class="n">skus</span> <span class="o">=</span> <span class="p">[</span><span class="mi">555</span><span class="p">]</span>
<span class="c1"># item1&#39;s skus attribute is now (555,).</span>
</pre></div>
</div>
</section>
</section>
<section id="impact-on-typing">
<h3><a class="toc-backref" href="#impact-on-typing" role="doc-backlink">Impact on typing</a></h3>
<p>A <code class="docutils literal notranslate"><span class="pre">converter</span></code> must be a callable that accepts a single positional argument, and
the parameter type corresponding to this positional argument provides the type
of the the synthesized <code class="docutils literal notranslate"><span class="pre">__init__</span></code> parameter associated with the field.</p>
<p>In other words, the argument provided for the converter parameter must be
compatible with <code class="docutils literal notranslate"><span class="pre">Callable[[T],</span> <span class="pre">X]</span></code> where <code class="docutils literal notranslate"><span class="pre">T</span></code> is the input type for
the converter and <code class="docutils literal notranslate"><span class="pre">X</span></code> is the output type of the converter.</p>
<section id="type-checking-default-and-default-factory">
<h4><a class="toc-backref" href="#type-checking-default-and-default-factory" role="doc-backlink">Type-checking <code class="docutils literal notranslate"><span class="pre">default</span></code> and <code class="docutils literal notranslate"><span class="pre">default_factory</span></code></a></h4>
<p>Because default values are unconditionally converted using <code class="docutils literal notranslate"><span class="pre">converter</span></code>, if
an argument for <code class="docutils literal notranslate"><span class="pre">converter</span></code> is provided alongside either <code class="docutils literal notranslate"><span class="pre">default</span></code> or
<code class="docutils literal notranslate"><span class="pre">default_factory</span></code>, the type of the default (the <code class="docutils literal notranslate"><span class="pre">default</span></code> argument if
provided, otherwise the return value of <code class="docutils literal notranslate"><span class="pre">default_factory</span></code>) should be checked
using the type of the single argument to the <code class="docutils literal notranslate"><span class="pre">converter</span></code> callable.</p>
</section>
<section id="converter-return-type">
<h4><a class="toc-backref" href="#converter-return-type" role="doc-backlink">Converter return type</a></h4>
<p>The return type of the callable must be a type thats compatible with the
fields declared type. This includes the fields type exactly, but can also be
a type thats more specialized (such as a converter returning a <code class="docutils literal notranslate"><span class="pre">list[int]</span></code>
for a field annotated as <code class="docutils literal notranslate"><span class="pre">list</span></code>, or a converter returning an <code class="docutils literal notranslate"><span class="pre">int</span></code> for a
field annotated as <code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">|</span> <span class="pre">str</span></code>).</p>
</section>
</section>
<section id="indirection-of-allowable-argument-types">
<h3><a class="toc-backref" href="#indirection-of-allowable-argument-types" role="doc-backlink">Indirection of allowable argument types</a></h3>
<p>One downside introduced by this PEP is that knowing what argument types are
allowed in the dataclass <code class="docutils literal notranslate"><span class="pre">__init__</span></code> and during attribute assignment is not
immediately obvious from reading the dataclass. The allowable types are defined
by the converter.</p>
<p>This is true when reading code from source, however typing-related aides such
as <code class="docutils literal notranslate"><span class="pre">typing.reveal_type</span></code> and “IntelliSense” in an IDE should make it easy to know
exactly what types are allowed without having to read any source code.</p>
</section>
</section>
<section id="backward-compatibility">
<h2><a class="toc-backref" href="#backward-compatibility" role="doc-backlink">Backward Compatibility</a></h2>
<p>These changes dont introduce any compatibility problems since they
only introduce opt-in new features.</p>
</section>
<section id="security-implications">
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
<p>There are no direct security concerns with these changes.</p>
</section>
<section id="how-to-teach-this">
<h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2>
<p>Documentation and examples explaining the new parameter and behavior will be
added to the relevant sections of the docs site (primarily on
<a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a>) and linked from the <em>Whats New</em> document.</p>
<p>The added documentation/examples will also cover the “common pitfalls” that
users of converters are likely to encounter. Such pitfalls include:</p>
<ul class="simple">
<li>Needing to handle <code class="docutils literal notranslate"><span class="pre">None</span></code>/sentinel values.</li>
<li>Needing to handle values that are already of the correct type.</li>
<li>Avoiding lambdas for converters, as the synthesized <code class="docutils literal notranslate"><span class="pre">__init__</span></code>
parameters type will become <code class="docutils literal notranslate"><span class="pre">Any</span></code>.</li>
<li>Forgetting to convert values in the bodies of user-defined <code class="docutils literal notranslate"><span class="pre">__init__</span></code> in
frozen dataclasses.</li>
<li>Forgetting to convert values in the bodies of user-defined <code class="docutils literal notranslate"><span class="pre">__setattr__</span></code> in
non-frozen dataclasses.</li>
</ul>
<p>Additionally, potentially confusing pattern matching semantics should be covered:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">Point</span><span class="p">:</span>
<span class="n">x</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">converter</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">match</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">&quot;0&quot;</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
<span class="k">case</span> <span class="n">Point</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">&quot;0&quot;</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span> <span class="c1"># Won&#39;t be matched</span>
<span class="o">...</span>
<span class="k">case</span> <span class="n">Point</span><span class="p">():</span> <span class="c1"># Will be matched</span>
<span class="o">...</span>
<span class="k">case</span><span class="w"> </span><span class="k">_</span><span class="p">:</span>
<span class="o">...</span>
</pre></div>
</div>
<p>However its worth noting this behavior is true of any type that does conversion
in its initializer, and type-checkers should be able to catch this pitfall:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">match</span> <span class="nb">int</span><span class="p">(</span><span class="s2">&quot;0&quot;</span><span class="p">):</span>
<span class="k">case</span> <span class="nb">int</span><span class="p">(</span><span class="s2">&quot;0&quot;</span><span class="p">):</span> <span class="c1"># Won&#39;t be matched</span>
<span class="o">...</span>
<span class="k">case</span><span class="w"> </span><span class="k">_</span><span class="p">:</span> <span class="c1"># Will be matched</span>
<span class="o">...</span>
</pre></div>
</div>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>The attrs library <a class="reference external" href="https://www.attrs.org/en/21.2.0/examples.html#conversion">already includes</a> a <code class="docutils literal notranslate"><span class="pre">converter</span></code>
parameter exhibiting the same converter semantics (converting in the
initializer and on attribute setting) when using the <code class="docutils literal notranslate"><span class="pre">&#64;define</span></code> class
decorator.</p>
<p>CPython support is implemented on <a class="reference external" href="https://github.com/thejcannon/cpython/tree/converter">a branch in the authors fork</a>.</p>
</section>
<section id="rejected-ideas">
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
<section id="just-adding-converter-to-typing-dataclass-transform-s-field-specifiers">
<h3><a class="toc-backref" href="#just-adding-converter-to-typing-dataclass-transform-s-field-specifiers" role="doc-backlink">Just adding “converter” to <code class="docutils literal notranslate"><span class="pre">typing.dataclass_transform</span></code>s <code class="docutils literal notranslate"><span class="pre">field_specifiers</span></code></a></h3>
<p>The idea of isolating this addition to
<a class="reference external" href="https://docs.python.org/3.11/library/typing.html#typing.dataclass_transform" title="(in Python v3.11)"><code class="xref py py-func docutils literal notranslate"><span class="pre">dataclass_transform()</span></code></a> was briefly
<a class="reference external" href="https://mail.python.org/archives/list/typing-sig&#64;python.org/thread/NWZQIINJQZDOCZGO6TGCUP2PNW4PEKNY/">discussed on Typing-SIG</a> where it was suggested
to broaden this to <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a> more generally.</p>
<p>Additionally, adding this to <a class="reference external" href="https://docs.python.org/3/library/dataclasses.html#module-dataclasses" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">dataclasses</span></code></a> ensures anyone can reap the
benefits without requiring additional libraries.</p>
</section>
<section id="not-converting-default-values">
<h3><a class="toc-backref" href="#not-converting-default-values" role="doc-backlink">Not converting default values</a></h3>
<p>There are pros and cons with both converting and not converting default values.
Leaving default values as-is allows type-checkers and dataclass authors to
expect that the type of the default matches the type of the field. However,
converting default values has three large advantages:</p>
<ol class="arabic simple">
<li>Consistency. Unconditionally converting all values that are assigned to the
attribute, involves fewer “special rules” that users must remember.</li>
<li>Simpler defaults. Allowing the default value to have the same type as
user-provided values means dataclass authors get the same conveniences as
their callers.</li>
<li>Compatibility with attrs. Attrs unconditionally uses the converter to
convert default values.</li>
</ol>
</section>
<section id="automatic-conversion-using-the-field-s-type">
<h3><a class="toc-backref" href="#automatic-conversion-using-the-field-s-type" role="doc-backlink">Automatic conversion using the fields type</a></h3>
<p>One idea could be to allow the type of the field specified (e.g. <code class="docutils literal notranslate"><span class="pre">str</span></code> or
<code class="docutils literal notranslate"><span class="pre">int</span></code>) to be used as a converter for each argument provided.
<a class="reference external" href="https://docs.pydantic.dev/usage/models/#data-conversion">Pydantics data conversion</a> has semantics which
appear to be similar to this approach.</p>
<p>This works well for fairly simple types, but leads to ambiguity in expected
behavior for complex types such as generics. E.g. For <code class="docutils literal notranslate"><span class="pre">tuple[int,</span> <span class="pre">...]</span></code> it is
ambiguous if the converter is supposed to simply convert an iterable to a tuple,
or if it is additionally supposed to convert each element type to <code class="docutils literal notranslate"><span class="pre">int</span></code>. Or
for <code class="docutils literal notranslate"><span class="pre">int</span> <span class="pre">|</span> <span class="pre">None</span></code>, which isnt callable.</p>
</section>
<section id="deducing-the-attribute-type-from-the-return-type-of-the-converter">
<h3><a class="toc-backref" href="#deducing-the-attribute-type-from-the-return-type-of-the-converter" role="doc-backlink">Deducing the attribute type from the return type of the converter</a></h3>
<p>Another idea would be to allow the user to omit the attributes type annotation
if providing a <code class="docutils literal notranslate"><span class="pre">field</span></code> with a <code class="docutils literal notranslate"><span class="pre">converter</span></code> argument. Although this would
reduce the common repetition this PEP introduces (e.g. <code class="docutils literal notranslate"><span class="pre">x:</span> <span class="pre">str</span> <span class="pre">=</span> <span class="pre">field(converter=str)</span></code>),
it isnt clear how to best support this while maintaining the current dataclass
semantics (namely, that the attribute order is preserved for things like the
synthesized <code class="docutils literal notranslate"><span class="pre">__init__</span></code>, or <code class="docutils literal notranslate"><span class="pre">dataclasses.fields</span></code>). This is because there isnt
an easy way in Python (today) to get the annotation-only attributes interspersed
with un-annotated attributes in the order they were defined.</p>
<p>A sentinel annotation could be applied (e.g. <code class="docutils literal notranslate"><span class="pre">x:</span> <span class="pre">FromConverter</span> <span class="pre">=</span> <span class="pre">...</span></code>),
however this breaks a fundamental assumption of type annotations.</p>
<p>Lastly, this is feasible if <em>all</em> fields (including those without a converter)
were assigned to <code class="docutils literal notranslate"><span class="pre">dataclasses.field</span></code>, which means the class own namespace
holds the order, however this trades repetition of type+converter with
repetition of field assignment. The end result is no gain or loss of repetition,
but with the added complexity of dataclasses semantics.</p>
<p>This PEP doesnt suggest it cant or shouldnt be done. Just that it isnt
included in this PEP.</p>
</section>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0712.rst">https://github.com/python/peps/blob/main/peps/pep-0712.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0712.rst">2024-06-06 23:57:11 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#rejection-notice">Rejection Notice</a></li>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#specification">Specification</a><ul>
<li><a class="reference internal" href="#new-converter-parameter">New <code class="docutils literal notranslate"><span class="pre">converter</span></code> parameter</a><ul>
<li><a class="reference internal" href="#example">Example</a></li>
</ul>
</li>
<li><a class="reference internal" href="#impact-on-typing">Impact on typing</a><ul>
<li><a class="reference internal" href="#type-checking-default-and-default-factory">Type-checking <code class="docutils literal notranslate"><span class="pre">default</span></code> and <code class="docutils literal notranslate"><span class="pre">default_factory</span></code></a></li>
<li><a class="reference internal" href="#converter-return-type">Converter return type</a></li>
</ul>
</li>
<li><a class="reference internal" href="#indirection-of-allowable-argument-types">Indirection of allowable argument types</a></li>
</ul>
</li>
<li><a class="reference internal" href="#backward-compatibility">Backward Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#just-adding-converter-to-typing-dataclass-transform-s-field-specifiers">Just adding “converter” to <code class="docutils literal notranslate"><span class="pre">typing.dataclass_transform</span></code>s <code class="docutils literal notranslate"><span class="pre">field_specifiers</span></code></a></li>
<li><a class="reference internal" href="#not-converting-default-values">Not converting default values</a></li>
<li><a class="reference internal" href="#automatic-conversion-using-the-field-s-type">Automatic conversion using the fields type</a></li>
<li><a class="reference internal" href="#deducing-the-attribute-type-from-the-return-type-of-the-converter">Deducing the attribute type from the return type of the converter</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0712.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>