简体   繁体   中英

What am I doing wrong regarding iteration

I want to print out all the odd numbers given in the call: o = Odds(10) , the output should be 1, 3, 5, 7, 9 but I'm doing something wrong in the commented area, it should be relatively simple but I can't see it.

class Odds:
    def __init__(self, arg):
        self.num = arg

    def __iter__(self):
        return OddsIterator(self.num)

class OddsIterator:
    def __init__(self, arg):
        self.high = arg
        self.low = 1

    def __next__(self):
        if self.low <= self.high:
            if (self.low % 2) == 0: #somethings wrong around here
                self.low += 1 
            else:
                self.low += 1
                return self.low - 1
        raise StopIteration

Your if branch doesn't return anything , so you always reach raise StopIteration at that point. Say low is 2 and high is 10:

if self.low <= self.high:  # true, 2 <= 10
    if (self.low % 2) == 0:  # true, 2 is even
        self.low += 1 
    # else branch is skipped, so we come to the next line
raise StopIteration  # iterator ends

You need to create larger steps in __next__ , because that method always returns something. Instead of not returning when a number is even, you need to return the next number in the sequence, always, so increment self.low by 2 each time, and the first time make sure you start with an odd number:

def __next__(self):
    if self.low > self.high:
        raise StopIteration
    if self.low % 2 == 0:
        self.low += 1  # ensure we get an odd number first
    retval = self.low
    self.low += 2
    return retval

I inverted testing for the StopIteration condition here to make it clearer that the function always returns something when there is still a value to return from the iterable.

Just to re-iterate, __next__ always returns something, you can't expect it to not return something, so it'll at the very least return None . The iterator is not counting . Python doesn't go lets ask what the result is for 1, then for 2, then for 3 . Python simply asks what the next value in the sequence of odd numbers is . After 1, comes 3, but Python doesn't know that, your code needs to produce that .

Your specific problem is that the first even number will fall through to the StopIteration line, rendering your sequence rather short: { 1 } .

It seems to me that generating all the odd numbers should be substantially easier. Start with -1 then, on every call:

  • add two.
  • if it's too high, stop.
  • otherwise return the current value.

That can be achieved with:

class Odds:
    def __init__(self, arg):
        self.num = arg

    def __iter__(self):
        return OddsIterator(self.num)

class OddsIterator:
    def __init__(self, arg):
        self.high = arg
        self.curr = -1

    def __next__(self):
        self.curr += 2
        if self.curr > self.high:
            raise StopIteration
        return self.curr

o = Odds(10)
for i in o:
    print (i)

Having said that, I'm not sure I'd implement it the same way you did. It seems unnecessary to have two separate classes when you can combine them into a single one:

class Odds:
    def __init__(self, end):
        self.curr = -1
        self.lim = end

    def __next__(self):
        self.curr += 2
        if self.curr > self.lim:
            raise StopIteration
        return self.curr

    def __iter__(self):
        return self

o = Odds(10)
for i in o:
    print (i)

In addition, you can make it far more general by providing start and step values as well:

class XFor:
    def __init__(self, start, end, step):
        self.curr = start - step
        self.lim = end
        self.step = step

    def __next__(self):
        self.curr += self.step
        if self.curr > self.lim:
            raise StopIteration
        return self.curr

    def __iter__(self):
        return self

o = XFor(1,10,2)
for i in o:
    print (i)

Although it's then encroaching dangerously on what range() does so I'd just use that. Unless your intent is self-education, in which case have fun.

How about using a simple generator for this purpose, unless you need the Odds class for other purposes -

def odds(num):
    for i in range(1, num + 1):
        if i % 2 != 0:
            yield i

print(list(odds(11)))

If you do want to use class Odds for specific reason -

class Odds:

    def __init__(self, num):
        self.high = num

    def __iter__(self):
        return odds(self.high)

print(list(Odds(10))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM