title: How should I deal with this recursive behaviour?
body: <p>Suppose I had a class 'A' and nested within 'A' was class 'B' and both objects were for use publicly...</p>
  
  <pre><code>class A(object):
  
      class B(object):
  
          def __init__(self):
              self._b = dict()
              self.a = A()
  
          def __setitem__(self, key, value):
              # set self._b
              self.a[key] = value  # recursion ??
  
      def __init__(self):
          self._a = dict()
          self.b = A.B()
  
      def __setitem__(self, key, value):
          # set self._a
          self.b[key] = value     # recursion ??
  </code></pre>
  
  <p>Now when doing...</p>
  
  <pre><code>d = A()
  d['sap'] = 'nin'
  </code></pre>
  
  <p><code>A.__setitem__</code> will call <code>B.__setitem__</code>, which will call <code>A.__setitem__</code> and so on...</p>
  
  <p>If this is the desired interface, what would be the best way to deal with this recursive behaviour?</p>
  
  <p>for more context, I've posted on codereview here...</p>
  
  <p><a href="https://codereview.stackexchange.com/questions/85842/a-dictionary-that-allows-multiple-keys-for-one-value">https://codereview.stackexchange.com/questions/85842/a-dictionary-that-allows-multiple-keys-for-one-value</a></p>
  
  <p>I've tried using another nested class to pass to other internal functions so that they can determine whether it was called from the user, or internally from other functions...</p>
  
  <pre><code>class A(object):
  
      class _Container(object):
          def __init__(self, _object):
              self.object = _object
  
      class B(object):
          def __init__(self, d, **kwargs):
              if is instance(d, A._Container):
                  self.a = d.object
              else:
                  self.a = A(A._Container(self))
  
      def __init__(self, d, **kwargs):
          if isinstance(d, A._Container):
              self.b = d.object
          else:
              self.b = A.B(A._Container(self))
  </code></pre>
  
  <p>This works for <strong>init</strong>, but I'm not sure I'd like to do that with <strong>setitem</strong> and all other methods.</p>
  
  <p>I've also tried delegating all or most of the work to class 'A' so essentially class 'B''s methods act as hooks...</p>
  
  <pre><code>class A(object):
      class B(object):
          def __setitem__(self, key, value):
              # some functionality
              self.a[key] = value
  
      def __setitem__(self, key, value):
          self._a[key] = value
          self._b[key] = value
  </code></pre>
  
  <p>If it helps, here is the code I'm working on atm.</p>
  
  <pre><code># -*- coding: utf-8 -*-
  
  class mkdict(object):
      """ A dictionary that allows multiple keys for one value """
  
  
      class _Container(object):
          """ This is used to wrap an object to avoid infinite
          recursion when calling my own methods from the inside.
  
          If a method sees this container, it assumes it has been
          called from the inside and not the user.
          """
  
          def __init__(self, _object):
              self.object = _object
  
  
      class dict(object):
          """ Interface for mkdict.dict for dict-like behaviour """
  
          def __init__(self, d={}, **kwargs):
              """ Using an mkdict._Container to avoid infinite
              recursion when allowing:
  
              &gt;&gt;&gt; d = mkdict({'what': 'ever'})
              &gt;&gt;&gt; d = mkdict.dict({'what': 'ever'})
              """
              if isinstance(d, mkdict._Container):
                  self.mkdict = d.object
              else:
                  self.mkdict = mkdict(mkdict._Container(self))
                  self.update(d, **kwargs)
  
          def __str__(self):
              return str(self.mkdict._dict)
  
          def __repr__(self):
              return str(self)
  
          def __len__(self):
              return len(self.mkdict._dict)
  
          def __setitem__(self, key, value):
              """ Desired behaviour:
  
              &gt;&gt;&gt; d = mkdict()
              &gt;&gt;&gt;
              &gt;&gt;&gt; d['what', 'ever'] = 'testing'
              &gt;&gt;&gt; d
              {'what': 'testing', 'ever': 'testing'}
              &gt;&gt;&gt; d.dict
              {('what', 'ever'): 'testing'}
              &gt;&gt;&gt; d['what'] is d['ever']
              True
              &gt;&gt;&gt;
              &gt;&gt;&gt; d.dict['what'] = 'new value'
              &gt;&gt;&gt; d
              {'what': 'new value', 'ever': 'testing'}
              &gt;&gt;&gt; d.dict
              {'what': 'new value', 'ever': 'testing'}
              &gt;&gt;&gt; d['what'] is d['ever']
              False
              """
              if key not in self and key in self.mkdict:
                  self.mkdict._key_already_set(key)
  
              self.mkdict[key] = value
  
          def __getitem__(self, key):
              return self.mkdict._dict[key]
  
          def __contains__(self, key):
              return key in self.mkdict._dict
  
          def __delitem__(self, key):
              if key not in self:
                  raise KeyError(key)
  
              if isinstance(key, tuple):
                  key = key[0]
  
              del self.mkdict[key]
  
          def clear(self):
              self.mkdict.clear()
  
          def update(self, d, **kwargs):
              if isinstance(d, mkdict.dict):
                  d = d.mkdict._dict
              elif isinstance(d, mkdict):
                  d = d._dict
  
              d.update(kwargs):
              for k, v in d.items():
                  self[k] = v
  
  
      class _FullKeyPtr(object):
          """ Desired behaviour:
  
          full_key_ptr1 = _FullKeyPtr()
          mkdict._key_map -&gt; {'key1', full_key_ptr1,
                              'key2', full_key_ptr1}
  
          &gt;&gt;&gt; d = mkdict()
          &gt;&gt;&gt; d['what', 'ever'] = 'testing'
          &gt;&gt;&gt; d._key_map
          &gt;&gt;&gt;
          &gt;&gt;&gt; # d._key_map:
          &gt;&gt;&gt; # {'what': full_key_ptr1, 'ever': full_key_ptr1}
          &gt;&gt;&gt; d._key_map
          &gt;&gt;&gt; {'what': ('what', 'ever'), 'ever': ('what', 'ever')}
          &gt;&gt;&gt;
          &gt;&gt;&gt; d['what']
          &gt;&gt;&gt; 'testing'
          &gt;&gt;&gt;
          &gt;&gt;&gt; # full_key = _key_map['ever'].full_key
          &gt;&gt;&gt; # i.e. full_key = ('what', 'ever')
          &gt;&gt;&gt; # _dict[full_key] = 'test'
          &gt;&gt;&gt; d['ever'] = 'test'
          &gt;&gt;&gt;
          &gt;&gt;&gt;
          &gt;&gt;&gt; d['what']
          &gt;&gt;&gt; 'test'
          """
  
          def __init__(self, full_key):
              self.full_key = full_key
  
          def __str__(self):
              return str(self.full_key)
  
          def __repr__(self):
              return str(self)
  
  
      def __init__(self, d={}, **kwargs):
          self._dict = dict()
          self._key_map = dict()
          self._dict_backup = None
          self._key_map_backup = None
  
          if isinstance(d, mkdict._Container):
              self.dict = d.object
          else:
              self.dict = mkdict.dict(mkdict._Container(self))
              self.update(d, **kwargs)
  
      def __str__(self):
          return str(dict(self.items()))
  
      def __repr__(self):
          return str(self)
  
      def __len__(self):
          return len(self._key_map)
  
      def __iter__(self):
          return iter(self.keys())
  
      def __getitem__(self, key):
          full_key = self.full_key(key)
          return self.dict[full_key]
  
      def __setitem__(self, key, value):
          """ Desired behaviour:
  
          &gt;&gt;&gt; d = mkdict()
          &gt;&gt;&gt; d['what', 'ever'] = 'testing'
          &gt;&gt;&gt;
          &gt;&gt;&gt; d
          {'what': 'testing', 'ever': 'testing'}
          &gt;&gt;&gt;
          &gt;&gt;&gt; d.dict
          {('what', 'ever'): 'testing'}
          &gt;&gt;&gt; d['what'] is d['ever']
          True
          &gt;&gt;&gt;
          &gt;&gt;&gt; d['what'] = 'new value'
          &gt;&gt;&gt; d
          {'what': 'new value', 'ever': 'new value'}
          &gt;&gt;&gt;
          &gt;&gt;&gt; d.dict
          {('what', 'ever'): 'new value'}
          &gt;&gt;&gt; d['what'] is d['ever']
          True
          """
          if key in self:
              key = self.full_key(key)
  
          if key not in self._dict:
              if isinstance(key, tuple):
                  full_key_ptr = self._FullKeyPtr(key)
                  for k in key:
                      if k in self:
                          self._key_already_set(k)
                      self._key_map[k] = full_key_ptr
              else:
                  self._key_map[key] = self._FullKeyPtr(key)
  
          self._dict[key] = value
  
      def __delitem__(self, key):
          full_key = self.full_key(key)
  
          if isinstance(full_key, tuple):
              for k in full_key:
                  del self._key_map[k]
          else:
              del self._key_map[full_key]
  
          del self._dict[full_key]
  
      def __contains__(self, key):
          return key in self._key_map
  
      def items(self):
          return [(k, self[k]) for k, v in self._key_map.items()]
  
      def iteritems(self):
          return iter(self.items())
  
      def update(self, d={}, **kwargs):
          if isinstance(d, mkdict.dict):
              d = d.mkdict._dict
          elif isinstance(d, mkdict):
              d = d._dict
  
          d.update(kwargs)
          for k, v in d.items():
              self[k] = v
  
      def clear(self):
          self._dict.clear()
          self._key_map.clear()
  
      def keys(self):
          return self._key_map.keys()
  
      def full_key(self, key):
          return self._key_map[key].full_key
  
      def has_key(self, key):
          return key in self
  
      def append(self, key, otherkey):
          pass
  
      def remove(self, key):
          full_key = self.full_key(key)
  
          if not isinstance(full_key, tuple):
              del self._dict[full_key]
              del self._key_map[full_key]
              return
  
          new_full_key = list(full_key)
          new_full_key.remove(key)
  
          if len(new_full_key) == 1:
              new_full_key = new_full_key[0]
          else:
              new_full_key = tuple(new_full_key)
  
          self._dict[new_full_key] = self.dict[full_key]
          del self._dict[full_key]
          self._key_map[key].full_key = new_full_key
          del self._key_map[key]
  
      def aliases(self, key):
          full_key = self.full_key(key)
          if isinstance(full_key, tuple):
              aliases = list(full_key)
              aliases.remove(key)
              return aliases
          return list()
  
      def backup(self):
          pass
  
      def revert(self):
          pass
  
      def _key_already_set(self, key):
          self.remove(key)
  </code></pre>
  
  <p>Desired behaviour for above code:</p>
  
  <pre><code>&gt;&gt;&gt; d = mkdict()
  &gt;&gt;&gt;
  &gt;&gt;&gt; d['-p', '--port'] = 1234
  &gt;&gt;&gt; d
  {'-p': 1234, '--port': 1234}
  &gt;&gt;&gt; d.dict
  {('-p', '--port'): 1234}
  &gt;&gt;&gt;
  &gt;&gt;&gt; d['-p'] = 5678
  &gt;&gt;&gt; d
  {'-p': 5678, '--port': 5678}
  &gt;&gt;&gt; d['--port'] is d['-p']
  True
  &gt;&gt;&gt; d.aliases('-p')
  ['--port']
  &gt;&gt;&gt;
  &gt;&gt;&gt; d.dict['-p'] = 1234
  &gt;&gt;&gt; d
  {'-p': 1234, '--port': 5678}
  &gt;&gt;&gt; d.dict
  {'-p': 1234, '--port': 5678}
  &gt;&gt;&gt;
  &gt;&gt;&gt; d['-p'] is d['--port']
  False
  </code></pre>