3

Is there a generic way of getting an iterator that always iterates over the values (preferably, although it may work iterating the keys too) of either dictionaries or other iterables (lists, sets...)?

Let me elaborate: When you execute "iter(list)" you get an iterator over the values (not the indexes, which sound pretty similar to the "key" in the dictionary) but when you do "iter(dict)" you get the keys.

Is there an instruction, attribute... whatever... which always iterates over the values (or the keys) of an iterable, no matter what type of iterable it is?

I have a code with an "append" method that needs to accept several different types of iterable types, and the only solution I've been able to come up with is something like this:

#!/usr/bin/python2.4

class Sample(object):
    def __init__(self):
        self._myListOfStuff = list()

    def addThings(self, thingsToAdd):
        myIterator = None
        if (isinstance(thingsToAdd, list) or 
            isinstance(thingsToAdd, set) or 
            isinstance(thingsToAdd, tuple)):
                myIterator = iter(thingsToAdd)
            elif isinstance(thingsToAdd, dict):
                myIterator = thingsToAdd.itervalues()

        if myIterator:
            for element in myIterator:
                self._myListOfStuff.append(element)


if __name__ == '__main__':
    sample = Sample()
    myList = list([1,2])
    mySet = set([3,4])
    myTuple = tuple([5,6])
    myDict = dict(
        a= 7,
        b= 8
    )

    sample.addThings(myList)    
    sample.addThings(mySet) 
    sample.addThings(myTuple)   
    sample.addThings(myDict)
    print sample._myListOfStuff
    #Outputs [1, 2, 3, 4, 5, 6, 7, 8]

I don't know... It looks a little bit... clunky to me

It would be great to be able to get a common iterator for cases like this, so I could write something like...

def addThings(self, thingsToAdd):
    for element in iter(thingsToAdd):
        self._myListOfStuff.append(element)

... if the iterator always gave the values, or...

def addThings(self, thingsToAdd):
    for element in iter(thingsToAdd):
        self._myListOfStuff.append(thingsToAdd[element])

... if the iterator returned the keys (I know the concept of key in a set is kind of "special", that's why I would prefer iterating over the values but still... maybe it could return the hash of the stored value).

Is there such thing in Python? (I have to use Python2.4, by the way)

Thank you all in advance

Savir
  • 17,568
  • 15
  • 82
  • 136
  • "I don't know... It looks a little bit... clunky to me" Right. Since you're treating dictionaries (which aren't sequences) like sequences, you're creating your own problems. – S.Lott Nov 05 '10 at 23:24
  • @S.Lott > But what about sets? Are they considered sequences? (it doesn't seem so according to http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-buffer-xrange) I can iterate sets as I do with lists. To me (which, let's admit, I'm not that knowledgeable in Python) sets sound more similar to dictionaries than to list (not positional order and the hash that sets use to store values sounds pretty similar to the "key" in the dictionaries). Maybe I got it wrong, but to me sets are pretty much like dictionaries with an "auto" generated key (the hash). – Savir Nov 06 '10 at 15:05
  • 1
    Yes, a set isn't a sequence (it doesn't preserve order). But it's not a mapping either. It's a pretty simple *collection* that emphasizes membership and nothing else. And although happen to use hashes, like dicts (soon after their introduction, theygot their own data structure instead of being derived from dicts as you imply), they are conceptually very different. Sets don't speak of "keys" and "values". They are not mappings. –  Nov 06 '10 at 16:21
  • @BorrajaX: "But what about sets?" Since you're treating dictionaries *and sets* (which aren't sequences) like sequences, you're creating your own problems. [I didn't think it necessary to enumerate all of the non-sequences you're treating as sequences.] They're all **different**. You can't treat them as being similar. Period. If you try to force them to be similar, the result **must** be clunky. What are you trying to accomplish? What's the **real** purpose behind all this clunky code? – S.Lott Nov 08 '10 at 12:12
  • @S.Lott > I understood the difference thanks to delnan and Laurence Gonsalves answers. **"What's the real purpose behind all this clunky code?"** -> I think it was clearly stated on my question and the code in it. Of course it was just a simplified example, but sometimes I receive data in dictionaries, sometimes in lists. I just wanted to know the best way to create iterators for dicts/lists. – Savir Nov 08 '10 at 14:58
  • @BorrajaX: "sometimes I receive data in dictionaries, sometimes in lists"? Why? Why can't that problem be fixed? – S.Lott Nov 08 '10 at 15:20
  • @S.Lott > Well... I got to a point in the project I'm currently working in which I need to recursively open objects (instances of classes) and iterate through all its values. That's why I asked for a generic iterator... To be able to open iterable types (fields of an instance of a non-well-known object) without needing to check what type it was. – Savir Nov 08 '10 at 15:25
  • @BorrajaX: "recursively open objects (instances of classes) and iterate through all its values"? "without needing to check what type it was". You **never** need to check the type. Ever. What is your **real** problem? Why do you think you might need to check types? Please provide some actual details on what you're **really** doing. – S.Lott Nov 08 '10 at 16:11
  • @S.Lott > I have a class which can have a list of objects and a dict whose values are objects. All said objects share a self.isValid() method. If it's not valid, show a warning. I need to iterate through the values of said list/dict, right? – Savir Nov 08 '10 at 16:17
  • @BorrajaX: This is the question. Why do you have a list of object and a dict of objects that "magically" must be treated the same? Why not simply process a list of objects? To work with the dict use `some_dict.values()` to get a proper list which you can work with consistently and simply. Why do you have both `list` and `dict`? – S.Lott Nov 08 '10 at 16:20
  • @S.Lott > Because I have objects that may contain list or dicts of objects. In order to use some_dict.values() I still need to check that some_dict is a dict(), correct? Because if it's not "some_dict" but "random_field_that_can_be_list_or_dict_of_other_objects" and I still need to get the objects contained in that "unknown" field, I have to check whether is a dict or a list (lists don't have some_list.values(), correct?). Therefore, I need to know its type in order to get a proper iterator. – Savir Nov 08 '10 at 16:42
  • @BorrajaX: Why is it "random_field_that_can_be_list_or_dict_of_other_objects"? How did this design problem happen? Random fields don't exist unless someone intentionally designed them poorly. Why doesn't this field have any consistency? What is the root cause for for this design error? – S.Lott Nov 08 '10 at 17:16
  • @S.Lott > Sorry, my bad: No, is not that a same field can be a list or dict... Is that I need to "open" and walk a bunch of different objects with different fields that can be dictionaries, lists, integers... And the collections/mappers inside said objects can contain other objects which I also need to walk (recusively) – Savir Nov 08 '10 at 17:40
  • @BorrajaX: Why do you have a classes that have different fields which can be list or dictionaries which then must "magically" must be treated the same? Why can't your classes be consistent? What design error leads you to handle these things all alike? Why can you change your classes to be more consistent? – S.Lott Nov 08 '10 at 17:59
  • What error is there in having two different classes with different names, different fields... which can be dictionaries or lists? Is this a problem in Python? class A: def __init__(self): self.a = list() class B: def __init__(self): self.b = dict() I just want to have a common way of walking through instances of A and instances of B. A good example to show what I need to do would be a JSON parser. Receives unknown classes as a parameter but still parses them... – Savir Nov 08 '10 at 18:16
  • @BorrajaX: "Is this a problem in Python?". No. It's a problem with OO design. Polymorphism dictates that you have some parallel structure that makes sense. Dictionaries, Lists and Sets are not trivially "similar". JSON parses (and XML parsers) have already been written. And -- bonus -- you can read their code to see how they work. And -- best -- they have clunky features. Read the code for `json` to see how it works. – S.Lott Nov 08 '10 at 21:55

