futurecoder
futurecoder copied to clipboard
Functions chapter
Some things to add to the current functions chapter:
- In the page on calling functions from functions, after showing what this looks like in various debuggers, I want to show what it looks like in a traceback. An exception should be raised in the innermost function (e.g. by intentionally misspelling a name) and then explain the resulting traceback.
- Scope and local variables:
- Explain that variables defined in one function are not accessible outside that function
- If they want to access them, they should return them
- However they can access global variables from within a function
- Explain that global and local variables should have different names to avoid errors
- Explain that
returnends the function call. Sometimes beginners want to return a list of values like this:
def double_nums(nums):
for n in nums:
return n * 2
Explain that they must build up a list instead. Maybe hint at generators but don't actually show them.
Things that will only be covered in a later chapter:
- Variadic, named, or optional arguments/parameters
- Higher order functions, functions as objects, functional programming, etc.
- How to set global variables with the
globalstatement - Inner functions and closures
I think this goes here:
Page on `return` to stop execution early
Early Return
Page: More on the return statement
Step: double return in one function (VerbatimStep)
Sometimes return can be a source of confusion and mistakes for new learners. Let's learn more about how return works, how it interacts with if statements and for loops.
First let's take a look at what happens when a function contains multiple return statements. What do you think the following code will do?
def foo():
return 1
return 2
print(foo())
Predicted output choices:
-
1 -
2 -
12 -
1 2 -
Error
user runs code, predicts output, reaches next Step
Step: cannot return multiple values (VerbatimStep)
In the case of multiple return statements it's first reached, first served. Whichever return is reached first by the code is the one that gets executed. Once a return statement is executed, the function will stop, and the rest of the code will not get executed. This means that it's possible to write unreachable return statements like the above: return 2 can never be reached no matter how many times we run this function! Because return 1 will always be executed first and stop the function.
One, and only one return can be executed per function call (which will stop the execution)!
So multiple return statements can be useful only in the case of multiple branches in the code, such as an if-else block. A common mistake is to misuse if conditions to create unreachable return statements:
if condition:
return 1
else:
return 2
If condition always ends up being True then the else part will never be executed, so return 2 can never be reached! Similarly if condition always ends up being False then return 1 will never be reached. This means that you have to think carefully about your conditions and all the possibilities.
Moreover since the first return 1 statement would stop the execution if it's reached, the else is not necessary. The above is equivalent to:
if condition:
return 1
return 2
Another common mistake is to misunderstand what return does in for loops. Try the following:
def double_numbers(numbers):
for x in numbers:
return x * 2
assert_equal(double_numbers([1, 2, 3]), [2, 4, 6])
user runs code, reaches next Step
Step: correct way to return: building a list and appending (ExerciseStep)
At first it may look intuitive to combine return with lists and for loops as above: "return one thing for each thing in a list". But it doesn't work like that!
If you inspect the code with Snoop you'll see what is happening clearly:
- the call to
double_numbersbegins, and then - only the first step of the
forloop is executed (which sets upx = 1), - after which
1 * 2 = 2is returned and the function stops! - Then
assert_equalrightfully complains that2 != [2, 4, 6].
So we cannot use return to output multiple values like that.
Now I'd like you to fix the code, so that the assert_equal statement gives OK. It should also work correctly for any list of numbers.
Hints:
What do you need to return to make the assertion correct?
What needs to be returned can be built up step by step with the for loop.
What method/function can you use to build up the result you need?
Solution:
def double_numbers(numbers):
doubles = []
for x in numbers:
doubles.append(x * 2)
return doubles
user solves exercise, reaches next Step
Step: return ends the whole function, not just the loop (VerbatimStep)
Excellent! Now you know how to correctly return multiple values by building up lists.
Above we saw that return stopped a for loop only after the first step of the loop. What happens if there are nested loops? Try the following function:
def foo():
for letter in 'abcde':
for number in range(3):
print(f"{letter} {number}")
if letter == 'c':
return letter
foo()
Predicted output choices:
-
a 0 a 1 a 2 b 0 b 1 b 2 c 0 -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 c 1 c 2 -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 d 0 d 1 d 2 e 0 e 1 e 2 -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 c 1 c 2 d 0 d 1 d 2 e 0 e 1 e 2 -
Error
user runs code, predicts output, reaches next Step
Step: break vs return: break only stops the inner loop (VerbatimStep)
As you see return does not only stop the inner loop or the outer loop, but it stops the whole function.
Previously you learned a way to stop loops early, by using break. Let's compare break to return and see how they are different. Change the function as follows:
def foo():
for letter in 'abcde':
for number in range(3):
print(f"{letter} {number}")
if letter == 'c':
break
return letter
print(foo())
Predicted output choices:
-
a 0 a 1 a 2 b 0 b 1 b 2 c -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 c -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 c 1 c 2 c -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 d 0 d 1 d 2 e 0 e 1 e 2 e -
a 0 a 1 a 2 b 0 b 1 b 2 c 0 c 1 c 2 d 0 d 1 d 2 e 0 e 1 e 2 e -
Error
user runs code, predicts output, reaches Final Text
Final Text: using return to your advantage
Unlike return, break only stops the loop in which it is used. In this case only the inner loop for number in range(3): is stopped by break:
- For
letter = c, the lineprint(f"{letter} {number}")is executed only fornumber = 0, - then the inner loop is stopped by
break, but - the outer loop continues its execution, moving on to the next letter
d, and thene, - the final value of
letteriseso that is returned by the function.
user reaches next Page
Leave out the if/else part. I often include the else as an intentional stylistic choice. "If condition always ends up being True" then there's a bigger problem which has little to do with this chapter.
There's already a double_numbers step in chapter 6. They should already know how to do this, we're just quickly reminding them that this is the correct way. I think making them do this as an exercise is too tedious.
This leaves the page with no exercises, which is maybe a problem, but i think it's ok.
All the code should be copyable, except the last one. The user should only have to change return letter to break so that they understand the difference. No need to print the return value.