title: How to register implementation of abc.MutableMapping as a dict subclass?
body: <p>I would like my <code>SpreadSheet</code> class below to be considered a <code>dict</code> subclass by the <code>isinstance()</code> built-in, but when I try to register it as such, an <code>AttributeError</code> exception is thrown (also shown below).</p>
  <p>What is a (or the) way to do something like this?</p>
  <p>Note: My question is similar to <a href="https://stackoverflow.com/questions/25149315/is-it-possible-to-be-a-virtual-subclass-of-a-built-in-type">Is it possible to be a virtual subclass of a built in type?</a>, but its accepted answer doesn't address the titular question asked (so please don't vote to close this as a duplicate).</p>
  <p>The primary motivation for wanting to do this is to allow an instance of the class to be passed to <code>json.dump()</code> and be treated just like a Python <code>dict</code>. This is needed because — for reasons I don't understand — the <code>JSONEncoder</code> class <a href="https://github.com/python/cpython/blob/3.8/Lib/json/encoder.py#L321" rel="nofollow noreferrer">uses</a> <code>isinstance(value, dict)</code>, rather than <code>isinstance(value, Mapping)</code>.</p>
  <pre><code>from collections.abc import MutableMapping
  
  
  class SpreadSheet(MutableMapping):
      def __init__(self, tools=None, **kwargs):
          self._cells = {}
          self._tools = {'__builtins__': None}
          if tools is not None:
              self._tools.update(tools)  # Add caller supplied functions.
  
      def clear(self):
          return self._cells.clear()
  
      def __contains__(self, k):
          return k in self._cells
  
      def __setitem__(self, key, formula):
          self._cells[key] = formula
  
      def __getitem__(self, key ):
          return eval(self._cells[key], self._tools, self)
  
      def __len__(self):
          return len(self._cells)
  
      def __iter__(self):
          return iter(self._cells)
  
      def __delitem__(self, k):
          del self._cells[k]
  
      def getformula(self, key):
          return self._cells[key]
  
  type(dict).register(SpreadSheet)  # Register class as dict subclass.
  
  ss = SpreadSheet()
  print(f'isinstance(ss, dict): {isinstance(ss, dict)}')  # Result should be True.
  </code></pre>
  <p>Exception:</p>
  <pre class="lang-none prettyprint-override"><code>Traceback (most recent call last):
    File &quot;spreadsheet.py&quot;, line 35, in &lt;module&gt;
      type(dict).register(SpreadSheet)  # Register class as dict subclass.
  AttributeError: type object 'type' has no attribute 'register'
  </code></pre>
  <h3>Chosen Solution</h3>
  <p>As the accepted answer to <a href="https://stackoverflow.com/a/25149515/355230">Is it possible to be a virtual subclass of a built in type?</a> says, it's impossible as primitive types are essentially immutable.</p>
  <p>However it <em><strong>is</strong></em> possible to make <code>json.dump()</code> treat a <code>Mapping</code> just like a <code>dict</code> by patching the module as shown in the <strong>second</strong> approach presented in @jsbueno's <a href="https://stackoverflow.com/a/58031309/355230">answer</a>. The implementation shown below patches the encoder in a slightly different, simpler way that gives equivalent results. I especially liked a bonus feature it has which is that it also prevents the C optimized version from being used (which silently fails).</p>
  <pre><code>from collections.abc import Mapping, MutableMapping
  from functools import partial
  import json
  
  
  class SpreadSheet(MutableMapping):
      def __init__(self, tools=None, **kwargs):
          self._cells = {}
          self._tools = {'__builtins__': None}  # Prevent eval() from supplying.
          if tools is not None:
              self._tools.update(tools)  # Add any caller-supplied functions.
  
      def clear(self):
          return self._cells.clear()
  
      def __contains__(self, key):
          return key in self._cells
  
      def __setitem__(self, key, formula):
          self._cells[key] = formula
  
      def __getitem__(self, key):
          return eval(self._cells[key], self._tools, self)
  
      def __len__(self):
          return len(self._cells)
  
      def __iter__(self):
          return iter(self._cells)
  
      def __delitem__(self, key):
          del self._cells[key]
  
      def getformula(self, key):
          &quot;&quot;&quot; Return raw un-evaluated contents of cell. &quot;&quot;&quot;
          return self._cells[key]
  
      def update(self, *args, **kwargs):
          for k, v in dict(*args, **kwargs).iteritems():
              self[k] = v
  
  
  # Monkey-path json module
  
  # Changes check for isinstance(obj, dict) to isinstance(obj, Mapping)
  # https://github.com/python/cpython/blob/3.8/Lib/json/encoder.py#L321
  # This changes the default value of the function's dict= keyword to be
  # a Mapping instead of a dict. The isinstance() call uses whatever it's
  # set to.
  _new__make_iterencode = partial(json.encoder._make_iterencode, dict=Mapping)
  
  json.encoder._make_iterencode = _new__make_iterencode
  json.encoder.c_make_encoder = None  # Disables use of C version of make encoder
  
  
  if __name__ == '__main__':
  
      import json
      from math import cos, sin, pi, tan
  
      # A small set of safe built-ins.
      tools = dict(len=len, sin=sin, cos=cos, pi=pi, tan=tan)
  
      ss = SpreadSheet(tools)
      ss['a1'] = '5'
      ss['a2'] = 'a1*6'
      ss['a3'] = 'a2*7'
      ss['b1'] = 'sin(pi/4)'
  
      print()
      print('isinstance(SpreadSheet(tools), dict) -&gt; {}'.format(isinstance(ss, dict)))
      print()
      print('Static Contents via getformula():')
      print(json.dumps({k: ss.getformula(k) for k in ss.keys()}, indent=4))
      print()
      print('Dynamic Contents via __getitem__():')
      print(&quot;  ss['a1'] -&gt; {!r}&quot;.format(ss['a1']))
      print(&quot;  ss['a2'] -&gt; {!r}&quot;.format(ss['a2']))
      print(&quot;  ss['a3'] -&gt; {!r}&quot;.format(ss['a3']))
      print(&quot;  ss['b1'] -&gt; {!r}&quot;.format(ss['b1']))
      print()
      print(&quot;via json.dumps(ss, indent=4):&quot;)
      print(json.dumps(ss, indent=4))
      print()
      print(&quot;via json.dumps(ss):&quot;)  # Works, too.
      print(json.dumps(ss))  # -&gt; {}
      print()
      print('dict(**ss): {}'.format(dict(**ss)))  # Gets dynamic contents.
  
  </code></pre>