Skip to content

Commit cd677c9

Browse files
committed
Tweaks to page about returning early
1 parent 1d36536 commit cd677c9

File tree

2 files changed

+294
-81
lines changed

2 files changed

+294
-81
lines changed

backend/main/chapters/c09_functions.py

Lines changed: 43 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -692,15 +692,14 @@ def alert(string: str, level: int):
692692

693693

694694
class MoreOnReturn(Page):
695-
title = "More on the `return` statement"
695+
title = "`return` ends the function call"
696696

697697
class double_return_in_one_function(VerbatimStep):
698698
"""
699699
Sometimes `return` can be a source of confusion and mistakes for new learners.
700-
Let's learn more about how `return` works, how it interacts with `if` statements and `for` loops.
700+
Let's learn more about how it works.
701701
702-
First let's take a look at what happens when a function contains multiple `return` statements.
703-
What do you think the following code will do?
702+
Run this code:
704703
705704
__copyable__
706705
__program_indented__
@@ -712,6 +711,8 @@ class double_return_in_one_function(VerbatimStep):
712711
""", """\
713712
2
714713
""", """\
714+
[1, 2]
715+
""", """\
715716
1
716717
2
717718
""", """\
@@ -728,16 +729,18 @@ def foo():
728729

729730
class cannot_return_multiple_values(VerbatimStep):
730731
"""
731-
In the case of multiple `return` statements it's first reached, first served.
732-
Whichever `return` is reached first by the code is the one that gets executed.
733-
Once a `return` statement is executed, the function will stop, and the rest of the code will not get executed.
734-
This means that it's possible to write unreachable `return` statements like the above:
732+
Once a `return` statement is executed, the function will stop, and the rest of the code is ignored.
733+
This means that any code immediately after a `return` in the same block is *unreachable*:
735734
`return 2` can *never* be reached no matter how many times we run this function!
736-
Because `return 1` will always be executed first and stop the function.
737735
738-
***One, and only one `return` can be executed per function call (which will stop the execution)!***
736+
***One, and only one `return` can be executed per function call, then execution stops.***
737+
738+
Multiple `return` statements can still be useful when used properly, e.g. in an `if-else` block:
739739
740-
So multiple `return` statements can be useful only in the case of multiple branches in the code, such as an `if-else` block.
740+
if condition:
741+
return value1
742+
else:
743+
return value2
741744
742745
A common mistake is to misunderstand what `return` does in `for` loops. Try the following:
743746
@@ -754,18 +757,13 @@ def double_numbers(numbers):
754757

755758
class return_ends_whole_function(VerbatimStep):
756759
"""
757-
At first it may look intuitive to combine `return` with lists and `for` loops as above:
758-
**"return one thing for each thing in a list"**. But it doesn't work like that!
759-
760-
If you inspect the code with *Snoop* you'll see what is happening clearly:
760+
At first it may look intuitive to `return` one value for each iteration in a `for` loop.
761+
But it doesn't work like that!
762+
If you inspect the code with Snoop or Python tutor you can see that the function returns 2 in the first
763+
loop iteration and then ends immediately.
761764
762-
- the call to `double_numbers` begins, and then
763-
- only the first step of the `for` loop is executed (which sets up `x = 1`),
764-
- after which `1 * 2 = 2` is returned and the function stops!
765-
- Then `assert_equal` rightfully complains that `2 != [2, 4, 6]`.
766-
767-
So we cannot use `return` to output multiple values like that.
768-
Instead we can build a list by appending the doubled numbers to it, and then return the whole list:
765+
Even when there's only one `return` statement, it will get executed only once and return one value.
766+
If you want to return several values, return a list:
769767
770768
__copyable__
771769
def double_numbers(numbers):
@@ -776,7 +774,6 @@ def double_numbers(numbers):
776774
777775
assert_equal(double_numbers([1, 2, 3]), [2, 4, 6])
778776
779-
Above we saw that `return` stopped a `for` loop only after the first step of the loop.
780777
What happens if there are nested loops? Try the following function:
781778
782779
__copyable__
@@ -785,10 +782,10 @@ def double_numbers(numbers):
785782

786783
def program(self):
787784
def foo():
788-
for letter in 'abcde':
785+
for letter in 'abc':
789786
for number in range(3):
790787
print(f"{letter} {number}")
791-
if letter == 'c':
788+
if letter == 'b':
792789
return letter
793790

794791
foo()
@@ -798,34 +795,26 @@ def foo():
798795
a 0
799796
a 1
800797
a 2
798+
""", """\
799+
a 0
800+
a 1
801+
a 2
801802
b 0
802-
b 1
803-
b 2
804-
c 0
805803
""", """\
806804
a 0
807805
a 1
808806
a 2
809807
b 0
810808
b 1
811809
b 2
812-
c 0
813-
c 1
814-
c 2
815810
""", """\
816811
a 0
817812
a 1
818813
a 2
819814
b 0
820-
b 1
821-
b 2
822815
c 0
823-
d 0
824-
d 1
825-
d 2
826-
e 0
827-
e 1
828-
e 2
816+
c 1
817+
c 2
829818
""", """\
830819
a 0
831820
a 1
@@ -836,31 +825,25 @@ def foo():
836825
c 0
837826
c 1
838827
c 2
839-
d 0
840-
d 1
841-
d 2
842-
e 0
843-
e 1
844-
e 2
845828
"""
846829
]
847830

