def hello():
print('Hello!')
Writing functions
Python comes with a number of built-in functions. Packages can provide additional ones. In many cases however, you will want to create your own functions to perform exactly the computations that you need.
In this section, we will see how to define new functions.
Syntax
The function definition syntax follows:
def <name>(<arguments>):
<body>
Once defined, new functions can be used as any other function.
Let’s give this a try by creating some greeting functions.
Function without argument
Let’s start with the simple case in which our function does not accept any argument:
Then we call it:
hello()
Hello!
This was great, but …
'Marie') hello(
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[88], line 1 ----> 1 hello('Marie') TypeError: hello() takes 0 positional arguments but 1 was given
… it does not accept arguments.
Function with one argument
Let’s step this up with a function which can accept an argument:
def greetings(name):
print('Hello ' + name + '!')
This time, this works:
'Marie') greetings(
Hello Marie!
However, this does not work anymore:
greetings()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[91], line 1 ----> 1 greetings() TypeError: greetings() missing 1 required positional argument: 'name'
🙁
F-strings
To be more fancy, you can use a formatted string literal or f-string instead of a simple string. F-strings allow to include the expressions that are replaced by arguments to be included inside the string and to format them.
To use them, you use f
or F
just before the string expression (without space) as in f'This is a formatted string literal'
. Then you include the expressions that will be replaced by arguments inside the string, but in curly braces as in f'This is a formatted string literal with an {expression}'
.
Example:
def greetings(name):
print(f'Hello {name}!')
'Marie') greetings(
Hello Marie!
Note the difference in syntax. Here, we aren’t using +
anymore as we aren’t concatenating a series of strings. Instead, we create a single string which includes the expression name
that will be replaced by the argument.
With f-strings, you can now add formatting to the output.
Example:
# Add quotes around the expression
def greetings(name):
print(f'Hello {name!r}!')
'Marie') greetings(
Hello 'Marie'!
You can explore more tricks that can be done with f-strings in the official Python tutorials.
Function with a facultative argument
Let’s make this even more fancy: a function with a facultative argument. That is, a function which accepts an argument, but also has a default value for when we do not provide any argument:
def howdy(name='everyone'):
print(f'Hello {name}!')
We can call it without argument (making use of the default value):
howdy()
Hello everyone!
And we can call it with an argument:
'Marie') howdy(
Hello Marie!
This was better, but …
'Marie', 'Alex') howdy(
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[97], line 1 ----> 1 howdy('Marie', 'Alex') TypeError: howdy() takes from 0 to 1 positional arguments but 2 were given
… this does not work.
Function with two arguments
We could create a function which takes two arguments:
def hey(name1, name2):
print(f'Hello {name1} and {name2}!')
Which solves our problem:
'Marie', 'Alex') hey(
Hello Marie and Alex!
But it is terribly limiting:
# This doesn't work
hey()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[100], line 2 1 # This doesn't work ----> 2 hey() TypeError: hey() missing 2 required positional arguments: 'name1' and 'name2'
# And neither does this
'Marie') hey(
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[101], line 2 1 # And neither does this ----> 2 hey('Marie') TypeError: hey() missing 1 required positional argument: 'name2'
# Nor to mention this...
'Marie', 'Alex', 'Luc') hey(
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[102], line 2 1 # Nor to mention this... ----> 2 hey('Marie', 'Alex', 'Luc') TypeError: hey() takes 2 positional arguments but 3 were given
Function with any number of args
Let’s create a function which handles all cases.
We will have to break it down into the various scenarios, but we already saw how to do this in the previous lesson with if
statements.
The scenarios are:
- no name given (we need to set some default somehow),
- one name given (no grammar syntax needs adding),
- two names given (we need to add “and”),
- more than two names (we need to add commas after all but the last name and we need to add “and” before the last name).
We saw above how to create a default value. Here, we will use a different approach that will make our life easier. We will use the argument as the list of names. That allows us to get its length (to see which scenario we are in) and to index it (to add the grammar syntax at the right place).
Finally, we need a way to make the function work with any number of arguments. To do this, we use an arbitrary argument list with *
followed by a name. When the function already accepts some arguments, by convention, people use *args
to signify that any number of additional arguments can be passed to the function. But you can use any name preceded by the asterisk.
Here, because we will only use the starred argument, let’s call it *names
.
This and that Stack Overflow questions attracted a lot of very useful answers to explain the concepts of *
and **
.
Here is our function:
def hi(*names):
# Case 1: No names were provided.
if not names:
print("Hello everyone!")
return
# Case 2: Only one name was provided.
if len(names) == 1:
# names is a tuple, so we access the first element with names[0]
print(f"Hello {names[0]}!")
return
# Case 3: Two names were provided.
if len(names) == 2:
print(f"Hello {names[0]} and {names[1]}!")
return
# Case 4: Three or more names were provided (the general case).
# We take all names except the last one for the main list.
= names[:-1]
all_but_last = names[-1]
last_person
# We join the main list with commas.
= ", ".join(all_but_last)
greeting_list
# Then we construct the final sentence.
print(f"Hello {greeting_list}, and {last_person}!")
Let’s test it:
hi()'Marie')
hi('Marie', 'Alex')
hi('Marie', 'Alex', 'Luc')
hi('Marie', 'Alex', 'Luc', 'Grace') hi(
Hello everyone!
Hello Marie!
Hello Marie and Alex!
Hello Marie, Alex, and Luc!
Hello Marie, Alex, Luc, and Grace!
Everything works! 🙂
Note the presence of the keyword return
in this function. When the return
statement is encountered during a function execution, the function terminates immediately and any code after that statement is not executed. This is why we could write this function with a series of if
statements.
Instead, we could have written our function using if elif else
statements as we saw in the previous lesson.
Your turn:
Write a version of this function that does not use return
to exit the function, but uses if elif else
statements instead.
Documenting functions
It is a good habit to document what your functions do. As with comments, those “documentation strings” or “docstrings” will help future you or other users of your code.
PEP 257—docstring conventions—suggests to use single-line docstrings surrounded by triple quotes.
Remember the function definition syntax we saw at the start of this chapter? To be more exhaustive, we should have written it this way:
def <name>(<arguments>):
"""<docstrings>"""
<body>
Example:
def hi(*names):
"""Greets a variable number of people with proper grammar."""
# Case 1: No names were provided.
if not names:
print("Hello everyone!")
return
# Case 2: Only one name was provided.
if len(names) == 1:
# names is a tuple, so we access the first element with names[0]
print(f"Hello {names[0]}!")
return
# Case 3: Two names were provided.
if len(names) == 2:
print(f"Hello {names[0]} and {names[1]}!")
return
# Case 4: Three or more names were provided (the general case).
# We take all names except the last one for the main list.
= names[:-1]
all_but_last = names[-1]
last_person
# We join the main list with commas.
= ", ".join(all_but_last)
greeting_list
# Then we construct the final sentence.
print(f"Hello {greeting_list}, and {last_person}!")
PEP 8—the style guide for Python code—suggests a maximum of 72 characters per line for docstrings.
If your docstring is longer, you should create a multi-line one. In that case, PEP 257 suggests to have a summary line at the top (right after the opening set of triple quotes), then leave a blank line, then have your long docstrings (which can occupy multiple lines), and finally have the closing set of triple quotes on a line of its own:
def <name>(<arguments>):
"""<summary docstrings line>"""
<more detailed description>
"""
<body>
Example:
def hi(*names):
"""
Greets a variable number of people with proper grammar.
This function uses *args to accept any number of string arguments.
"""
# Case 1: No names were provided.
if not names:
print("Hello everyone!")
return
# Case 2: Only one name was provided.
if len(names) == 1:
# names is a tuple, so we access the first element with names[0]
print(f"Hello {names[0]}!")
return
# Case 3: Two names were provided.
if len(names) == 2:
print(f"Hello {names[0]} and {names[1]}!")
return
# Case 4: Three or more names were provided (the general case).
# We take all names except the last one for the main list.
= names[:-1]
all_but_last = names[-1]
last_person
# We join the main list with commas.
= ", ".join(all_but_last)
greeting_list
# Then we construct the final sentence.
print(f"Hello {greeting_list}, and {last_person}!")
You can now access the documentation of your function as you would any Python function:
help(hi)
Help on function hi in module __main__:
hi(*names)
Greets a variable number of people with proper grammar.
This function uses *args to accept any number of string arguments.
Or:
print(hi.__doc__)
Greets a variable number of people with proper grammar.
This function uses *args to accept any number of string arguments.
Returning values
So far, all the functions we looked at printed something. Often, you will want your functions to calculate some result. This result needs to be “returned”. This is also done with the keyword return
that we saw above, this time followed by the value(s) to be returned.
Let’s create a dummy function:
def add_one(value):
+ 1 value
and test it:
4) add_one(
We don’t get any result. 🤔
That’s because our function is not returning anything. To fix it, we need to return the result:
def add_one(value):
return value + 1
Now it works:
4) add_one(
5
Printing vs returning
So what’s the difference between printing and returning?
Printing is called a side-effect: it modifies the state of the terminal by displaying some text on it, but it doesn’t return any value to the program (in fact it returns None
):
def test_print():
print('Printing function')
= test_print() a
Printing function
type(a)
NoneType
print(a)
None
On the contrary, returning a value makes it available to the program:
def test_return():
return 3
= test_return() a
type(a)
int
print(a)
3
Your turn:
Write a function that calculates an area. It should:
- be documented,
- accept 2 arguments:
length
andwidth
, - print an error message if
length
and/orwidth
is negative.