476 lines
43 KiB
HTML
476 lines
43 KiB
HTML
|
||
<!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> » </li>
|
||
<li><a href="../pep-0000/">PEP Index</a> » </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 <joshdcannon at gmail.com></dd>
|
||
<dt class="field-even">Sponsor<span class="colon">:</span></dt>
|
||
<dd class="field-even">Eric V. Smith <eric at trueblade.com></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@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 field’s 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">-></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">"assets/unknown.png"</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">"1"</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">"PYTHON PLUSHIE"</span><span class="p">,</span> <span class="s2">"FLUFFY SNAKE"</span><span class="p">]</span>
|
||
<span class="p">)</span>
|
||
<span class="c1"># item1'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=('PYTHON PLUSHIE', 'FLUFFY SNAKE'),</span>
|
||
<span class="c1"># stock_image_path=PurePosixPath('assets/unknown.png'),</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'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 that’s compatible with the
|
||
field’s declared type. This includes the field’s type exactly, but can also be
|
||
a type that’s 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 don’t 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>What’s 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>
|
||
parameter’s 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">"0"</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">"0"</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'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 it’s 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">"0"</span><span class="p">):</span>
|
||
<span class="k">case</span> <span class="nb">int</span><span class="p">(</span><span class="s2">"0"</span><span class="p">):</span> <span class="c1"># Won'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">@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 author’s 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@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 field’s 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">Pydantic’s 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 isn’t 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 attribute’s 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 isn’t 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 isn’t
|
||
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 doesn’t suggest it can’t or shouldn’t be done. Just that it isn’t
|
||
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 field’s 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> |