848831
class break_vs_return(VerbatimStep):
849832
"""
850-
As you see `return` does not only stop the inner loop or the outer loop, but ***it stops the whole function.***
833+
As before, `return` ***stops the whole function***, including all loops.
851834
852-
Previously you learned a way to stop loops early, by using `break`. Let's compare `break` to `return` and see how they are different.
853-
Change the function as follows:
854-
855-
__program_indented__
835+
Previously we showed [how to stop a loop with `break`](/course/?page=UsingBreak).
836+
Change `return letter` to `break` and see what the difference is.
856837
"""
857838

839+
program_in_text = False
840+
858841
def program(self):
859842
def foo():
860-
for letter in 'abcde':
843+
for letter in 'abc':
861844
for number in range(3):
862845
print(f"{letter} {number}")
863-
if letter == 'c':
846+
if letter == 'b':
864847
break
865848

866849
foo()
@@ -870,41 +853,26 @@ def foo():
870853
a 0
871854
a 1
872855
a 2
873-
b 0
874-
b 1
875-
b 2
876856
""", """\
877857
a 0
878858
a 1
879859
a 2
880860
b 0
881-
b 1
882-
b 2
883-
c 0
884861
""", """\
885862
a 0
886863
a 1
887864
a 2
888865
b 0
889866
b 1
890867
b 2
891-
c 0
892-
c 1
893-
c 2
894868
""", """\
895869
a 0
896870
a 1
897871
a 2
898872
b 0
899-
b 1
900-
b 2
901873
c 0
902-
d 0
903-
d 1
904-
d 2
905-
e 0
906-
e 1
907-
e 2
874+
c 1
875+
c 2
908876
""", """\
909877
a 0
910878
a 1
@@ -915,21 +883,15 @@ def foo():
915883
c 0
916884
c 1
917885
c 2
918-
d 0
919-
d 1
920-
d 2
921-
e 0
922-
e 1
923-
e 2
924886
"""
925887
]
926888

927889
final_text = """
928-
Unlike `return`, `break` only stops the loop in which it is used.
929-
In this case only the inner loop `for number in range(3):` is stopped by `break`:
890+
Unlike `return`, `break` only stops the innermost loop in which it is used, in this case `for number in range(3):`.
891+
Here's exactly what happens:
930892
931-
- For `letter = c`, the line `print(f"{letter} {number}")` is executed only for `number = 0`,
893+
- For `letter = b`, the line `print(f"{letter} {number}")` is executed only for `number = 0`,
932894
- then the inner loop is stopped by `break`, but
933-
- the outer loop continues its execution, moving on to the next letter `d`, and then `e`,
934-
- the inner loop is still executed for letters `d` and `e` since they do not trigger the `break` statement.
935-
"""
895+
- the outer loop continues its execution, moving on to the next letter `c`
896+
- which is executed in full since it does not trigger the `break` statement.
897+
"""

0 commit comments

Comments
 (0)