Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set your subtitle preferences in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please refer to our video player troubleshooting guide for assistance.

Debugging the outer() Function

00:00 In this lesson, you’ll use the Thonny debugger to step through this piece of code that you quickly refactored in the previous lesson, and then you’ll hopefully get a good idea of what’s going on in terms of different scopes and closures in this example code snippet here.

00:16 So to get started debugging, I’m going to click on the Debug current script button in the top of the Thonny menu bar. It’s a little bug symbol. And when you click this, then it starts the debugger, and you can already see that it nicely highlights the first thing that it’s going to execute inside of the script when you run it, which is going to be the function definition of outer().

00:38 What I’m going to be doing is using the Step into button to just keep stepping through the code line by line, and I’ll use the shortcut, which is F7 in this case.

00:48 So you won’t see me click the button up here, but just know every time that the yellow thing jumps somewhere else is when I’m stepping into the next line of code. So let’s get started.

00:59 First thing is the function definition, and then keep in mind that the function has been defined now, but that doesn’t mean that what’s inside of the function already exists.

01:09 So these variables, for example message here or also the inner() function, they haven’t been defined yet because you just create the outer() function at first, and only when you call the outer() function, all of these variables in here are going to get defined.

01:26 And this is going to happen in the next line of code. On line 11, you’re actually going run the outer() function. You’re going to call it. So in this case, what happens is that Python needs to understand what does this outer() refer to?

01:38 And it figures out that outer() refers to this function up here and opens up a new scope. This scope is the local scope of the outer() function.

01:48 So I can see in here how Thonny’s debugger nicely shows what’s going on here visually. You can see that it even opens up a new window that represents the new scope that you’re working with in here.

01:59 And if you have some understanding of scopes, then you know that this scope has access to the global variables, but for example, any variable that you define in here is not going to be accessible in the global scope. And this local scope during the function execution now actually starts to define also the inner() function.

02:16 And it’s going define the message variable next when we keep stepping through the code. So the function inner() gets defined and then you can see it show up here as a local variable.

02:28 There is now a local variable called inner that points to a function object that is defined inside of the local scope of the outer() function.

02:37 And now next you’re going to define a variable message. And first Python needs to understand what is this 'hello', and it figures out it’s a string and then assigns that string to the variable message. And you can see that one pop up in your local variables here as well.

02:52 Now you have the two defined variables. You have inner, which is a function object, and message, which points to the string 'hello'. Right, now you keep going.

03:01 The next line of code is going to be a call to the inner() function.

03:05 So Python understands what’s going on, and because this is a new function call, it opens up yet another scope for the inner() function. So this is a new local scope that relates to the inner() function.

03:18 And in here, there’s not much going on. You have a call to the print() function that is going to print out a message, but what is this message, right?

03:26 Because you have access to the higher-up scope—in this case, it’s called the nonlocal scope to the inner() function because it’s, you’re talking about a nested function that lives inside of another function—so this nested function has access to the variables that were defined in the function that contains the inner() function. So in our case, this is the outer() function.

03:48 And it also has access to the variables that were defined in the nonlocal scope—in the local scope of the outer() function, which is the nonlocal scope to this inner() function.

03:57 So this is why you can see that the inner() function comes with a local scope that says message 'hello'. So the correct term is to call this variable a nonlocal variable because it isn’t defined inside of the local scope of the inner() function, but in the higher-up scope of the outer() function. Now, if I keep stepping, you’re going to see that Python understands that this is a call to the print() function. It evaluates the variable message in here, understands it points to the string 'hello', and then knows what to do with it, which is print it to the console.

04:28 So you can see it come up out here in your script. In the output, you can see hello printed to the console. And this None that you can see there is just the return value of the print() function, which is None.

04:42 The next step is going to be closing the scope because the function execution is over, and it returns something, and because you don’t have an explicit return value in the function definition of inner(), Python returns the default of None.

04:54 So this is also what Thonny is showing you here. And this ends the local scope of this specific call to the inner() function. Every function call opens up its own scope, even if you call the same function object multiple times.

05:08 So if you keep going now, you’ll see that you’re back inside of the scope of the outer() function, and here the message variable is going to be redefined to something else. It’s going to point now to the string 'world'.

05:20 You can also see that change down here in the local variables. And you’re back to another call to the inner() function. That is going go down pretty similarly to what happened just before. So let’s step through it.

05:33 We have another call to the same function object, but because it’s a new call, it opens up a new scope, and this new scope, again through the nonlocal variables, has access to 'world'.

05:45 So this message is a nonlocal variable, and again, Python will be able to print it, and now it’s not hello anymore, but it’s world.

05:54 And you can see it come up here in the console as well. You have another return value of None for the print() function call, and then another return value of None for the call to the inner() function.

06:03 All right, and then your outer() function is about to return, and this function doesn’t return None, but instead it returns the function object inner, figures out what that is—that is a function object in the local of the outer function called inner()and that gets returned. You can see it here is the return value of the function call that you started with, and it gets assigned to inner_returned, and now it lives inside of here.

06:30 And finally, for the last line of code, line 12, you’re going to call inner_returned() and now it’s a little interesting what’s going to happen there.

06:39 And I’m going to stop this lesson and give you a chance to try it out yourself and see whether what happens is what you expect to happen. Once you’re done, move to the next video, and you’re going to see what happens when we step through this final call to inner_returned().

Become a Member to join the conversation.