If we think of variables as a sort-of label for data values, then think of functions as another kind of label, but for code that is meant to be called at a later time. Just as it is convenient to give a human-readable name – a.k.a. assigning a variable – to a complicated data object for later reference, it’s convenient ttindo give a label/nickname to a series of expressions that we intend to execute again.
We've been using functions from the very start:
print("hello world")
The token print
is not a special Python keyword. It's a name assigned to a block of code that handles the work of taking an argument – "hello world"
– and printing it to screen. The actual code that does that work is much more complex than you probably thought:
Imagine typing all of those instructions just to print something to screen. But we never have to actually do that. By having all the Python instructions needed to print to screen wrapped up in the label, print
, we just have to remember that label (i.e. print
) and call it with the appropriate arguments:
print("hello world", "it's", "me")
Why do we want to define our own functions? Because we're lazy, and we're tired of having to copy and paste the same code, over and over, when we need to do something repeatedly. Defining our own functions let's us, at a bare minimum, clean up our scripts and make things much more readable.
Defining a Python function is almost as easy as using them, but more complicated than assigning a variable.
Basically, we use the def
keyword, then pick a name, and then write the code we want to save for later execution as an indented block.
The rest of this guide focuses on the bare syntax for defining functions. Check out Al Sweigart's Chapter 3: Functions, via Automate the Boring Stuff for a good walkthrough that covers functions and some of their related topics.
Here's a very basic function:
def hello_world():
print("Hello world!")
Jump into the interactive Python shell and type out the code above. Nothing should happen because nothing is being executed when we're simply defining the function.
The function's code only executes when we call the function by name:
>>> hello_world()
Hello world!
Note that the parentheses have to be included to indicate that we want to execute the function. Otherwise, the function acts just as any another Python object:
>>> hello_world
<function __main__.hello_world>
>>> type(hello_world)
function
From the Python documentation:
The keyword
def
introduces a function definition. It must be followed by the function name and the parenthesized list of formal parameters. The statements that form the body of the function start at the next line, and must be indented.
The function example from above does not have a parenthesized list of formal parameters. We'll deal with that example later.
Essentially, a function can be broken down to this bare form:
def the_function_name():
what_the_function_does
def
keywordThis is just one of those Python key words you memorize. It is short for, ``
This can basically have the same conventions as a variable name. In general, stick to lowercase letters and underscore characters.
As for what kind of code can go inside the indented code block; anything that is valid Python code. Just like indented blocks for conditional branches and for-loops.
One subtle aspect of the print
function is that it does not return a value. What does that mean? It's easier to demonstrate a function that does return a value: len
>>> x = len("hello")
The variable x
contains the value 5, because the len
function is designed to return an integer that represents the length of whatever value was passed to it.
Now try it with print()
:
>>> pval = print("hello")
hello
The function obviously executed, because hello
is printed to screen. But what is assigned to the variable pval
? Nothing
>>> type(pval)
NoneType
The simple hello_world
function didn't return anything because it simply called the print
function. To have our function actually return a value, we need to use the return keyword – yet another of one of Python's few keywords:
def hello_world_again():
return "Hello world!"
Try this out in your interactive console:
>>> txt = hello_world_again()
>>> type(txt)
str
Note that nothing is printed to screen. That's because hello_world_again
doesn't call the print
function itself. If we want to print the result of hello_world_again
, we have to call print
ourselves:
>>> print(hello_world_again())
Hello world!
Again, note that the closed parentheses have to follow the function name in order for it to be executed. That is, you probably don't want to print the function itself, like this:
>>> print(hello_world_again)
<function hello_world_again at 0x10c289bf8>
A return statement effectively ends a function; that is, when the Python interpreter executes a function's instructions and reaches the return
, it will exit the function at that point.
The function below will always just return "hello"
:
def foo_a():
return "hello"
return "world"
The function examples so far have been very dull. The use of arguments allows us to call a function for different situations, making functions vastly more useful.
Here's a simple example:
def hello_there(someone):
print("Hello, %s!" % str(someone).upper())
>>> hello_there('dan')
Hello, DAN!
>>> hello_there(42 * 42)
Hello, 1764!
In the function definition, the arguments are in the parentheses that follow the function name. Think of them as variable names to refer to in the function body. At the time that we define the function, we don't know exactly what values the user will pass to it.
Functions can have more than one argument; use a comma to separate each argument:
def sup(first_name, last_name):
print("Sup, %s %s?" % (last_name, first_name))
>>> sup('dan', 'nguyen')
Sup, nguyen dan?
Arguments can be defined as optional by assigning a default value to them in the argument list:
def sup_2(first_name, last_name='Doe'):
print("Sup, %s %s?" % (last_name, first_name))
>>> sup_2('John', "Johnson")
Sup, John Johnson?
>>> sup_2('John')
Sup, John Doe?
When should you define a function? Whenever you want. But typically, we define functions for things that we do, over and over.
For example, we have repeatedly been fetching files from the web and, in the case of JSON files, deserializing them into Python objects:
import requests
import json
resp = requests.get("http://stash.compciv.org/congress-twitter/2016-01/sentedcruz.json")
mydata = json.loads(resp.text)
If we had to do that for many JSON files (that require downloading from the web), we'd have to copy-paste that snippet of code, over and over. It's repetitive. And it's prone to errors.
So let's make it into a function. The main strategy in designing a function is knowing:
Well, given a URL for a JSON file, we need to download it and turn it into a data object. We just did that above, so let's just copy-paste that code and throw it into a function body named fetch_json
:
def fetch_json():
import requests
import json
resp = requests.get("http://stash.compciv.org/congress-twitter/2016-01/sentedcruz.json")
mydata = json.loads(resp.text)
If you copy-paste that code above and then call the function:
>>> fetch_json()
It will work. But it doesn't actually return anything, so it will, for all practical purposes, not be useful.
So let's just have it return the deserialized object:
def fetch_json():
import requests
import json
resp = requests.get("http://stash.compciv.org/congress-twitter/2016-01/sentedcruz.json")
mydata = json.loads(resp.text)
return mydata
Note that mydata
, and all the other variable names, do not have meaning outside the scope of the function definition. In other words, you don't have to worry about resp
and mydata
clashing with other places that you've used those same variable names.
>>> thing = fetch_json()
>>> type(thing)
dict
>>> thing['name']
'Senator Ted Cruz'
This is where we think about arguments. The fetch_json
function works…but only if we want to keep fetching the stashed version of Ted Cruz's Twitter profile.
So, what is the part of the function that is repeatable. And what is the part of the function that changes?
In this case, the url is the thing that we'd like to vary each time. So let's make that an argument:
def fetch_json(url):
import requests
import json
resp = requests.get(url)
mydata = json.loads(resp.text)
return mydata
Now, we can point it to any URL we want that purportedly contains JSON formatted text. Try calling fetch_json
on these URLs out for yourself:
Check out Al Sweigart's Chapter 3: Functions, via Automate the Boring Stuff for a good walkthrough that covers functions and some of their related topics.