{"id":2575,"date":"2023-05-12T17:15:06","date_gmt":"2023-05-12T22:15:06","guid":{"rendered":"http:\/\/www.ishygddt.xyz\/~blog\/?p=2575"},"modified":"2023-05-26T15:20:18","modified_gmt":"2023-05-26T20:20:18","slug":"frozenset-as-frozendict","status":"publish","type":"post","link":"http:\/\/www.ishygddt.xyz\/~blog\/2023\/05\/frozenset-as-frozendict","title":{"rendered":"the real frozendict was the standard library we neglected along the way"},"content":{"rendered":"<p>If you need a quick immutable representation of dictionaries, but can't be bothered with a 3rd-party library, why not try <code class=\"language-python\" data-line=\"\">frozenset(dict_items)<\/code>?<\/p>\n<p>It is immutable. It is in the standard library. It is unordered, so you're <strong>guaranteed<\/strong> that <code class=\"language-python\" data-line=\"\">frozenset([(&#039;a&#039;, 1), (&#039;b&#039;, 2)]) == frozenset([(&#039;b&#039;, 2), (&#039;a&#039;, 1)])<\/code>. And it is the case that, for dictionaries with all immutable values (which is implied by the problem statement anyway), <code class=\"language-python\" data-line=\"\">dict(frozenset(d.items())) == d<\/code>.<\/p>\n<p>Now, yes, it is possible to <em>create<\/em> a <code class=\"language-python\" data-line=\"\">frozenset<\/code> that isn't a valid representation of a dictionary (e.g. if <code class=\"language-python\" data-line=\"\">not all(len(item) == 2 for item in something_which_isnt_dict_items)<\/code>). But that won't happen by calling <code class=\"language-python\" data-line=\"\">frozenset(dict_items)<\/code>. \ud83d\ude42<\/p>\n<hr \/>\n<p>And if the above <em>really<\/em> isn't fancy enough for you, maybe try this on for size:<\/p>\n<pre><code class=\"language-python\" data-line=\"\">import collections.abc\n\nclass frozenmapping(frozenset, collections.abc.Mapping):\n\tdef __iter__(self):\n\t\tyield from (k for k, v in super().__iter__())\n\tdef __getitem__(self, key):\n\t\tmissing = object()\n\t\tvalue = next((v for k, v in super().__iter__() if k == key), missing)\n\t\tif value is missing:\n\t\t\traise KeyError(key)\n\t\treturn value\n\tdef __contains__(self, key):\n\t\t# skip frozenset and force use of abc.Mapping&#039;s mixin\n\t\treturn super(frozenset, self).__contains__(key)\n\tdef __eq__(self, other):\n\t\t# skip frozenset and force use of abc.Mapping&#039;s mixin\n\t\treturn super(frozenset, self).__eq__(other)\n\tdef __neq__(self, other):\n\t\t# skip frozenset and force use of abc.Mapping&#039;s mixin\n\t\treturn super(frozenset, self).__neq__(other)\n\tdef __new__(cls, mapping_or_iterable):\n\t\tif isinstance(mapping_or_iterable, collections.abc.Mapping):\n\t\t\titems = mapping_or_iterable.items()\n\t\telse:\n\t\t\titems = mapping_or_iterable\n\t\treturn super().__new__(cls, ((k, v) for k, v in items))\n\tdef __repr__(self):\n\t\tif not self:\n\t\t\treturn f&#039;{self.__class__.__name__}()&#039;\n\t\td = dict(self)\n\t\treturn f&#039;{self.__class__.__name__}({d!r})&#039;\n\t# We didn&#039;t &quot;have&quot; to implement this; abc.Mapping would &quot;have our back&quot;, kinda\n\t# but we will implement it to avoid O(N^2) performance\n\tdef items(self):\n\t\tyield from super().__iter__()<\/code><\/pre>\n<p>The handy thing about <a href=\"https:\/\/docs.python.org\/3\/library\/collections.abc.html#collections.abc.Mapping\"><code class=\"language-python\" data-line=\"\">collections.abc.Mapping<\/code><\/a> is that <strong>all<\/strong> you need to provide is <code class=\"language-python\" data-line=\"\">__getitem__<\/code>, <code class=\"language-python\" data-line=\"\">__iter__<\/code>, and <code class=\"language-python\" data-line=\"\">__len__<\/code>, and that class will magically \"mixin\" all the remaining mapping methods (<code class=\"language-python\" data-line=\"\">__contains__<\/code>, <code class=\"language-python\" data-line=\"\">keys<\/code>, <code class=\"language-python\" data-line=\"\">items<\/code>, <code class=\"language-python\" data-line=\"\">values<\/code>, <code class=\"language-python\" data-line=\"\">get<\/code>, <code class=\"language-python\" data-line=\"\">__eq__<\/code>, and <code class=\"language-python\" data-line=\"\">__ne__<\/code>) that you didn't implement to create a fully featured <code class=\"language-python\" data-line=\"\">dict<\/code>-a-like class. (Actually, it's not <em>fully<\/em> featured since this one's not mutable\u2014<em>obviously<\/em>, per our goal today\u2014if you want a mutable mapping instead, look into <a href=\"https:\/\/docs.python.org\/3\/library\/collections.abc.html#collections.abc.MutableMapping\"><code class=\"language-python\" data-line=\"\">collections.abc.MutableMapping<\/code><\/a>.)<\/p>\n<p>Going over each of the methods:<\/p>\n<ul>\n<li><code class=\"language-python\" data-line=\"\">__iter__<\/code> and <code class=\"language-python\" data-line=\"\">__getitem__<\/code> are implemented entirely by us.<\/li>\n<li>We didn't implement <code class=\"language-python\" data-line=\"\">__len__<\/code>, since we're inheriting correct behavior from <code class=\"language-python\" data-line=\"\">frozenset<\/code>.<\/li>\n<li><code class=\"language-python\" data-line=\"\">items<\/code> was implemented by us only for run-time efficiency, since the <code class=\"language-python\" data-line=\"\">abc.Mapping<\/code> mixin would, if we hadn't implemented it, iterate through the items, take the key from that item, <em>and then<\/em> call <code class=\"language-python\" data-line=\"\">__getitem__<\/code> (which iterates through the sequence again looking for the item it just got the key from) for each key, which is obviously horribly inefficient. Since we know that our underlying datastructure is\u00a0<em>already<\/em> just items, we use it.<\/li>\n<li><code class=\"language-python\" data-line=\"\">__contains__<\/code>, <code class=\"language-python\" data-line=\"\">__eq__<\/code>, and <code class=\"language-python\" data-line=\"\">__ne__<\/code> weren't exactly <em>implemented<\/em> by us. We were just lazy and used <code class=\"language-python\" data-line=\"\">abc.Mapping<\/code>'s mixin. But, to do this, we had to skip <code class=\"language-python\" data-line=\"\">frozenset<\/code>'s incorrect-for-us behavior in the <a href=\"https:\/\/docs.python.org\/3\/glossary.html#term-method-resolution-order\"><abbr title=\"method resolution order\">MRO<\/abbr><\/a> to \"convince\" <code class=\"language-python\" data-line=\"\">abc.Mapping<\/code> to kick in with its <code class=\"language-python\" data-line=\"\">dict<\/code>-a-like behavior. This showcases use of <a href=\"https:\/\/docs.python.org\/3\/library\/functions.html#super\"><code class=\"language-python\" data-line=\"\">super<\/code><\/a>.<\/li>\n<li><code class=\"language-python\" data-line=\"\">__new__<\/code> is basically as described in the first half of this post, plus a codepath to handle <code class=\"language-python\" data-line=\"\">dict_items<\/code> sequences, plus a structured iteration to provide similar protective behavior to <code class=\"language-python\" data-line=\"\">dict([(&#039;Key1&#039;, &#039;Value1&#039;, &#039;\\U0001F4A91&#039;)])<\/code>.<\/li>\n<li><code class=\"language-python\" data-line=\"\">__repr__<\/code> is optional but <em>very<\/em> convenient.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>If you need a quick immutable representation of dictionaries, but can't be bothered with a 3rd-party library, why not try frozenset(dict_items)? It is immutable. It is in the standard library. It is unordered, so you're guaranteed that frozenset([(&#039;a&#039;, 1), (&#039;b&#039;, 2)]) == frozenset([(&#039;b&#039;, 2), (&#039;a&#039;, 1)]). And it is the case that, for dictionaries with &hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2575","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/posts\/2575","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/comments?post=2575"}],"version-history":[{"count":30,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/posts\/2575\/revisions"}],"predecessor-version":[{"id":2691,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/posts\/2575\/revisions\/2691"}],"wp:attachment":[{"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/media?parent=2575"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/categories?post=2575"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.ishygddt.xyz\/~blog\/wp-json\/wp\/v2\/tags?post=2575"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}