<p>A common misconception many people have is that Python 3's <code>range(...)</code> function returns an iterator of some kind. This is actually false: it returns a custom sort of sequence object (which, by definition, is also an iterable). What this means is that I think it actually <em>is</em> legitimate to teach <code>range(...)</code> as being basically a "list-like" thing.</p>

<p>More precisely, it returns an object that actually supports almost the same set of operations Python's <code>list</code> object supports -- the only exceptions are that any methods that allow mutation (such as <code>.append(...)</code>) are missing, and the <code>.copy()</code> method is missing for some reason.</p>

<p>More precisely, the range object...</p>

<ul>
<li>...supports O(1) indexing</li>
<li>...supports slicing</li>
<li>...can be iterated over multiple times, just like a list. They aren't "consumed"after one pass (After all, ranges are not only iterable, they're sequences).</li>
<li>You can check two ranges against each other by equality</li>
<li>It supports a wide variety of dunder methods -- for example, doing <code>4 in range(3, 5)</code> or <code>reversed(range(3, 10))</code> both work more or less as expected.</li>
</ul>

<p>What this means is that if you're teaching how to use <code>range(...)</code>, you can skip having to talk about generators/the whole iterable vs iterator thing entirely, and just introduce <code>range</code> as a special sort of list that's specifically optimized to represent a large range of numbers w/o needing to consume a huge amount of memory.</p>

<p>This means you still need to introduce a new concept ("oh btw, here's another list-like thing"), but hopefully this shouldn't be as new of a concept if you're also planning on introducing things like tuples or sets.</p>

<p>You could even use this as a way of introducing the notion of objects/how everything in Python is an object. For example, you could introduce the concept of fields and methods using <code>range</code>'s <code>.start</code>, <code>.stop</code>, and <code>.step</code> attributes and <code>.count(...)</code> and <code>.index(...)</code> methods.</p>

<p>For zip, no such luck, I'm afraid. The <code>zip(...)</code> function genuinely does return an iterator (e.g. an object that's consumed after one iteration).</p>

<p>But given how <code>zip</code> is really more of a convenience feature more then anything, you could perhaps defer talking about it until later once students are comfortable with the notion of iterating over various collection types.</p>

CC BY-SA 3.0
2018-02-16T20:18:45.273Z
2018-02-16T20:18:45.273Z
Michael0x2a
<blockquote>
  <p>...but that's really changing the output of zip into something else.</p>
</blockquote>

<p>You can print the value returned by <code>zip(...)</code>.  It just isn't as transparent as it used to be. </p>

<pre><code>Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; z = zip([1, 2, 3], ["x", "y", "z"])
&gt;&gt;&gt; repr(z)
'&lt;zip object at 0x7f197ce6f688&gt;'
</code></pre>

<p>You shy away from "turning it into something else," but really?  You only have to type eight more keystrokes in the repl:</p>

<pre><code>&gt;&gt;&gt; list(z)
[(1, 'x'), (2, 'y'), (3, 'z')]
&gt;&gt;&gt;
</code></pre>

<p>And anyway, that's how it works.  You <em>have</em> to turn it into something else.  Python3 is not just a bunch of new features added to Python2:  It's a new language.</p>

<p>It's a better language---one where you can do this:</p>

<pre><code>&gt;&gt;&gt; z = zip(range(10000000000), range(10000000000))
&gt;&gt;&gt; z.__next__()
(0, 0)
&gt;&gt;&gt; z.__next__()
(1, 1)
</code></pre>

CC BY-SA 3.0
2018-02-16T21:27:21.060Z
Yes, a new language, and a better one. Think anew about how you teach it.
Buffy
CC BY-SA 3.0
2018-02-16T21:09:26.083Z
2018-02-16T21:09:26.083Z
Solomon Slow
<p>As suggested, moved from comment to answer.</p>

<p>A range is exactly that - a definition of a beginning and an end point. The 2.7 default behavior made it look as if it were a list, and confused the issue. To new programmers, there is no "change", it is simply how things are. Only those with prior expectations will be confused.</p>

<p>My perspective is someone who has no practical experience in Python, but decades of OO programming experience. I would find the 2.7 implementation of range confusing because a list is not inherently a range, although a range could be implemented as a list with a length of two (beginning and end).</p>

