python-peps/pep-0416/index.html

350 lines
23 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 416 Add a frozendict builtin type | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0416/">
<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 416 Add a frozendict builtin type | peps.python.org'>
<meta property="og:description" content="Add a new frozendict builtin type.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0416/">
<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="Add a new frozendict builtin type.">
<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 416</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 416 Add a frozendict builtin type</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Victor Stinner &lt;vstinner&#32;&#97;t&#32;python.org&gt;</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">29-Feb-2012</dd>
<dt class="field-odd">Python-Version<span class="colon">:</span></dt>
<dd class="field-odd">3.3</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="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#constraints">Constraints</a></li>
<li><a class="reference internal" href="#implementation">Implementation</a></li>
<li><a class="reference internal" href="#recipe-hashable-dict">Recipe: hashable dict</a></li>
<li><a class="reference internal" href="#objections">Objections</a></li>
<li><a class="reference internal" href="#alternative-dictproxy">Alternative: dictproxy</a></li>
<li><a class="reference internal" href="#existing-implementations">Existing implementations</a></li>
<li><a class="reference internal" href="#links">Links</a></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>Im rejecting this PEP. A number of reasons (not exhaustive):</p>
<ul class="simple">
<li>According to Raymond Hettinger, use of frozendict is low. Those
that do use it tend to use it as a hint only, such as declaring
global or class-level “constants”: they arent really immutable,
since anyone can still assign to the name.</li>
<li>There are existing idioms for avoiding mutable default values.</li>
<li>The potential of optimizing code using frozendict in PyPy is
unsure; a lot of other things would have to change first. The same
holds for compile-time lookups in general.</li>
<li>Multiple threads can agree by convention not to mutate a shared
dict, theres no great need for enforcement. Multiple processes
cant share dicts.</li>
<li>Adding a security sandbox written in Python, even with a limited
scope, is frowned upon by many, due to the inherent difficulty with
ever proving that the sandbox is actually secure. Because of this
we wont be adding one to the stdlib any time soon, so this use
case falls outside the scope of a PEP.</li>
</ul>
<p>On the other hand, exposing the existing read-only dict proxy as a
built-in type sounds good to me. (It would need to be changed to
allow calling the constructor.) GvR.</p>
<p><strong>Update</strong> (2012-04-15): A new <code class="docutils literal notranslate"><span class="pre">MappingProxyType</span></code> type was added to the types
module of Python 3.3.</p>
</section>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>Add a new frozendict builtin type.</p>
</section>
<section id="rationale">
<h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2>
<p>A frozendict is a read-only mapping: a key cannot be added nor removed, and a
key is always mapped to the same value. However, frozendict values can be not
hashable. A frozendict is hashable if and only if all values are hashable.</p>
<p>Use cases:</p>
<ul class="simple">
<li>Immutable global variable like a default configuration.</li>
<li>Default value of a function parameter. Avoid the issue of mutable default
arguments.</li>
<li>Implement a cache: frozendict can be used to store function keywords.
frozendict can be used as a key of a mapping or as a member of set.</li>
<li>frozendict avoids the need of a lock when the frozendict is shared
by multiple threads or processes, especially hashable frozendict. It would
also help to prohibe coroutines (generators + greenlets) to modify the
global state.</li>
<li>frozendict lookup can be done at compile time instead of runtime because the
mapping is read-only. frozendict can be used instead of a preprocessor to
remove conditional code at compilation, like code specific to a debug build.</li>
<li>frozendict helps to implement read-only object proxies for security modules.
For example, it would be possible to use frozendict type for __builtins__
mapping or type.__dict__. This is possible because frozendict is compatible
with the PyDict C API.</li>
<li>frozendict avoids the need of a read-only proxy in some cases. frozendict is
faster than a proxy because getting an item in a frozendict is a fast lookup
whereas a proxy requires a function call.</li>
</ul>
</section>
<section id="constraints">
<h2><a class="toc-backref" href="#constraints" role="doc-backlink">Constraints</a></h2>
<ul class="simple">
<li>frozendict has to implement the Mapping abstract base class</li>
<li>frozendict keys and values can be unorderable</li>
<li>a frozendict is hashable if all keys and values are hashable</li>
<li>frozendict hash does not depend on the items creation order</li>
</ul>
</section>
<section id="implementation">
<h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2>
<ul class="simple">
<li>Add a PyFrozenDictObject structure based on PyDictObject with an extra
“Py_hash_t hash;” field</li>
<li>frozendict.__hash__() is implemented using hash(frozenset(self.items())) and
caches the result in its private hash attribute</li>
<li>Register frozendict as a collections.abc.Mapping</li>
<li>frozendict can be used with PyDict_GetItem(), but PyDict_SetItem() and
PyDict_DelItem() raise a TypeError</li>
</ul>
</section>
<section id="recipe-hashable-dict">
<h2><a class="toc-backref" href="#recipe-hashable-dict" role="doc-backlink">Recipe: hashable dict</a></h2>
<p>To ensure that a frozendict is hashable, values can be checked
before creating the frozendict:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">itertools</span>
<span class="k">def</span> <span class="nf">hashabledict</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">):</span>
<span class="c1"># ensure that all values are hashable</span>
<span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">itertools</span><span class="o">.</span><span class="n">chain</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">kw</span><span class="o">.</span><span class="n">items</span><span class="p">()):</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="p">(</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">,</span> <span class="nb">float</span><span class="p">,</span> <span class="nb">frozenset</span><span class="p">,</span> <span class="nb">complex</span><span class="p">)):</span>
<span class="c1"># avoid the compute the hash (which may be slow) for builtin</span>
<span class="c1"># types known to be hashable for any value</span>
<span class="k">continue</span>
<span class="nb">hash</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="c1"># don&#39;t check the key: frozendict already checks the key</span>
<span class="k">return</span> <span class="n">frozendict</span><span class="o">.</span><span class="fm">__new__</span><span class="p">(</span><span class="bp">cls</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="objections">
<h2><a class="toc-backref" href="#objections" role="doc-backlink">Objections</a></h2>
<p><em>namedtuple may fit the requirements of a frozendict.</em></p>
<p>A namedtuple is not a mapping, it does not implement the Mapping abstract base
class.</p>
<p><em>frozendict can be implemented in Python using descriptors” and “frozendict
just need to be practically constant.</em></p>
<p>If frozendict is used to harden Python (security purpose), it must be
implemented in C. A type implemented in C is also faster.</p>
<p><em>The</em> <a class="pep reference internal" href="../pep-0351/" title="PEP 351 The freeze protocol">PEP 351</a> <em>was rejected.</em></p>
<p>The <a class="pep reference internal" href="../pep-0351/" title="PEP 351 The freeze protocol">PEP 351</a> tries to freeze an object and so may convert a mutable object to an
immutable object (using a different type). frozendict doesnt convert anything:
hash(frozendict) raises a TypeError if a value is not hashable. Freezing an
object is not the purpose of this PEP.</p>
</section>
<section id="alternative-dictproxy">
<h2><a class="toc-backref" href="#alternative-dictproxy" role="doc-backlink">Alternative: dictproxy</a></h2>
<p>Python has a builtin dictproxy type used by type.__dict__ getter descriptor.
This type is not public. dictproxy is a read-only view of a dictionary, but it
is not read-only mapping. If a dictionary is modified, the dictproxy is also
modified.</p>
<p>dictproxy can be used using ctypes and the Python C API, see for example the
<a class="reference external" href="http://code.activestate.com/recipes/576540/">make dictproxy object via ctypes.pythonapi and type() (Python recipe 576540)</a>
by Ikkei Shimomura. The recipe contains a test checking that a dictproxy is
“mutable” (modify the dictionary linked to the dictproxy).</p>
<p>However dictproxy can be useful in some cases, where its mutable property is
not an issue, to avoid a copy of the dictionary.</p>
</section>
<section id="existing-implementations">
<h2><a class="toc-backref" href="#existing-implementations" role="doc-backlink">Existing implementations</a></h2>
<p>Whitelist approach.</p>
<ul class="simple">
<li><a class="reference external" href="http://code.activestate.com/recipes/498072/">Implementing an Immutable Dictionary (Python recipe 498072)</a> by Aristotelis Mikropoulos.
Similar to frozendict except that it is not truly read-only: it is possible
to access to this private internal dict. It does not implement __hash__ and
has an implementation issue: it is possible to call again __init__() to
modify the mapping.</li>
<li>PyWebmail contains an ImmutableDict type: <a class="reference external" href="http://pywebmail.cvs.sourceforge.net/viewvc/pywebmail/webmail/webmail/utils/ImmutableDict.py?revision=1.2&amp;view=markup">webmail.utils.ImmutableDict</a>.
It is hashable if keys and values are hashable. It is not truly read-only:
its internal dict is a public attribute.</li>
<li>remember project: <a class="reference external" href="https://bitbucket.org/mikegraham/remember/src/tip/remember/dicts.py">remember.dicts.FrozenDict</a>.
It is used to implement a cache: FrozenDict is used to store function callbacks.
FrozenDict may be hashable. It has an extra supply_dict() class method to
create a FrozenDict from a dict without copying the dict: store the dict as
the internal dict. Implementation issue: __init__() can be called to modify
the mapping and the hash may differ depending on item creation order. The
mapping is not truly read-only: the internal dict is accessible in Python.</li>
</ul>
<p>Blacklist approach: inherit from dict and override write methods to raise an
exception. It is not truly read-only: it is still possible to call dict methods
on such “frozen dictionary” to modify it.</p>
<ul class="simple">
<li>brownie: <a class="reference external" href="https://github.com/DasIch/brownie/blob/HEAD/brownie/datastructures/mappings.py">brownie.datastructures.ImmutableDict</a>.
It is hashable if keys and values are hashable. werkzeug project has the
same code: <a class="reference external" href="https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/datastructures.py">werkzeug.datastructures.ImmutableDict</a>.
ImmutableDict is used for global constant (configuration options). The Flask
project uses ImmutableDict of werkzeug for its default configuration.</li>
<li>SQLAlchemy project: <a class="reference external" href="http://hg.sqlalchemy.org/sqlalchemy/file/tip/lib/sqlalchemy/util/_collections.py">sqlalchemy.util.immutabledict</a>.
It is not hashable and has an extra method: union(). immutabledict is used
for the default value of parameter of some functions expecting a mapping.
Example: mapper_args=immutabledict() in SqlSoup.map().</li>
<li><a class="reference external" href="http://code.activestate.com/recipes/414283/">Frozen dictionaries (Python recipe 414283)</a>
by Oren Tirosh. It is hashable if keys and values are hashable. Included in
the following projects:<ul>
<li>lingospot: <a class="reference external" href="http://code.google.com/p/lingospot/source/browse/trunk/frozendict/frozendict.py">frozendict/frozendict.py</a></li>
<li>factor-graphics: frozendict type in <a class="reference external" href="https://github.com/ih/factor-graphics/blob/41006fb71a09377445cc140489da5ce8eeb9c8b1/python/fglib/util_ext_frozendict.py">python/fglib/util_ext_frozendict.py</a></li>
</ul>
</li>
<li>The gsakkis-utils project written by George Sakkis includes a frozendict
type: <a class="reference external" href="http://code.google.com/p/gsakkis-utils/source/browse/trunk/datastructs/frozendict.py">datastructs.frozendict</a></li>
<li>characters: <a class="reference external" href="https://github.com/JasonGross/characters/blob/15a2af5f7861cd33a0dbce70f1569cda74e9a1e3/scripts/python/frozendict.py#L1">scripts/python/frozendict.py</a>.
It is hashable. __init__() sets __init__ to None.</li>
<li>Old NLTK (1.x): <a class="reference external" href="http://nltk.googlecode.com/svn/trunk/nltk-old/src/nltk/util.py">nltk.util.frozendict</a>. Keys and
values must be hashable. __init__() can be called twice to modify the
mapping. frozendict is used to “freeze” an object.</li>
</ul>
<p>Hashable dict: inherit from dict and just add an __hash__ method.</p>
<ul class="simple">
<li><a class="reference external" href="https://bitbucket.org/pypy/pypy/src/1f49987cc2fe/pypy/rpython/lltypesystem/lltype.py#cl-86">pypy.rpython.lltypesystem.lltype.frozendict</a>.
It is hashable but dont deny modification of the mapping.</li>
<li>factor-graphics: hashabledict type in <a class="reference external" href="https://github.com/ih/factor-graphics/blob/41006fb71a09377445cc140489da5ce8eeb9c8b1/python/fglib/util_ext_frozendict.py">python/fglib/util_ext_frozendict.py</a></li>
</ul>
</section>
<section id="links">
<h2><a class="toc-backref" href="#links" role="doc-backlink">Links</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://bugs.python.org/issue14162">Issue #14162: PEP 416: Add a builtin frozendict type</a></li>
<li>PEP 412: Key-Sharing Dictionary
(<a class="reference external" href="http://bugs.python.org/issue13903">issue #13903</a>)</li>
<li><a class="pep reference internal" href="../pep-0351/" title="PEP 351 The freeze protocol">PEP 351</a>: The freeze protocol</li>
<li><a class="reference external" href="http://www.cs.toronto.edu/~tijmen/programming/immutableDictionaries.html">The case for immutable dictionaries; and the central misunderstanding of
PEP 351</a></li>
<li><a class="reference external" href="http://code.activestate.com/recipes/576540/">make dictproxy object via ctypes.pythonapi and type() (Python recipe
576540)</a> by Ikkei Shimomura.</li>
<li>Python security modules implementing read-only object proxies using a C
extension:<ul>
<li><a class="reference external" href="https://github.com/vstinner/pysandbox/">pysandbox</a></li>
<li><a class="reference external" href="http://www.egenix.com/products/python/mxBase/mxProxy/">mxProxy</a></li>
<li><a class="reference external" href="http://pypi.python.org/pypi/zope.proxy">zope.proxy</a></li>
<li><a class="reference external" href="http://pypi.python.org/pypi/zope.security">zope.security</a></li>
</ul>
</li>
</ul>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document has been placed in the public domain.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0416.rst">https://github.com/python/peps/blob/main/peps/pep-0416.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0416.rst">2023-09-09 17:39:29 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="#rationale">Rationale</a></li>
<li><a class="reference internal" href="#constraints">Constraints</a></li>
<li><a class="reference internal" href="#implementation">Implementation</a></li>
<li><a class="reference internal" href="#recipe-hashable-dict">Recipe: hashable dict</a></li>
<li><a class="reference internal" href="#objections">Objections</a></li>
<li><a class="reference internal" href="#alternative-dictproxy">Alternative: dictproxy</a></li>
<li><a class="reference internal" href="#existing-implementations">Existing implementations</a></li>
<li><a class="reference internal" href="#links">Links</a></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-0416.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>