3 Answers3

3

I don't know of anything built in that does this. I suppose you could do something like:

def itervalues(x):
  if hasattr(x, 'itervalues'): return x.itervalues()
  if hasattr(x, 'values'): return iter(x.values())
  return iter(x)
Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
3

Most collections have no concept or "keys" or "values". They just store items, and iterating over them gives you those items. Only mappings (dict-likes) provide "keys" and "values". And the default way of iterating them uses the keys because for x in collection: assert x in collection should be an invariant (and a (key, value) in d rarely makes sense, as opposed to key in d). So there is no method to iterate over the values only, because the least collections have things called "values".

You can, however, choose to ignore the keys in mappings. Since you have to use Python 2.4, using ABCs is out of questions... I fear the simplest way would be:

def iter_values(it):
    if hasattr(it, 'itervalues'):
        return it.itervalues()
    return iter(x)

Which can still break, but handles dicts and similar mappings.

  • Great. And thank you for the bonus explanation of "why" this works the way it does! :-) – Savir Nov 06 '10 at 16:04
  • 1
    @BorrajaX: I mostly just summarized AM's answer to http://stackoverflow.com/questions/3744568/why-do-you-have-to-call-iteritems-when-iterating-over-a-dictionary-in-python –  Nov 06 '10 at 16:16
  • Oh... More bonus replies 12 minutes after my comment... Astonishing! Thank you again! Programming wise, I think I'm like a little kid... Always asking "Why?, Why?, Why?" and this kind of replies are great to me :-) – Savir Nov 06 '10 at 16:24
  • 1
    FWIW: in older versions of Python dicts weren't iterable, nor did they support the `in` operator. The decision to add those two capabilities wasn't entirely without controversy, as making these tow operations work with keys instead of values leads to an inconsistency with lists and tuples (as implied by the question). – Laurence Gonsalves Nov 06 '10 at 23:21
0

What you're looking for is the dict.items() method, which return (key, value) tuples. Here is a usage example:

d = {'a': 1, 'b': 2, 'c': 3}
for k, v in d.items():
    print "key:", k, "- value:", v

Which, as you can imagine, would output

key: a - value: 1
key: b - value: 2
key: c - value: 3

Edit: if you want to get a real iterator and not just a sequence, you can also use the dict.iteritems() method, which works the same way.

Edit2: seems i misunderstood your question, as you seem to ask for a generic function that would work with any iterable, forget my answer ;-)

mdeous
  • 17,513
  • 7
  • 56
  • 60
  • It's ok! I appreciate the time you took, and it's a good explanation of how iteritems() works. Thank you! – Savir Nov 06 '10 at 16:08