1

I'm trying to implement simple tree class which is inherited from dictionary. Here my code:

class tree(dict):
    def __init__(self, hsymbol="/"):
        self.hsymbol = hsymbol
    def __setitem__(self, key, value):
        if key[0] == self.hsymbol : key = key[1:]
        parts = key.split(self.hsymbol, 1)
        if len(parts) == 2:
            if parts[0] not in self: self[parts[0]] = tree(hsymbol = self.hsymbol)
            self[parts[0]].__setitem__(parts[1], value)
        else:
            super(tree, self).__setitem__(key, value)

    def __getitem__(self, key):
        if key[0] == self.hsymbol : key = key[1:]
        parts = key.split(self.hsymbol, 1)
        if len(parts) == 2:
            if parts[0] not in self: raise KeyError(parts[0])
            return self[parts[0]][parts[1]]
        else:
            if key not in self: raise KeyError(parts[0])
            return super(tree, self).__getitem__(key)
    def __contains__(self,key):
        if key[0] == self.hsymbol : key = key[1:]
        parts = key.split(self.hsymbol, 1)
        if len(parts) == 2:
            if not super(tree, self).__contains__(parts[0]): return False
            return parts[1] in self[parts[0]]
        else:
            if not super(tree, self).__contains__(key): return False
            return True
    def __delitem__(self, key):
        if key[0] == self.hsymbol : key = key[1:]
        parts = key.split(self.hsymbol, 1)
        if len(parts) == 2:
            if parts[0] not in self: raise KeyError(parts[0])
            self[parts[0]].__delitem__(parts[1])
        else:
            if key not in list(self): raise KeyError(parts[0])
            super(tree,self).__delitem__(key)
    def keys(self,parent=""):
        #if parent is None: parent = self.hsymbol
        names = []
        for name in super(tree, self).keys():
            if isinstance(self[name], tree):
                names += self[name].keys(parent=parent+self.hsymbol+name)
            else:
                names.append(parent+self.hsymbol+name)
        return names

So everything works quite well, although I'm not sure about keys function realization:

>>> t=tree()
>>> t['/user/Johnson/incoming'] = 2200
>>> t['/user/Johnson/family'] = 4
>>> t['/user/Johnson/play'] = False
>>> t['/user/Smith/incoming'] = 12000
>>> t['/user/Smith/family'] = 1
>>> t['/user/Smith/play'] = True
>>> t['user/Smith/incoming']
12000    
>>> print t
{'user': {'Smith': {'play': True, 'incoming': 12000, 'family': 1}, 'Johnson': {'play': False, 'incoming': 2200, 'family': 4}}}
>>> print t.keys()
['/user/Smith/play', '/user/Smith/incoming', '/user/Smith/family', '/user/Johnson/play', '/user/Johnson/incoming', '/user/Johnson/family']
>>> t
{'user': {'Smith': {'play': True, 'incoming': 12000, 'family': 1}, 'Johnson': {'play': False, 'incoming': 2200, 'family': 4}}}

...but not an iteration through it:

>>> for k in t:
...  print k
... 
user
>>> 

How can I get something like this?

/user/Smith/play
/user/Smith/incoming
/user/Smith/family
/user/Johnson/play
/user/Johnson/incoming
/user/Johnson/family

Pretty sure that it must be __iter__ and next attributes of tree class, but I haven't figured out how to write it yet.

