 In this lesson, we'll be learning about one of the biggest tools that a programmer has at their disposal, and those are called functions. We've already been using some of Python's functions that are built into the programming language. For example, the print or the absolute value function, these come as part of the Python language. No special commands needed to use them except for their names. But there are a lot of other built-in functions to Python, some that we'll see more later on in this course, and there's actually a whole complete library of these that you can look up online. For example, the int function can take a string that contains numbers and actually turn them into numbers instead of strings for use within calculations. There's the max function that you can provide three different inputs, and then it will give you the largest of those inputs. You can do five, six as many inputs as you like. And then there's a counterpart to that to the min function. So all of these are built-in functions that come with Python right out the box, so to speak. No special instructions necessary. But we can actually write our own function. And we want to think about, well, why would we even want to use functions? And the reason why we would use functions, if you look at these two examples from our previous lesson, we have two code cells that contain almost identical code except for the starting conditions. The values for A and B are different, but everything underneath them is the same. If I needed to compute two GCDs, why would I call this code in two different cells with two different values for A and B? Wouldn't it be great if I could just take all of that relevant code, everything from the while on down, and put that into a function that I could call on command just like I can call the print function. So we're going to have different inputs, but the same operations is a great choice for using functions. So we're going to learn how to write our own functions, and that's a way to divide our code into smaller subparts that will help us from avoiding writing repetitive code over and over again. And we can see here an example of the GCD function. We can call it down here at the bottom two lines of the code and then print to the output of those functions, and we're going to dig in now to see exactly how we would write our own function from start to finish. So we start with this keyword def, D-E-F, and it's just shorthand for define. We're defining our function. What comes next is the name of the function. Here we'll name our function GCD, just like with all variable names in Python, it's good to be descriptive. So this function will be computing the greatest common divisor, hence the name. A and B are what we call the parameters of the function. It's the name of the inputs. And a function can have several parameters like we see here, two, three, four or more, or it can have no parameters at all. It's up to you. It depends on what you need for your function to work. You need to indent all of the code that will get run when your function is called. So just like most other things, after an if statement, after a while statement, when you put that colon at the end of the definition line, usually your notebook environment will indent the next line for you. But just in case, you got to put those spaces or tabs in there to indent it. Next up is a return statement. And this is the command that specifies what your function will output. You can have several different return statements, maybe nested into some ifs and else. So return this statement or this calculation under these criteria and then return this calculation or variable under those criteria. So you can have several, but only one gets run and as soon as it's run, the rest of the code in the function never gets executed. So it's kind of like an escape hatch from the function. You can also have no functions at all. Maybe the job of your function is just to print something no matter what. You don't want to return anything else just print and be done. You could do that. Most of the functions will write in this class will have a return statement. Lastly, you can call your function from anywhere else in your code after it's been defined. You just use the name GCD and you pass it some arguments to go to the parameters. So the arguments are the actual values like 707 and 980, and they get passed to the parameters A and B. And then we get to decide what to do with the output. So we know we're going to return the value A from the function. We've chosen to put that call to the GCD function inside a call to the print function. So whatever comes out of the GCD function will be printed, but we could have just as easily taken that GCD and multiplied it by 5 or assigned its value to a variable. We get to decide once the function returns the value, what do we do with it? So again, just some vocabulary here. A and B are what we call the parameters. So those are the names of the input variables. And 707 and 980 are the arguments. So those are the values of the input. And when we call our function and provide the arguments that get passed to the parameters, basically what's happening behind the scenes is that those argument values get stored to the corresponding variable names. A and B are called positional parameters, or we provide positional arguments to them, meaning that since 707 was the first argument, it goes to the first parameter A. And since 980 was the second argument, it will get passed along to the second parameter B. Basically, A equals 707 and B equals 980 are kind of these invisible lines of code that get run whenever you call your function with those values as the arguments. You can see here on the second cell of code down below the definition that it's the same idea here. When we call GCD 18 comma 93, there's basically some invisible lines of code that make sure that A is assigned the value of 18 and B is assigned the value of 93 when you call that command. One thing about functions and their parameters is that those values A and B are only known to the code that's inside of the function. It kind of retains control over them. That's called having local scope. Those variables are not accessible to everything else in the notebook or a program that you're running, only to the code that's inside your function. So for example, if I were to run this function and say print the greatest common divisor of 707 and 980, the parameters A and B are going to take on the values of 707. They're going to take on the values of 980. They're going to do the work, return the value. But if I were to then run in the next cell, print A, you're going to get an error message because the entire notebook, the notebook at large or what we call the global scope, doesn't have access to that variable A. Let's hop over to the code visualizer and see how the computer thinks about these values for A and B at different points of the execution. So here's the exact same code that we just saw on our notebook slides. And let's go ahead and load them into the visualizer. First thing you'll notice is that it ran the definition statement and we didn't get an output, but it did create the function as an object for it to work with. You can see it labeled here function for the rest of our use in this code cell. So when you define a function, it doesn't actually run the function because the function itself needs some arguments to perform its job. So we have to give it 707 and 980. So let's go ahead and actually run that command on line nine. It's going to have to print something in order to print it. That's got to compute it. So we'll see it goes back up and it creates an instance of our function GCD with the value 707 and 980. But they're inside the box here that corresponds to the scope of this function. So values will change as we run our code, just like they would normally. And we'll step through this until we get to our return statement. And you'll see the visualizer is telling us it's about to return a value of seven. And it's that returned value of seven that the print statement will now be able to do its job with. So let's run. And we'll see there we go. The last code was now executed. Those values for A and B are now destroyed. They are no longer in the memory of the computer. So when your function is done running any values that you have been using as a part of that function's job get destroyed or wiped out of the memory. It's actually a process called garbage collection. When there's no other references to the values, they get cleared out of memory. And if I were to have another line of code here that would say look for value a, it would not be able to do it because it is not available any longer. Now here's a slight variation on the same block of code. But notice before we define our function, we actually assigned the value seven and 26 to the values A and B. But those are the names of the parameters for GCD. So what's going to happen when we run this line of code number 11 that passes value 707 and 980 to the parameters A and B? Will it overwrite these A and B? Will they create another copy? Let's run and find out. So we've run the first two lines of code and we can see we have the values seven and 26 stored the A and B respectively in the global frame, the global scope. Now we have our function defined in the global scope GCD and we'll run line 11 and let's see what happens. It creates our GCD instance and local copies of the values A and B. So everywhere inside this function, when we refer to values A and B, it's going to be referring to the local version of those variables. And that's the default behavior for all functions in Python is that it will automatically look for the local version of the variable first. And if it exists, it will use it. If no local version of that variable exists, it will go look for a global version and see if it has a possibility to avoid an error message there. But if there's no place where that variable is assigned, it will return an error. So again, if you have variables outside the function with the same name as the parameters, the function will create local versions with those values provided by the arguments. And while you can do this, have both global and local variables with the same name, it often gets confusing trying to keep track of which version the variable you meaning to use in certain situations. So just be careful. And as best you can, try not to use the same names in both places when you're writing your code. One other little wrinkle here about variable scope is that as I mentioned earlier, if you reference a variable inside of a function that does not have that variable defined inside the function, either by a parameter or by code, it will go looking for it in the global scope before it gives up. So this line of code here is attempting to reference the value stored to the parameter a by some number of capital N, but capital N is not defined anywhere else in our code except for outside the function. It'll go ahead and take a look and say, okay, there's a capital N equals three outside the function. I'll use that to help make sure that it actually returns the value that it needs to do. One note about return statements is that we mentioned earlier that your code might have multiple return statements often nestled in between some if and else is perhaps, or it could have none at all. But if you don't include a return statement, then none is output from the function and it's not the string none, it's a special data type that's reserved in Python to literally represent there is nothing here. So while you would have this function as defined above, if you were to run it, you would see three pop out, it would print out the greatest common divisor of three. It will not actually store that value to the variable X. If you were to now print X, it's going to say none. And that's because while the code inside the function specified to print the value a, it will return none, which is what the output of the function is, and that output is what is assigned to the variable X. Remember, the print command literally will just show you what's inside those parentheses to the screen, but doesn't save it or do anything else with it besides show it to you. What got returned and what got saved was none. So if you're going to use a function without a return statement, be prepared that you are in fact going to return something, but that something is nothing. This is often really helpful to keep in mind when you're debugging your code. If you see a none type showing up, the likely culprit of that bug is that you forgot to include a return statement somewhere in your function. So go ahead and check that out and see if you could figure out where the best place to include one would be. The last aspect of functions that we'll be covering in this lesson are what are called optional arguments. You can see we have our gcd function. The return statement is now in place instead of the print statement. But the one thing that's very different is in the header of the definition line of the function. Instead of having dcd and then the two parameters just a and b, we've replaced b with b equals 26. And we're going to be doing this when there's a common argument that's going to be passed to this function. We can make it the default value. So you can see the way that we've done that is just replacing the name of the parameter by itself to the name of the parameter as an assignment statement b equals 26. Now when we call this function, we only need to specify the non default argument. So in this case just a. And so the number seven here will be passed to the parameter a. And b will take the default value of 26 since we didn't specify anything differently. And the function now runs as usual. You still retain the option of providing a value for the second parameter by just providing it in place of the same position that it would have gone normally. So this value of 20 will override the default argument of 26. Here's a function that we've defined to have the name mystery. But if you look at the return statement, it's probably not not that mysterious what it does just finds the product of a b and c. But what you can see has provided three different parameters a b and c. A is just a simple positional parameter doesn't have any default value assigned to it. But b and c do have some default values assigned of 10 and five. By the way, these are also called keyword arguments because we'll see in a moment you can refer to them as you can refer to them by their names later on. So if you're going to use this function, some requirements, you must provide all the positional arguments without telling the function what the value is for a it's never going to be able to do its job. So we must provide a value for a. You can override the default values for b and c that we saw before by just providing them all in order that you wanted them use. So here, two would go to a three would go to be and six would go to see. And as long as you provide all three, that's great. And if you only wanted the overwrite value for B, you could provide just two arguments to the mystery function. The first one will go to the positional argument a. The second one is going to start going in order. So three will go to be. And if you don't specify a third argument, it will continue to use the default values for any parameters that come after B. So here, since we only provided two, those values will go to a and B, C retains the default value. But what if you wanted to provide an override for the parameter C, but make this function keep using the default value for B? It's going to get hard to specify just by listing out numbers because Python will assume no matter what that second number is, is that it should go to the second parameter. So it turns out Python has a nice way to handle that kind of request. Here, we call the mystery function by passing an argument for a. That's the value two. And because we want to leave the value of B alone, the default argument of 10, we can just specify that we want to override the default value for C by mentioning it by name. So we would do two comma C equals and then we pick the value to deviate from the default. So here we chose C equals three, and we can see that we get the output of 60 because we're multiplying to the argument for a, the default value for B of 10, and then the overridden value of C, which was three. You must use the name for the provided optional arguments. If you're not going to provide values for all of the optional arguments, especially when you're skipping over some of those default values, we saw earlier that if you're going to use all of the parameters default, regular, whatever they are, and you're going to just pass them all in, that's okay. But if you're going to skip over any of these optional arguments, then you're going to have to use the keyword argument or the name of those arguments for the ones that come later on down the line. When in doubt, you can always just specify the name when you're calling something that has an optional argument. That's usually the safe bet. There's no question about what value is getting passed to which parameter as long as you specify it in the function call. And that wraps up our initial discussion of functions. There's a lot more about functions that you can read about online and maybe we'll even cover later on in the course. But for now, you know enough to go on and start writing your own functions. In fact, if you think about some of the code that you've already in this course, perhaps writing the Caesar cipher or the rail fence cipher, those are great things that you could turn in the functions on your own. In fact, throughout the rest of the course, we'll be utilizing functions quite a bit to handle routine tasks that we're going to perform over and over again. So these skills that you learned about functions now will serve you well throughout the remainder of your time in this course.