CC BY-SA 3.0
2018-01-23T17:26:11.867Z
The thing which is confusing you isn't the implementation but the name. `range` doesn't return a range in the sense of an object which represents a continuous subset of the real line. It returns an iterable/enumerable object which can be used to use foreach loops instead of C-style for loops for fixed start and end points.
Peter Taylor
CC BY-SA 3.0
2018-01-24T05:03:51.977Z
Exactly. The 2.7 implementation is not a "range" in the strict sense of the word, so you have to know the library to use it.  The 3.0 implementation is exactly a "range" in the strictest sense of the word, so anyone can read the code and see what it's doing.
pojo-guy
CC BY-SA 3.0
2018-01-13T00:20:25.283Z
2018-01-13T00:20:25.283Z
pojo-guy
<p>I don't think the concepts behind <a href="https://wiki.python.org/moin/Generators" rel="nofollow noreferrer">generators</a>, sequences and <a href="https://wiki.python.org/moin/Iterator" rel="nofollow noreferrer">iterators</a> are too difficult to grasp if you're clear on what exactly they are.</p>

<p>The 'old' <code>range(n)</code> function of Python 2 just produces a list of numbers: </p>

<pre><code>[0, 1, 2, 3, ... , n - 1]
</code></pre>

<p>You ask it to give you a list of numbers from $0$ to $n - 1$, and it gives you a <code>list</code>.</p>

<p>The new <code>range(n)</code> doesn't do that. Instead, it makes an object that knows the start, end and step that you asked for... <em>but it doesn't give you a list!</em> This object will, however, give you the <em>next</em> element of the sequence if you ask for it.</p>

<p>Running <code>list(range(n))</code> specifically asks for the list of all the integers, but in the vast majority of cases, you don't need the list, so there's no point making it and storing the whole thing in memory.</p>

<p>Imagine you wanted to print the numbers 1 to 1000. Python 2's <code>range()</code> would generate <em>every</em> number up front, before you can begin iterating. Clearly, this is very wasteful and pointless; it's much easier to work out each number at the end of the iteration, and doesn't require nearly as much memory. </p>

<p>However, I suppose that the merits of the feature aren't <em>really</em> relevant or important to absolute beginners; it's better that they gain some understanding of how to use the feature before understanding the merits behind it. Instead, consider explaining like this:</p>

<ul>
<li><p>Iterables (such as <code>range</code> and <code>zip</code>) only do work when specifically asked (they're lazy — if your students have had any exposure to functional programming, that might be familiar)</p></li>
<li><p>Generally, being lazy makes things faster, because you don't do unnecessary work (e.g. in case you quit the loop early).</p></li>
<li><p>If you specifically need the whole list, you can ask for it with <code>list(iterable)</code> — but this is usually not as common as just iterating through with <code>for</code>.</p></li>
</ul>

<p>If your students know Python 2's <code>xrange</code>, they'll find <code>range</code> simple to understand. </p>

CC BY-SA 3.0
2018-01-11T19:37:30.583Z
They definitely don't know `xrange` or anything about functional programming. Depending on the class, they either have never programmed before at all or have a rudimentary background.
jaia
CC BY-SA 3.0
2018-01-11T17:13:45.507Z
2018-01-11T17:13:45.507Z
Aurora0001
<p>I do mathematical biology curriculum development and design and sometimes teach the associated computer labs. The students are very much beginners. Recently, I have had to start working with a Python 3-based system rather than a Python 2.7-based one. Some of the changes in Python 3 have made basic functions much more abstract. I can't have students just type in <code>zip([1,2,3], [4,5,6])</code> to see what it does. (I had them do <code>list(zip([1,2,3], [4,5,6]))</code>, but that's really changing the output of <code>zip</code> into something else.) And <code>range</code> no longer outputs a list -- instead, it outputs something that counts, but again, is quite abstract. How would you explain these things to total beginners?</p>

