[Tutor] How to override getting items from a list for iteration

Oscar Benjamin oscar.j.benjamin at gmail.com
Sun Feb 10 22:20:54 CET 2013


On 10 February 2013 14:32, Walter Prins <wprins at gmail.com> wrote:
[snip
>
> This worked mostly fine, however yesterday I ran into a slightly unexpected
> problem when I found that when the list contents is iterated over and values
> retrieved that way rather than via [], then __getitem__ is in fact *not*
> called on the list to read the item values from the list, and consequently I
> get back the "not yet calculated" entries in the list, without the
> calculation routine being automatically called as is intended.
>
> Here's a test application that demonstrates the issue:
>
> class NotYetCalculated:
>     pass
>
> class CalcList(list):
>     def __init__(self, calcitem):
>         super(CalcList, self).__init__()
>         self.calcitem = calcitem
>
>     def __getitem__(self, key):
>         """Override __getitem__ to call self.calcitem() if needed"""
>         print "CalcList.__getitem__(): Enter"
>         value = super(CalcList, self).__getitem__(key)
>         if value is NotYetCalculated:
>             print "CalcList.__getitem__(): calculating"
>             value = self.calcitem(key)
>             self[key] = value
>         print "CalcList.__getitem__(): return"
>         return value
>
> def calcitem(key):
>     # Demo: return square of index
>     return key*key
>
> What's the best way to fix this problem?  Do I need to maybe override
> another method, perhaps provide my own iterator implementation? For that
> matter, why doesn't iterating over the list contents fall back to calling
> __getitem__?

It would use __getitem__ if __iter__ wasn't defined. e.g.:

>>> class A(object):
...   def __getitem__(self, index):
...     if index > 4:
...       raise IndexError
...     return index ** 2
...
>>> a = A()
>>> for x in a:
...   print(x)
...
0
1
4
9
16

The problem is that by subclassing list you inherit its __iter__
method. A for loop begins by calling iter()  on the iterable. The iter
function is roughly like:

def iterseq(seq):
  count = 0
  while True:
    try:
       yield seq[count]
    except IndexError:
      return
    count += 1

def iter(iterable):
  if hasattr(iterable, '__iter__'):
     return iterable.__iter__()
  elif hasattr(iterable, '__getitem__'):
    return iterseq(iterable)
  else:
    raise TypeError


Oscar


More information about the Tutor mailing list