Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over dictionary using __getitem__ in python [duplicate]

I've implemented a python class to generate data as follows:

class Array:
    def __init__(self):
        self.arr = [1,2,3,4,5,6,7,8]

    def __getitem__(self,key):
        return self.arr[key]

a = Array()
for i in a:
    print(i, end = " ")

It runs as expected and I get following

1 2 3 4 5 6 7 8

However, I want to do the same thing for dictionary. Why am I not able to iterate over dictionary like this?

class Dictionary:
    def __init__(self):
        self.dictionary = {'a' : 1, 'b' : 2, 'c': 3}

    def __getitem__(self,key):
        return self.dictionary[key]
d = Dictionary()
for key in d:
    print(key, d[key], end=" ")

I expect following output

a 1 b 2 c 3

But when I run above code, I get following error :

Traceback (most recent call last):

  File "<ipython-input-33-4547779db7ec>", line 8, in <module>
    for key in d:

  File "<ipython-input-33-4547779db7ec>", line 6, in __getitem__
    return self.dictionary[key]

KeyError: 0

We can iterate over normal dictionary like this : for key in d. This iterates over all the keys, is it not possible to iterate like this using __getitem__()?

like image 662
Mahavir I Avatar asked May 07 '20 11:05

Mahavir I


People also ask

What does __ Getitem __ do in Python?

__getitem__(x, i) . The method __getitem__(self, key) defines behavior for when an item is accessed, using the notation self[key] . This is also part of both the mutable and immutable container protocols. Unlike some other languages, Python basically lets you pass any object into the indexer.

Can you iterate over dictionary keys?

As mentioned above, you can iterate keys by using the dictionary object directly, but you can also use keys() . The result is the same, but keys() may clarify the intent to the reader of the code. The keys() method returns dict_keys . It can be converted to a list with list() .

Is iterating through a dictionary faster than a list?

Using iteritems is a tad bit faster... But the time to create a view is negligable; it is actually slower to iterate over than a list. This means that in Python 3, if you want to iterate many times over the items in a dictionary, and performance is critical, you can get a 30% speedup by caching the view as a list.

How many ways are there to iterate over a dictionary?

There are two ways of iterating through a Python dictionary object. One is to fetch associated value for each key in keys() list. There is also items() method of dictionary object which returns list of tuples, each tuple having key and value.


Video Answer


1 Answers

A for loop works with iterators, objects you can pass to next. An object is an iterator if it has a __next__ method.

Neither of your classes does, so Python will first pass your object to iter to get an iterator. The first thing iter tries to do is call the object's __iter__ method.

Neither of your classes defines __iter__, either, so iter next checks if its object defines __getitem__. Both of your classes do, so iter returns an object of type iterator, whose __next__ method can be imagined to be something like

def __next__(self):
    try:
        rv = self.thing.__getitem__(self.i)
    except IndexError:
        raise StopIteration
    self.i += 1
    return rv

(The iterator holds a reference to the thing which defined __getitem__, as well as the value of i to track state between calls to __next__. i is presumed to be initialized to 0.)

For Array, this works, because it has integer indices. For Dictionary, though, 0 is not a key, and instead of raising an IndexError, you get a KeyError with the __next__ method does not catch.

(This is alluded to in the documentation for __getitem__:

Note for loops expect that an IndexError will be raised for illegal indexes to allow proper detection of the end of the sequence.

)

To make your Dictionary class iterable, define __iter__

class Dictionary:
    def __init__(self):
        self.dictionary = {'a' : 1, 'b' : 2, 'c': 3}

    def __getitem__(self,key):
        return self.dictionary[key]

    def __iter__(self):
        return iter(self.dictionary)

dict.__iter__ returns a value of type dict_keyiterator, which is the thing that yields the dict's keys, which you can use with Dictionary.__getitem__.

like image 69
chepner Avatar answered Oct 20 '22 06:10

chepner