CC BY-SA 3.0
2018-01-11T02:15:15.420Z
Hi @jaia! Welcome to [cseducators.se]! I hope you're able to get some useful answers :)
thesecretmaster
CC BY-SA 3.0
2018-01-11T02:55:43.360Z
Are you asking about how to explain the language changes? Or how to explain the new features to people who've never seen python before? Also, why isn't using python 2 an option?
thesecretmaster
CC BY-SA 3.0
2018-01-11T05:02:04.697Z
A bit of both, but mostly how to explain the new features to students with no programming experience at all. (In this particular class, most students do have some Python 2 experience, but I'm thinking about the future.) And it was the main instructor's decision.
jaia
CC BY-SA 3.0
2018-01-12T06:47:59.123Z
I had this problem the other day. I told a pupil type `range(10)` into the interactive shell (to see what it is), but it now replies `range(0,10)`. When in the past it showed a list `[0,1,2,3,4,5,6,7,8,9]`. This made it possible to see that it could be replaced by a list.
ctrl-alt-delor
CC BY-SA 3.0
2018-01-12T21:42:06.497Z
A range is exactly that - a definition of a beginning and an end point. The 2.7 default behavior made it look as if it were a list, and confused the issue.  To new programmers, there is no "change", it is simply how things are.  Only those with prior expectations will be confused.
pojo-guy
CC BY-SA 3.0
2018-01-12T23:46:59.367Z
@pojo-guy Good point. The only problem is that explaining it that way makes it look like there are two fundamentally different ways to do for loops. And you can't directly examine what the range function does. But maybe you should make your comment an answer.
jaia
CC BY-SA 3.0
2018-01-13T11:31:03.587Z
Yes, with the old behaviour, it was obvious that a list could stand in place of a range. Is there a way to make this connection with python 3 (to make it easier to **learn**)?
ctrl-alt-delor
CC BY-SA 3.0
2018-02-16T21:14:07.593Z
@ctrl-alt-delor, Don't think of Python3 as "new" and Python2 as "old."  They are different languages, and the developers still are coming up with new versions of both of them.
Solomon Slow
CC BY-SA 3.0
2018-02-17T09:57:02.930Z
@jameslarge I know. It is true that python 3 is a newer language though. I also, mostly, prefer python3 (for an exception see my comment above).
ctrl-alt-delor
<python>
Teaching zip and range in Python 3
# Teaching zip and range in Python 3

(Asked by: username_0 on 2018-01-11)

I do mathematical biology curriculum development and design and sometimes teach the associated computer labs. The students are very much beginners. Recently, I have had to start working with a Python 3-based system rather than a Python 2.7-based one. Some of the changes in Python 3 have made basic functions much more abstract. I can't have students just type in `zip([1,2,3], [4,5,6])` to see what it does. (I had them do `list(zip([1,2,3], [4,5,6]))`, but that's really changing the output of `zip` into something else.) And `range` no longer outputs a list -- instead, it outputs something that counts, but again, is quite abstract. How would you explain these things to total beginners?

**Tags:** python

