 Thank you very much. So I'm going to give a talk about, I'm going to tell you more than you ever wanted to know about Python functions. It's my experience that people learn functions early on. For many people, Python is their first language as well. And it's one of those building blocks that I think people want to move past because they're trying to get through all of the basic syllabus so they can write useful programmes. And because they learn it early, to some degree they kind of skim over it. And gradually they pick up the details later on, piece by piece, ad hoc. And they don't necessarily get the full picture of how functions work. So this is a slightly awkward talk to grade in terms of how difficult it is. Because it starts off very, very basic. It's really going to be teaching you stuff you know, hopefully, at the start. And then it gradually builds up and builds up in complexity and kind of some of the more obscure stuff you can do. Until at some point it crosses a threshold into an area where I'd really not recommend that you do the things that I'm showing you. But at least understanding how these things work might be useful or at least interesting and fun. So I'll just briefly talk about myself. I'm a DD2K online, generally. It's not a very interesting story so I won't explain it. Offline, my name is Mark Smith. I'm a developer advocate for Nexmo, one of the conference sponsors. We have a booth downstairs. You should come to talk to us. We provide web-based APIs that allow web developers to write code, send text messages, make phone calls, or send messages via different instant messaging platforms. If that sounds interesting, you should go and talk to either me at the booth or one of my awesome colleagues down there. So enough about me. Let's start with a function. This is about as simple a function as you can get. It's got no parameters. It's got no return values. It's got a single statement inside. Print something to the screen. And here we are calling it twice, and you see it does the same thing twice. It's not hugely interesting. This line is the line that defines the function. It creates a variable called say hello. This is the line that actually executes. So it's worth saying, actually, this line in most programs will only be executed once in the entire lifecycle of the program. This line, however, is executed every time you call the function, which is why when you call it once or twice, it prints out say hello. It prints out hello that many times. Functions are variables. So unlike some other programming languages, when you define a function called say hello, you can then assign it to another variable. In this case, we are assigning it to a variable called hello, and then we call it just as if we were directly calling the original function and the same thing happens. But functions aren't very interesting if they don't take any parameters generally. So here's a similar function that sends an SMS, well, in theory, and it has two parameters. It's got recipient and message. So both of those are variables that can be used within the function. In this case, we're just using them within the F string that are passed in. It can be called in many different ways, but I just want to have a little diversion first because I'm going to be quite specific about my terminology. I'm almost certainly going to get it wrong at some point as well because, like many people, I'm a bit fuzzy on the words that are used in different situations. So definitions and calls look very similar. So you'll see that line that starts with def looks very similar to the line that actually calls the function. But they are different in certain ways, certainly in terminology. So these things here, recipient and message, are parameters. So they are variables. They don't yet have any values. They're just a name that's attached to the thing that will be passed in when the function is called. So on a definition, these are parameters. On a function call, these are arguments. So arguments of values that are passed into the function. In this case, they're literals, but they could be variables where the value is essentially extracted from the variable that's passed in, and it's kind of given a different name within the function. So even with a simple definition like this, we can now call this function in a number of different ways. And again, this is different in Python to some other languages. So in this case, we can call it with positional arguments. So the first argument is bound to the recipient variable because it's a recipient parameter. And then the second argument is bound to the message parameter. We can also call them with named arguments, often called keyword arguments. It's kind of a bad name because they're not keywords. They're just names. They're just variable names. So I'm going to try and call them named arguments all the way through. So in this case, we are specifically saying the recipient is this number and the message is the string. How's it going? Because if we're providing them by name, the order of the parameters doesn't matter. So I've swapped between the two. So in this case, now we're not passing them recipient, then message. We're passing message and then recipient. But because we've given them names, that's not a problem. That can be useful in APIs where it's got a long list of parameters and maybe they don't make so much sense, especially when you're sending an SMS. Is it from and to, or is it to and from? There are different advantages in the definition to the order that those come in. But when you're calling them, sometimes one makes more sense than the other. So it's nice and clear. You can also mix them up. So we can pass a positional parameter in the case of this phone number. And then we can provide the second parameter as a keyword, as a named argument. But there can be dangers with this. So in this case, what happens when these values are bound to the parameters is that the positional parameters are bound first and then it attempts to bind the named parameters. So what we've done here, we've got a problem. We've got this error. Send many SMS got multiple values for argument recipient. The function is called send SMS, not send many SMS, but never mind. So the problem that's happened here is we've provided this positional argument and it's the first argument. So it's matched to the first parameter. And then we've provided this second argument, this named argument and we've tried to bind it to recipient. But recipient already has a value. So at runtime, this messes up, so that's not ideal. And in this case, all the arguments are required. We haven't provided any default values. I'm about to get into those. So if we try to call send SMS with no values because it has two parameters, this will fail. Excuse me. So we get into default arguments, gradually building up in complexity. In this case, I've added a parameter at the end and it has a default argument. So this is a value that will be supplied for that parameter when the function is called, if you don't provide one explicitly when you call the function. So we've called it three different ways. We've called it three different ways under here. The first one, we haven't provided value at all. So in this case, it will send to that number in the definition. In the second one, we're providing it by position. And because it's the third argument, it will be provided to the third parameter. And in the last case, we've been explicit about the parameter that we want to bind to. So that works too. One thing to know about default parameters is that, sorry, default arguments to parameters is that they must be defined last, they must be defined after any parameters that don't have a default value. So in this case, what I've done is I've moved sender to the middle. There's a parameter after it called message, which doesn't have a default argument, and this is not allowed. Again, the error messages for these are all quite clear. So once you've seen them once or twice, it's generally easy to work out what you've done wrong and fix it, hopefully. So this is now a function called send many SMS. So I've changed the signature a little bit. In this case, the default argument is now a list, and lists are mutable. So this is something that tends to trick people after a while of programming in Python. It's a complexity about default arguments that people skim over when they're teaching people because it's awkward and it's rarely encountered. But when it is encountered, it's quite weird. And I'll show you kind of why it's difficult to detect in production code. So in this case, what we're doing is we're looping through the recipients list that's passed in, if one's passed in, and then we're just printing out a message. So the first time we call it, it just prints out that it's sending this message to a single phone number, and the second time we call it, we give it a list with two items explicitly, and it calls that. So this is fine, right? This is doing what we would sort of expect it to do. The problem comes when further down the line, requirements change, and maybe we have some sort of SMS phone that every time we send an SMS, we also want to send a copy to our logging phone. So we changed the definition of the function so that we're now appending this special phone number into the 666 to the list, and then the rest of the function stays the same. It's great, right? It's an easy win. So the first time we call it, and you can see that further down the screen, we are, in fact, we're sending it to 666 and we're sending it to the other phone number. So this is all great. But it's not fine, and I suspect quite a few people in the room know why. The second time we call it, it sends to the logging phone twice, and if we called it a third time, it would send to the logging phone three times after sending to the phone number that we asked it to. So the reason for this is that we are mutating. So I mentioned at the beginning that the definition, well, firstly, here's the list, so we define this list, and then here we're appending to it, so we're mutating the list. But I mentioned earlier that this line, the deaf line, is only executed once in your entire programme. And so that list is only created once in your entire programme. But this append call in here is executed every time you call the function. So every time you call the function, it appends that value to the one list that only exists once in your programme. So let's fix it, and we'll all have seen this in code. We've changed the default argument to none, which is an immutable value. And then the first thing we do is we just say, if it's none, then set it to the value we wanted. But because we've set this recipient's variable inside our function, it's now local to that one function call. So it's not going to be appended to each time, it's going to be recreated each time. So that's great. So we execute this and it works fine. But there's still one problem. Who's spotted the problem? Okay. So let's say the thing we're passing in is actually a variable. So in this case we've got the list variable called recipients and then we call our function and then we print out the value of recipients. It should be the same, right? But it isn't because what we just... Excuse me, that's the fix. I don't know where I am now. Yes, so afterwards it's got two values in it. And that's because when we call this function, when we're providing a value, it's not calling that if block, it's not executing that if block, but it is still executing that append to the list that was passed in. So we've modified an argument that was passed in and that argument isn't actually just owned by the function, it's owned by a variable outside the function. And so we're now getting behaviour that we probably don't want. So we need to make one more fix, which is instead of appending to the value that was passed in or created, we are now creating a new variable. So what we're doing is we're assigning... We're creating recipients plus the list literal, which creates a new list, and then we're just assigning that. We're kind of tagging it with the recipients variable. To make it clearer what's happening there, that we're not reusing the recipients that was passed in anymore, we can change the name of the variable that we're assigning to, so we can call it all recipients, and then it's much clearer that we're not reusing this recipients parameter. So moving on to the next thing, we've got variadic parameters. A variadic isn't a word that I think is used in the Python community very often. I've come up with it, come across it learning other languages, but essentially it means that we can provide more than... We can provide any number of values that we want, any number of arguments. And printf is a really good example of a function that's variadic. So in this case, we're providing... We're always providing a format string at the start, and then we can provide any number of arguments after that. And so the first time we're calling this, we're passing it a single string, passing it two strings, and the args parameter just kind of mops up any extra positional arguments that pass to the function. One thing that's worth noting is although it looks like a standard parameter to your function, you can't assign it by name. So those values are provided as a tuple inside the function normally, but you can't provide them explicitly as a tuple. There are ways to do that, I'm going to get to that in a bit. And I think finally in terms of this function parameter definition, we've got the star, star, k, w, args. I don't really know what to call this, to be honest. It seems to be named different things in different places. But this does the same thing for keyword arguments as the previous syntax uses for positional arguments. So in this case, this function takes a bunch of key value pairs just as function arguments, and then it creates a dictionary from them, but it prefixes the name of each argument that you can see in the output at the bottom. It prefixes it with, in this case, the word super underscore, which could be kind of useful. Again, the dict function is a good example of a function that is kind of defined in this way. We don't need to worry about how the internals work, but the signature is kind of similar to this. So it's quite common, especially working with web APIs and things like that to get a function of a whole list of arguments. And some of them are optional, some of them aren't optional. Sometimes they sort of end up grouped together, which in which case you should use an object, but they're not necessarily set up that way. And if you call it using positional arguments, it's pretty much unreadable. So ideally you would like your users to call it using keyword arguments. This is actually kind of future proof as well. It allows you to insert new parameters into your list and because they're all being provided by name, the order of your parameters doesn't matter. And you never used to be able to enforce this, certainly in Python. You could do it in the C layer, but you couldn't do it in Python itself. But now you can use this asterisk as a sort of parameter on its own. All it does is declare that all the parameters after it in the list must be provided as keywords, as named arguments. One of the useful things about this is if you have defined a function and pylint or pycharm a complaining that you have too many parameters in your function definition, just doing this makes it go away. But it's also going to force your users to use the syntax, which could be a benefit or not. You don't have to stick it at the start of your parameter list either. You can stick it in the middle. So in this case, I can still provide send a recipient and message as positional arguments, but the rest of them can be provided as they must be provided as named arguments only. Finally, just to finish off the keywords bit, this does a very similar thing. We don't have to use the asterisk on its own. If we have very attic positional arguments, wherever we provide that in the parameter list, all the arguments after that or the parameters must be provided by named arguments. This is the same as before in that send a recipient and message can be provided by position, but we can also provide an arbitrary number of positional values after that before having to switch to the keyword, the named format for message type headers, et cetera. So one of the weaknesses I think in Python syntax is unpacking arguments looks very similar to the syntax that I just described. So the red parts at the bottom here, what we're doing is, I'll describe the whole thing, so we pretty much have the same function at the start, and then what we're doing is we're taking the parameters we would normally pass explicitly in a tuple and a dictionary. So the positional parameters are in the tuple and the named parameters are in the dictionary, and then we are passing them using this asterisk and double asterisk syntax and they will be kind of exploded into the arguments required by the function. So it's equivalent to what we've just been seeing, but it's useful to be able to build up those arguments programmatically in certain cases, especially with optional arguments where you don't know whether you're actually going to have a value for that up front. So you can build up these data structures and then call the function with the data that you end up with. But it does a completely different thing to the variadic syntaxes, but it's the same syntax. But this is used when you call the function, not when you define the function. So talking about return values very briefly, unlike Ruby and some other programming languages, functions in Python don't automatically return a value, but they, well, that's not true. They don't return a value from your code. If you don't explicitly return something, your function will return none. If you return something, the end result of the expression on the right-hand side of that will be returned as a value. So in this case, if we pass one and two in, it adds them together and then returns a value, so we get three. Now I'm going to talk about scope. When I started doing Python, there were only three scopes. It made for really clear descriptions in the documentation and in the book that I bought. So we had function, module, and built-in scope. So if you accessed a variable inside a function, first it would look to see if that variable existed in your function, and if it didn't, it would look to see if it was defined in your module, and if it didn't exist there, it would look to see if it was defined in the built-in dictionary. So let's work with that as a definition right now, because all that stuff is still true. So we're going to talk about accessing global variables. This function is fine. So it accesses the global variable useHTTPS. It kind of combines it with a value that's passed in, and then it returns the result. And that works okay. So because we're not defining any... we're not defining a value of the same name or the function that this works okay. There's no ambiguity. Whereas... and this one is fine. So we're now mutating the value that we have access to. So we have a list of domains, and we can append new values to them. That's fine. The problem comes when you want to assign. So this assignment statement, imagine that global definition at the start, the declaration at the start wasn't there. This function would work, right? It would create a local variable to that function called useHTTPS, assign it the value, and then return. It's useless, but it would work. But now we have a global value, which is going to completely ignore. It will do exactly what I just said. So the fact that it's got a global value or a module scope variable there is irrelevant. And another problem we might... so it's not going to do what you expect. If you print out the value after calling the function, it's still going to be the same value, but it's not going to get a local variable. There we go. One of the problems comes when you try to do both. So you try to access the variable, and you try to set it. So in this case I've got the print statement to make it really clear. And this is a bit weird. So the error that's raised here is actually raised on this line. And all I'm trying to do here is access a global variable. It turns out the error is actually caused by this line. This is one of the few places in Python where your error can be caused by a line after the relevant line of code. And the reason for this is when this function is compiled, it detects that there's a local use-https variable and says, okay, whenever we refer to use-https, it's a local variable. And the problem is that first line is trying to access a local variable that hasn't been given a value yet. So you get this unbound local error. The second line, if this function isn't of the second line, that also wouldn't work because we are trying to set use-https using a value on the right-hand side of use-https, and that is local, so it doesn't have a value yet. This can really be confusing when it happens to especially newer programmers. We fix that with a global declaration. Stick that at the start of your function. Follow it by the names of the global variables you want to access. And now every time you refer to that name, it will refer to the global variable, which may or may not be what you want, but that's how it works. Yes, so when I started, we had this, built-ins, modules and functions, but that was because we couldn't define in a function. So now we can define functions inside functions. So this function, it's called get later, and the idea is you pass it a URL and it hands you back a function that you can call anytime with no parameters, and at that point, it will then execute the request's call to go and get the URL. So you can build up a whole list of functions that require no parameters, and then at some point later, you can either execute them using multiprocessing or you can go through them one by one and execute. You can do whatever you want. You don't have to worry about passing these parameters around with the functions that they're calling. So this is executed when get later is called. This is executed when get later is called. And then, so that's our call to get later. And we're assigning the result to getter. So what goes into getter is actually that get function that's been created and returned. And then it's only when we call getter that actually it calls that in a function, the get function. Oops, I have no idea why I've got so many errors on there. Right, so we've used the global keyword to get from function scope to module scope. I don't really like the name global because they're kind of not global. They look like a global namespace. But now we've got the same problem. This looks very similar to the problem we just had before, right? We've got this value that's defined in counter, but it's modified inside the inner function. So that, where we're assigning to value in that inner function, it looks very similar. It's a local declaration definition inside increment and return. So we've got the same problem. And we can't use global because it's not global. So we use non-local, which just looks up a scope to get the variable that we can set. I don't recommend using this too much. I don't recommend using either of these too much. It's pretty much, it implies that you have a design floor, I think, in your code. I would use classes to wrap the values that you're trying to move around. So yeah, now we have our three scopes, but we also have functions inside functions and possibly functions inside functions inside functions. Global gets us from a function to module. It gets us from an inner function to module scope. And then non-local gets us from an inner function to the function that it's declared in. So here we're going to move on to classes. Who have I lost so far gradually building up a complexity? Excellent. Still keeping everybody with me. Hopefully not boring you. So now we have a class with, and we've defined what looks like a function on it. It's got self as the first parameter, but other than that, and it's indented against the class definition, it looks very much like a function. We can call it via, and I've taken it out of the class definition there, but imagine it's still there. So we can create our client, we can then call it. And this is really weird. So we, again, unlike other programming languages, we had to declare this self parameter explicitly, and I don't think many people like that particularly. And then when we call it via the instance, we don't pass it in. It's not there. It's over here. So it's the first thing we access the method through. And here's the secret, is that the thing that you get back from the class, so client is our class, that's a function. Even though we declared it inside the class, it's a function. But when we access that value through the instance, we get back a bound method. One other thing that's worth noting is, I feel like I've missed a slide. One other thing that's worth noting is we have a magic value inside methods, inside functions defined in classes. So if you ever want to know inside your method what class it's been defined in, so not the subclass that you're calling it through, you can use this dunder class thing. It was added with Python 3. It's how super works. So you know we used to have to do this in Python 2. You used to have to pass in your first arguments to your self, essentially. And then you used to have to pass in your instance. Sorry, the class that you're defined on. Now you don't have to. And the reason is that the compiler, when it encounters calls to super, when it can detect them, it rewrites it in the older, longer form. And because it has this value that's just there at runtime, it still works. It's a bit magic and a bit horrible, and it's one of the things Radomir was talking about in his lightning talk yesterday. So this all works through the descriptor protocol, which I'm paraphrasing here. When accessing a class attribute via an instance, if the attribute defines a dunder get method, that will be called with the instance as a parameter, and the result is returned instead. So we'll just work through this. I've got rid of the function entirely. So we've now just got a value, it's a string. So this won't work, actually, because strings aren't descriptors. So we create our client instance, and then we access that URL attribute through the instance. So if imagine string was a descriptor, imagine it implemented this dunder get method, this attribute access, the first line, would be kind of rewritten as this last line. That's what's actually executed. So that access of URL, if it was a function, it creates a new object, essentially, a method. And the method wraps, it has access to the function, contains that, and it also has access to client instance. So this is why you don't have to pass self in when you call a method. It's because the method was created through a call that had access to self, and it just kind of scrolled it away, like one of those closures that we looked at earlier. So I'm going to skip calling a method. We're running out of time right now. So inspecting functions is something that's called... Oh, yeah, callable objects. Callable on a class. Then when you create instances of that, you can call them just like functions. Works very similar to closures that we looked at earlier. It's a great way to create functions dynamically that have data associated with them. So inspecting functions is quite fun. We really are getting towards the end here, so I'm just going to have to speak very quickly. So here I've declared a function, and then I've imported inspect. I've called signature on inspect, which creates a signature object, and it has a parameters attribute, which is a dictionary of the parameters that have been defined on it. If you are using type hints, it also has the type associated with each of those parameters, so it's name and type. You can also use it for binding arguments. So if I was actually calling that function, I could call it in this way. I could call it using explicit arguments, and I can do exactly the same thing with this bind method, and it will return me a bound arguments object, and then inspect. And one of the interesting things that has been done with this, and I actually have about 10 lines of code that implements it, is that you can enforce types at runtime. So I know everybody hates this idea, but it just shows the kind of power of the things we can do. So enforce provides a decorator that wraps the function and just validates all the parameters as they're passed in, and you don't have to put anything into it. So all of them are kind of boring, except the code one, which really stood out for me. So that is the byte code for the function. And you can get hold of it, you can modify it, and then you can put it back in. And a friend of mine, Sebastian, who's sometimes at EuroPython, has written Python go to, which is a decorator for functions, and instead of wrapping the function, what it does is it digs inside the function, rewrites the byte code in a valid Python syntax, but it changes their behavior, and it does stuff in byte code that you can't do in the Python language, so it allows you to jump between code in the function definition. So in summary, functions are surprisingly complex. The slides and the code are all up at this URL. I had my Twitter handle on every single slide, so if any of you don't follow me on Twitter, I will want to know why. Thank you very much for coming.