0

I am new to learn python these days. While reading a book, I found a line of code that I can't understand. Please see line 46 under print_progression() method, print(' '.join(str(next(self)) for j in range(n))).

class Progression:
    '''Iterator producing a generic progression.
    Default iterator produces the whole number, 0, 1, 2, ...
    '''

def __init__(self, start = 0):
    '''
    Initialize current to the first value of the progression.
    '''
    self._current = start

def _advance(self):
    '''
    Update self.current to a new value.
    This should be overriden by a subclass to customize progression.
    By convension, if current is set to None, this designates the
    end of a finite progression.
    '''
    self._current += 1

def __next__(self):
    '''
    Return the next element, or else raise StopIteration error.
    '''
    # Our convention to end a progression
    if self._current is None:
        raise StopIteration()
    else:
        # record current value to return
        answer = self._current
        # advance to prepare for next time
        self._advance()
        # return the answer
        return answer

def __iter__(self):
    '''
    By convention, an iterator must return itself as an iterator.
    '''
    return self

def print_progression(self, n):
    '''
    Print next n values of the progression.
    '''
    print(' '.join(str(next(self)) for j in range(n)))


class ArithmeticProgression(Progression): # inherit from Progression
    pass

if __name__ == '__main__':
    print('Default progression:')
    Progression().print_progression(10)

'''Output is
Default progression:
0 1 2 3 4 5 6 7 8 9 10'''

I have no idea how next(self) and j works.

  1. I think it should be str(Progression.next()). (solved)

  2. I cannot find j anywhere. What is j for? Why not using while loop such as while Progression.next() <= range(n)?

For my final thought, it should be

print(' '.join(str(next(self)) while next(self) <= range(n)))

Save this newbie.

Thanks in advance!

Jonas
  • 121,568
  • 97
  • 310
  • 388
  • That section of code **is** a loop, it's called a "list comprehension" or a "generator comprehension". `next(self)` is the same as running the `__next__` method – Ofer Sadan Jun 07 '18 at 19:21
  • probably a duplicate of https://stackoverflow.com/questions/40255096/next-in-generators-and-iterators-and-what-is-a-method-wrapper – Vamsidhar Reddy Gaddam Jun 07 '18 at 19:22
  • Thanks guys! What about j? I can't find j in the code. if it is used nowhere, why not use while loop? – Andy Junghyun Kim Jun 07 '18 at 19:25
  • `while` loops are traditionally used for code that runs indefinitely. Since you have a fixed number of items to iterate over, use a `for` loop. The value of `j`, itself, is throw away. – roganjosh Jun 07 '18 at 19:28
  • Thank you, but I still can't get how j is used here. For example for j in range(10): j += j, j should be somewhere in the code as well... – Andy Junghyun Kim Jun 07 '18 at 19:31
  • @AndyJunghyunKim you should really look up tutorials on list comprehensions and that will clear up the confusion you have about `j` (which is nothing special, just a place holder for items in the `range`). Try starting here: http://www.pythonforbeginners.com/basics/list-comprehensions-in-python – Ofer Sadan Jun 07 '18 at 19:34
  • No, `j` is auto-incremented. `range(10)` gives you a list of numbers (if we ignore the generator in python 3). We want to go through that list, so just use some random name to assign to each value in the list. The assignment is pointless (EDIT: insignificant) because we don't want to use `j` but it allows us to do something 10 times. – roganjosh Jun 07 '18 at 19:35
  • In other words, `(str(next(self)) for j in range(n)))` just means that `str(next(self))` will run for `n` times, `j` is just there as part of the syntax in this specific case – Ofer Sadan Jun 07 '18 at 19:35

2 Answers2

1

I think @csevier added a reasonable discussion about your first question, but I'm not sure the second question is answered as clearly for you based on your comments so I'm going to try a different angle.

Let's say you did:

for x in range(10):
    print(x)

That's reasonably understandable - you created a list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] and you printed each of the values in that list in-turn. Now let's say that we wanted to just print "hello" 10 times; well we could modify our existing code very simply:

for x in range(10):
    print(x)
    print('hello')

Umm, but now the x is messing up our output. There isn't a:

do this 10 times:
    print('hello')

syntax. We could use a while loop but that means defining an extra counter:

loop_count = 0
while loop_count < 10:
    print('hello')
    loop_count += 1 

That's baggage. So, the better way would be just to use for x in range(10): and just not bother doing print(x); the value is there to make our loop work, not because it's actually useful in any other way. This is the same for j (though I've used x in my examples because I think you're more likely to encounter it in tutorials, but you could use almost any name you want). Also, while loops are generally used for loops that can run indefinitely, not for iterating over an object with fixed size: see here.

roganjosh
  • 12,594
  • 4
  • 29
  • 46
0

Welcome to the python community! This is a great question. In python, as in other languages, there are many ways to do things. But when you follow a convention that the python community does, that is often referred to as a "pythonic" solution. The method print_progression is a common pythonic solution to iteration of a user defined data structure. In the case above, lets explain first how the code works and then why we would do it that way.

Your print_progression method takes advantage of the fact that your Progression class implements the iteration protocol by implementing the next and iter dunder/magic methods. Because those are implemented you can iterate your class instance both internally as next(self) has done, and externally next(Progression()) which is the exactly what you were getting at with you number 1. Because this protocol is implemented already, this class can by used in any builtin iterator and generator context for any client! Thats a polymorphic solution. Its just used internally as well because you don't need to do it in 2 different ways.

Now for the unused J variable. They are just using that so they can use the for loop. Just using range(n) would just return an itterable but not iterate over it. I dont quite agree with the authors use of the variable named J, its often more common to denote an unused variable that is just used because it needs to be as a single underscore. I like this a little better:

 print(' '.join(str(next(self)) for _ in range(n)))
PoweredBy90sAi
  • 1,163
  • 7
  • 7