I've searched over Stack Overflow with no luck:

  • python recursive iteration nested dictionaries

  • python class inherited from dictionary iteration through nested dictionaries

  • python iteration through nested dictionaries

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
rth
  • 2,946
  • 1
  • 22
  • 27
  • 1
    Complaining about dupehammer holders in the initial rev of a question isn't a good way to set up a... productive working relationship. Keep in mind that if your question *does* get closed, the folks who can reopen it without needing four other voters to agree? Also those with badges. So. **If** your question gets closed with something that you believe isn't a duplicate, argue the case for how it's distinguished at that time; don't set up a hostile position up-front. – Charles Duffy Mar 05 '17 at 17:10
  • 2
    Because you inherit from `dict`, when you iterate over the instance you're calling `dict.__iter__`; your custom `keys` method is ignored. Rather than inheritance, consider *composition*; implement the [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping) ABC with the wrapped dictionary as an *attribute*. See e.g. http://stackoverflow.com/a/3387975/3001761 – jonrsharpe Mar 05 '17 at 17:17
  • @CharlesDuffy but I'm just tired to ask for example to do something without function, and got that it is duplicate that everything done by functions.... Look a this [link](http://stackoverflow.com/questions/42603914/is-any-way-to-expend-dictionary-notation).... – rth Mar 05 '17 at 17:19
  • 1
    Alternatively, implement `__iter__` as `return iter(self.keys())`. – jonrsharpe Mar 05 '17 at 17:20
  • **If** this question gets closed as a duplicate, **and** if, *after you've actually tried the answers* on the duplicate with appropriate modifications for your specific use case and found they do not solve your problem, **then** edit the question to *clarify specifically how they don't*. There's no guarantee that your question isn't a dupe, and if the dupe has the answer you need why *wouldn't* you want to be shown it? – jonrsharpe Mar 05 '17 at 17:22
  • I've followed your link, and frankly, I don't think the folks who closed it were acting wrongly. If the title were, say, "Making a single `__getitem__()` recurse", or otherwise explicitly focused on your real goal, then I'd agree. If there were something more concrete describing why function syntax isn't acceptable ("heavier than I want" isn't clear at all -- perhaps a lightweight/clean function would still be fine, then?), maybe then too. – Charles Duffy Mar 05 '17 at 17:32
  • So. If you edit that question -- without getting hostile -- to be more explicit about what you're looking for (a better question title could be ''supporting dict["item/subitem"] as synonym to dict["item"]["subitem"]'' or such), I might be on-board with its reopening. But as it is, the duplicates look defensible as such. – Charles Duffy Mar 05 '17 at 17:33
  • that said, even with that edit, the XPath dupe would look like a compelling one. At that point, the only difference is not knowing `__getitem__()` exists -- ie. the remaining aspect of the question that isn't answered is just "how do I override square-bracket dictionary lookup syntax?" or such. – Charles Duffy Mar 05 '17 at 17:36
  • @CharlesDuffy you are probably right, although XPath answers don't help me with my problem. May be I overestimated a main body of the question, and two direct comments to main question. It doesn't seem correct to close a topic just by title, isn't it? – rth Mar 05 '17 at 17:42
  • @CharlesDuffy but anyway, never mind. I got perfect answers in both cases. I deeply appreciate guys on Stack Overflow who really try to help. – rth Mar 05 '17 at 17:46
  • So -- keep in mind that what we're trying to build here at StackOverflow is a Q&A knowledgebase where folks with a question have a good chance of being redirected by Google &c. to a preexisting canonical answer. To be useful to folks coming from Google, they need to be able to figure out what a question is *about*, and whether it matches their own question, very quickly: That means the title, the excerpt of the question at the top, &c. should be as useful as possible, as opposed to needing to read through the text in detail to decide if the question and its answers will be helpful to them. – Charles Duffy Mar 05 '17 at 17:56
  • ...so inasmuch as there's a lot of pickiness about being explicit and visible about your question's parameters, that's not just for the benefit of folks who are potentially answering (though it certainly *is* to their benefit), but also for folks with related questions trying to determine whether their actual question has been previously asked, and thus to find a preexisting answer that will suit them. – Charles Duffy Mar 05 '17 at 17:57
  • Going into SO's historical mission, see https://stackoverflow.blog/2011/01/05/the-wikipedia-of-long-tail-programming-questions/ -- now, notable there, and supportive of your point, is the rule that "it has to be a real duplicate". – Charles Duffy Mar 05 '17 at 18:02
  • However, since the audience is that long tail, and what we're providing them with is a *really big knowledgebase* to search through, having that knowledgebase be written and edited in such a way as to make it easy to see which answers apply to them is important: A perfect answer hidden in a lot of dupes-that-aren't because someone reading headings from search results can't tell that the one sentence at the end modifies the meaning of everything else isn't a perfect answer. – Charles Duffy Mar 05 '17 at 18:03
  • ...anyhow, I've tried to edit the other question in a way that clarifies your intent, and responds usefully to the proposed dupes; hopefully it's useful as a template for the future. – Charles Duffy Mar 05 '17 at 18:10
  • @CharlesDuffy. Appreciate your honest explanations. As a scientists with code in hands, I should say that SO is a good source of ideas how to work around problems. But coding is a tool for me, not a goal. So i do my best to search first and then ask question, only If couldn't find an answer . Thank you for editing, hope it will be useful for other guys. – rth Mar 05 '17 at 18:34

1 Answers1

1

Yes, you need __iter__ (an iterator will have a next() automatically).

Following your existing logic:

def __iter__(self, parent=""):
    for name in super(tree, self).keys():
        if isinstance(self[name], tree):
            for item in self[name].__iter__(parent=parent+self.hsymbol+name):
                yield item
        else:
            yield parent+self.hsymbol+name

Unlike your current keys() implementation, this only walks the tree on an as-needed basis: If a client only needs the first two keys, it only calls next() twice, and so the iterator only proceeds past two yields.

(I might suggest implementing keys as simply return list(iter(self)) -- that way you have the lazy approach available for those that want to avoid the inefficiency of unnecessarily walking a full tree, and the non-lazy approach otherwise).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441