> **Comments:**
> **username_1**: Hi @username_0! Welcome to [cseducators.se]! I hope you're able to get some useful answers :)  
> **username_1**: Are you asking about how to explain the language changes? Or how to explain the new features to people who've never seen python before? Also, why isn't using python 2 an option?  
> **username_0**: A bit of both, but mostly how to explain the new features to students with no programming experience at all. (In this particular class, most students do have some Python 2 experience, but I'm thinking about the future.) And it was the main instructor's decision.  
> **username_2**: I had this problem the other day. I told a pupil type `range(10)` into the interactive shell (to see what it is), but it now replies `range(0,10)`. When in the past it showed a list `[0,1,2,3,4,5,6,7,8,9]`. This made it possible to see that it could be replaced by a list.  
> **username_3**: A range is exactly that - a definition of a beginning and an end point. The 2.7 default behavior made it look as if it were a list, and confused the issue.  To new programmers, there is no "change", it is simply how things are.  Only those with prior expectations will be confused.  
> **username_0**: @username_3 Good point. The only problem is that explaining it that way makes it look like there are two fundamentally different ways to do for loops. And you can't directly examine what the range function does. But maybe you should make your comment an answer.  
> **username_2**: Yes, with the old behaviour, it was obvious that a list could stand in place of a range. Is there a way to make this connection with python 3 (to make it easier to **learn**)?  
> **username_4**: @username_2, Don't think of Python3 as "new" and Python2 as "old."  They are different languages, and the developers still are coming up with new versions of both of them.  
> **username_2**: @jameslarge I know. It is true that python 3 is a newer language though. I also, mostly, prefer python3 (for an exception see my comment above).  

---

## 4 answers

### Answer by username_8 on 2018-01-11. Score: 3

I don't think the concepts behind generators, sequences and iterators are too difficult to grasp if you're clear on what exactly they are.
The 'old' range(n) function of Python 2 just produces a list of numbers: 
[0, 1, 2, 3, ... , n - 1]

You ask it to give you a list of numbers from $0$ to $n - 1$, and it gives you a list.
The new range(n) doesn't do that. Instead, it makes an object that knows the start, end and step that you asked for... but it doesn't give you a list! This object will, however, give you the next element of the sequence if you ask for it.
Running list(range(n)) specifically asks for the list of all the integers, but in the vast majority of cases, you don't need the list, so there's no point making it and storing the whole thing in memory.
Imagine you wanted to print the numbers 1 to 1000. Python 2's range() would generate every number up front, before you can begin iterating. Clearly, this is very wasteful and pointless; it's much easier to work out each number at the end of the iteration, and doesn't require nearly as much memory. 
However, I suppose that the merits of the feature aren't really relevant or important to absolute beginners; it's better that they gain some understanding of how to use the feature before understanding the merits behind it. Instead, consider explaining like this:

Iterables (such as range and zip) only do work when specifically asked (they're lazy — if your students have had any exposure to functional programming, that might be familiar)
Generally, being lazy makes things faster, because you don't do unnecessary work (e.g. in case you quit the loop early).
If you specifically need the whole list, you can ask for it with list(iterable) — but this is usually not as common as just iterating through with for.

If your students know Python 2's xrange, they'll find range simple to understand.

> **Comments:**
> **username_0**: They definitely don't know `xrange` or anything about functional programming. Depending on the class, they either have never programmed before at all or have a rudimentary background.  

---

### Answer by username_5 on 2018-02-16. Score: 1

A common misconception many people have is that Python 3's `range(...)` function returns an iterator of some kind. This is actually false: it returns a custom sort of sequence object (which, by definition, is also an iterable). What this means is that I think it actually *is* legitimate to teach `range(...)` as being basically a "list-like" thing.

More precisely, it returns an object that actually supports almost the same set of operations Python's `list` object supports -- the only exceptions are that any methods that allow mutation (such as `.append(...)`) are missing, and the `.copy()` method is missing for some reason.

More precisely, the range object...

* ...supports O(1) indexing
* ...supports slicing
* ...can be iterated over multiple times, just like a list. They aren't "consumed"after one pass (After all, ranges are not only iterable, they're sequences).
* You can check two ranges against each other by equality
* It supports a wide variety of dunder methods -- for example, doing `4 in range(3, 5)` or `reversed(range(3, 10))` both work more or less as expected.

What this means is that if you're teaching how to use `range(...)`, you can skip having to talk about generators/the whole iterable vs iterator thing entirely, and just introduce `range` as a special sort of list that's specifically optimized to represent a large range of numbers w/o needing to consume a huge amount of memory.

This means you still need to introduce a new concept ("oh btw, here's another list-like thing"), but hopefully this shouldn't be as new of a concept if you're also planning on introducing things like tuples or sets.

You could even use this as a way of introducing the notion of objects/how everything in Python is an object. For example, you could introduce the concept of fields and methods using `range`'s `.start`, `.stop`, and `.step` attributes and `.count(...)` and `.index(...)` methods.

For zip, no such luck, I'm afraid. The `zip(...)` function genuinely does return an iterator (e.g. an object that's consumed after one iteration).

But given how `zip` is really more of a convenience feature more then anything, you could perhaps defer talking about it until later once students are comfortable with the notion of iterating over various collection types.


---

### Answer by username_4 on 2018-02-16. Score: 1

...but that's really changing the output of zip into something else.

You can print the value returned by zip(...).  It just isn't as transparent as it used to be. 
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> z = zip([1, 2, 3], ["x", "y", "z"])
>>> repr(z)
''

You shy away from "turning it into something else," but really?  You only have to type eight more keystrokes in the repl:
>>> list(z)
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>

And anyway, that's how it works.  You have to turn it into something else.  Python3 is not just a bunch of new features added to Python2:  It's a new language.
It's a better language---one where you can do this:
>>> z = zip(range(10000000000), range(10000000000))
>>> z.__next__()
(0, 0)
>>> z.__next__()
(1, 1)

> **Comments:**
> **username_6**: Yes, a new language, and a better one. Think anew about how you teach it.  

---

### Answer by username_3 on 2018-01-13. Score: 1

As suggested, moved from comment to answer.

A range is exactly that - a definition of a beginning and an end point. The 2.7 default behavior made it look as if it were a list, and confused the issue. To new programmers, there is no "change", it is simply how things are. Only those with prior expectations will be confused.

My perspective is someone who has no practical experience in Python, but decades of OO programming experience. I would find the 2.7 implementation of range confusing because a list is not inherently a range, although a range could be implemented as a list with a length of two (beginning and end).

> **Comments:**
> **username_7**: The thing which is confusing you isn't the implementation but the name. `range` doesn't return a range in the sense of an object which represents a continuous subset of the real line. It returns an iterable/enumerable object which can be used to use foreach loops instead of C-style for loops for fixed start and end points.  
> **username_3**: Exactly. The 2.7 implementation is not a "range" in the strict sense of the word, so you have to know the library to use it.  The 3.0 implementation is exactly a "range" in the strictest sense of the word, so anyone can read the code and see what it's doing.  

---

