Introduction
In this lab, you will learn how to define and use Python functions. We will start with simple examples and gradually increase the complexity.
Let's get started!
Achievements
- Python Functions
In this lab, you will learn how to define and use Python functions. We will start with simple examples and gradually increase the complexity.
Let's get started!
To define a Python function, you need to use the def
keyword followed by the function name and a set of parentheses that may contain arguments. The function body is indented and follows the :
character.
Open up a new Python interpreter.
python3
Here's an example of a simple function that takes a single argument x
and returns the square of x
:
def square(x):
return x ** 2
In this example, the square function takes a single argument x
and returns the square of x
. The value is returned to the caller using the return
statement.
To call a function, simply use the function name followed by a set of parentheses and any necessary arguments. For example:
result = square(5) ## result is 25
print(result)
If a function doesn't have a return
statement, it will return None
by default. For example:
def square(x):
print(x ** 2)
result = square(5) ## result is None
print(result)
You must know the difference between return
and print
statement. print
statement is used to print the value on the screen, but return
statement is used to return the value from the function.
It's also possible to use the return statement to end the execution of a function early. For example:
def find_first_positive(numbers):
for number in numbers:
if number > 0:
return number
return None
result = find_first_positive([-1, -2, 3, -4, 5]) ## result is 3
print(result)
In this example, the find_first_positive
function returns the first positive number in the numbers
list, or None
if no positive numbers are found. The return
statement ends the execution of the function as soon as a positive number is found.
Sometimes, it's useful to specify default values for function arguments. This way, the function can be called without passing a value for that argument, and the default value will be used instead.
Here's an example of a function that calculates the area of a rectangle. It takes two arguments: the length and the width of the rectangle. We can specify default values for both arguments, so that the function can be called with only one argument if the length and width are equal:
def rectangle_area(length, width=1):
return length * width
print(rectangle_area(5)) ## Output: 5
print(rectangle_area(5, 2)) ## Output: 10
In this example, the width
argument has a default value of 1. If the function is called with only one argument, the default value of 1 is used for the width
.
Try it yourself: define a function called power
that takes two arguments: a base x
and an exponent n
. Make the exponent argument optional, with a default value of 2. Then call the function with different values for the base and the exponent.
def power(x, n=2):
return x ** n
print(power(2)) ## Output: 4
print(power(2, 3)) ## Output: 8
In Python, function argument type hints indicate the expected type of an argument passed to a function. They were introduced in Python 3.5 and are denoted by a colon followed by the type, placed after the argument name in the function definition.
Type hints are optional and do not affect the runtime behavior of the code. They are mainly used by tools such as IDEs and Linters to provide better code analysis and auto-completion suggestions.
For example, consider the following function that takes two arguments, an integer, and a string, and returns their concatenation:
def concatenate(a: int, b: str) -> str:
return str(a) + b
print(concatenate(1, "world")) ## "1world"
In this example, the type hints int
and str
indicate that the first argument a
should be an integer and the second argument b
should be a string.
Type hints can also be used with classes and their instances, for example:
class MyClass:
pass
def my_function(a: MyClass) -> None:
pass
It is worth mentioning that, Type hints are optional and do not affect the runtime behavior of the code. They are mainly used by tools such as IDEs and Linters to provide better code analysis and auto-completion suggestions.
Also, Python has a module named typing
which contains useful types like List
, Tuple
, Dict
, Set
, etc. which can be used to give hints about the types of elements in a collection.
from typing import List, Tuple
def my_function(a: List[int], b: Tuple[str, int]) -> None:
pass
It is a good practice to use type hints in your code to make it more readable and maintainable.
A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the __doc__
special attribute of that object.
Docstrings are used to document the code and are written in plain text. They are enclosed in triple quotes """
and are usually placed at the beginning of the function definition.
def my_function():
"""This is a docstring."""
pass
Docstrings usually contain a short description of the function, its arguments, and its return value. They can also contain a longer description of the function's behavior.
def my_function(a: int, b: int) -> int:
"""Return the sum of a and b.
Args:
a (int): The first number.
b (int): The second number.
Returns:
int: The sum of a and b.
"""
return a + b
In Python, a function can return multiple values using a tuple. Here's an example of a function that calculates the minimum and maximum values of a list:
def min_max(numbers):
return min(numbers), max(numbers)
nums = [1, 2, 3, 4, 5]
min_val, max_val = min_max(nums)
print("Minimum value:", min_val) ## Output: "Minimum value: 1"
print("Maximum value:", max_val) ## Output: "Maximum value: 5"
In this example, the min_max
function returns a tuple containing the minimum and maximum values of the numbers
list. The tuple is unpacked into the variables min_val
and max_val
when the function is called.
Here's another example of returning multiple values from a function:
def get_student_info(name):
if name == "John":
return "John", "Doe", "Computer Science"
elif name == "Jane":
return "Jane", "Smith", "Physics"
else:
return "Unknown", "Unknown", "Unknown"
first_name, last_name, major = get_student_info("John")
print("First name:", first_name) ## Output: "First name: John"
print("Last name:", last_name) ## Output: "Last name: Doe"
print("Major:", major) ## Output: "Major: Computer Science"
first_name, last_name, major = get_student_info("Jane")
print("First name:", first_name) ## Output: "First name: Jane"
print("Last name:", last_name) ## Output: "Last name: Smith"
print("Major:", major) ## Output: "Major: Physics"
first_name, last_name, major = get_student_info("Bob")
print("First name:", first_name) ## Output: "First name: Unknown"
print("Last name:", last_name) ## Output: "Last name: Unknown"
print("Major:", major) ## Output: "Major: Unknown"
In this example, the get_student_info
function takes a student name and returns a tuple containing the student's first name, last name, and major. The tuple is unpacked into separate variables when the function is called.
If the student name is not recognized, the function returns a tuple with "Unknown" values.
In Python, you can specify function arguments using "keyword arguments". When you use keyword arguments, you specify the argument name followed by the value, and the arguments can be provided in any order.
Here's an example of a function that takes two arguments: a name and a message:
def greet(name, message):
print("Hello, " + name + "! " + message)
greet(message="How are you?", name="John") ## Output: "Hello, John! How are you?"
In this example, the greet
function is called with the arguments "John" and "How are you?", but the arguments are provided in a different order than they are defined in the function. By using keyword arguments, you can specify the arguments in any order and make the code more readable.
You can also mix keyword arguments with positional arguments, as long as the positional arguments are provided first. For example:
def greet(name, message):
print("Hello, " + name + "! " + message)
greet("John", message="How are you?") ## Output: "Hello, John! How are you?"
In this example, the name
argument is provided as a positional argument, while the message
argument is provided as a keyword argument.
Keyword arguments are especially useful when a function has a large number of arguments, or when the arguments have default values. For example:
def create_user(name, age=18, gender="unknown"):
print("Creating user:", name, age, gender)
create_user("John") ## Output: "Creating user: John 18 unknown"
create_user("Jane", gender="female") ## Output: "Creating user: Jane 18 female"
create_user("Bob", 25, "male") ## Output: "Creating user: Bob 25 male"
In this example, the create_user function takes three arguments: name
, age
, and gender
. The age
and gender
arguments have default values, so they are optional. By using keyword arguments, you can specify only the arguments you want to provide, and the default values will be used for the rest.
In Python, you can use the *args
and **kwargs
syntax to define a function that can take a variable number of arguments.
The *args
syntax is used to pass a variable number of non-keyworded arguments to a function. For example:
def print_numbers(*args):
for arg in args:
print(arg)
print_numbers(1, 2, 3, 4, 5) ## Output: 1, 2, 3, 4, 5
print_numbers(10, 20, 30) ## Output: 10, 20, 30
In this example, the print_numbers
function takes a variable number of arguments and prints them to the console. The *args
syntax is used to define the function, and the arguments are passed as a tuple to the function.
The **kwargs
syntax is used to pass a variable number of keyworded arguments to a function. For example:
def print_keywords(**kwargs):
for key, value in kwargs.items():
print(key, ":", value)
print_keywords(name="John", age=30, city="New York")
## Output:
## name : John
## age : 30
## city : New York
print_keywords(country="USA", population=327000000)
## Output:
## country : USA
## population : 327000000
In this example, the print_keywords
function takes a variable number of keyworded arguments and prints them to the console. The **kwargs
syntax is used to define the function, and the arguments are passed as a dictionary to the function.
You can mix the *args
and **kwargs
syntax with other arguments in a function definition, as long as the *args
and **kwargs
arguments are provided last. For example:
def print_info(title, *args, **kwargs):
print(title)
for arg in args:
print(arg)
for key, value in kwargs.items():
print(key, ":", value)
print_info("Person", "John", "Jane", "Bob", age=30, city="New York")
## Output:
## Person
## John
## Jane
## Bob
## age : 30
## city : New York
In this example, the print_info
function takes a fixed argument title, followed by a variable number of non-keyworded arguments (*args
) and a variable number of keyworded arguments (**kwargs
). The arguments are passed to the function in the order they are defined: first title, then *args
, and finally **kwargs
.
The *args
and **kwargs
syntax can be useful when you want to define a flexible function that can accept a variable number of arguments. They can also make the code more readable by allowing you to avoid using hard-coded argument names in the function definition.
Here's an example of a function that uses *args
and **kwargs
to create a dictionary of arguments:
def create_dict(**kwargs):
return kwargs
my_dict = create_dict(name="John", age=30, city="New York")
print(my_dict) ## Output: {'name': 'John', 'age': 30, 'city': 'New York'}
my_dict = create_dict(a=1, b=2, c=3)
print(my_dict) ## Output: {'a': 1, 'b': 2, 'c': 3}
In this example, the create_dict
function takes a variable number of keyworded arguments and returns them as a dictionary. The **kwargs
syntax is used to define the function, and the arguments are passed as a dictionary to the function.
You can also use the *
operator to "unpack" a list or tuple into separate arguments when calling a function. For example:
def print_numbers(*args):
for arg in args:
print(arg)
numbers = [1, 2, 3, 4, 5]
print_numbers(*numbers) ## Output: 1, 2, 3, 4, 5
tuple_of_numbers = (10, 20, 30)
print_numbers(*tuple_of_numbers) ## Output: 10, 20, 30
In this example, the *
operator is used to unpack the numbers list and the tuple_of_numbers
tuple into separate arguments when calling the print_numbers
function.
You can also use the **
operator to "unpack" a dictionary into keyworded arguments when calling a function. For example:
def print_keywords(**kwargs):
for key, value in kwargs.items():
print(key, ":", value)
my_dict = {'name': 'John', 'age': 30, 'city': 'New York'}
print_keywords(**my_dict)
## Output:
## name : John
## age : 30
## city : New York
another_dict = {'country': 'USA', 'population': 327000000}
print_keywords(**another_dict)
## Output:
## country : USA
## population : 327000000
In this example, the **
operator is used to unpack the my_dict
and another_dict
dictionaries into keyworded arguments when calling the print_keywords
function.
In Python, you can use "lambda functions" to create anonymous functions. Lambda functions are small functions that don't have a name, and are usually defined and called in a single line of code.
Here's an example of a lambda function that takes two arguments and returns their sum:
sum = lambda x, y: x + y
result = sum(1, 2) ## result is 3
In this example, the lambda keyword is used to define a lambda function that takes two arguments x
and y
and returns their sum
. The lambda function is assigned to the sum
variable, and can be called like any other function.
Can you use a normal function instead of a lambda function to define the same function?
def sum(x, y):
return x + y
result = sum(1, 2) ## result is 3
Lambda functions are often used as a shortcut for defining simple functions. They are especially useful when you want to pass a function as an argument to another function, or when you want to define a function inline.
Here's an example of using a lambda function as an argument to the sorted function:
words = ["apple", "banana", "cherry", "date"]
sorted_words = sorted(words, key=lambda x: len(x))
print(sorted_words) ## Output: ['apple', 'date', 'banana', 'cherry']
In this example, the sorted
function takes a list of words and a "key" function that specifies how the words should be sorted. The lambda function passed as the key function returns the length of each word, and the words are sorted based on their length.
Here's an example of defining a lambda function inline:
result = (lambda x: x ** 2)(5) ## result is 25
In this example, the lambda function takes a single argument x
and returns the square of x
. The lambda function is defined and called in a single line of code, and the result
is assigned to the result variable.
Lambda functions are often used in Python for short, simple functions that don't need to be reused elsewhere in the code. They are a convenient way to define small functions on the fly, without the need to define a separate function using the def keyword.
In Python, variables defined inside a function are "local" to the function, and are only available inside the function. Variables defined outside a function are "global" and are available throughout the entire program.
Here's an example of a global variable:
message = "Hello, world!"
def greet():
print(message)
greet() ## Output: "Hello, world!"
In this example, the message
variable is defined outside the greet function, so it is a global
variable. The greet
function can access and print the message variable, because it is available throughout the entire program.
Here's an example of a local variable:
def greet(name):
message = "Hello, " + name + "!"
print(message)
greet("John") ## Output: "Hello, John!"
print(message) ## Output: NameError: name 'message' is not defined
In this example, the message
variable is defined inside the greet
function, so it is a local variable. The greet
function can access and print the message
variable, but it is not available outside the function. If you try to access the message
variable outside the function, you will get a NameError
because the variable is not defined.
You can also use the global
keyword to modify a global variable inside a function. For example:
message = "Hello, world!"
def greet():
global message
message = "Hello, Python!"
print(message)
greet() ## Output: "Hello, Python!"
print(message) ## Output: "Hello, Python!"
In this example, the greet function uses the global
keyword to modify the message
global variable. The greet function prints the modified value of message
, and the value of message
is also modified outside the function.
It's generally a good idea to avoid using global
variables, because they can make the code harder to understand and maintain. It's usually better to pass variables to functions as arguments and return them as results, rather than modifying global
variables.
In Python, you can pass a function as an argument to another function. This is called a "higher-order function". Here's an example of a function that takes another function as an argument and calls it multiple times:
def greet(name):
print("Hello, " + name + "!")
def call(func, args):
for arg in args:
func(arg)
names = ["John", "Jane", "Bob"]
call(greet, names) ## Output: "Hello, John!", "Hello, Jane!", "Hello, Bob!"
In this example, the call
function takes two arguments: a function func
and a list of arguments args
. It calls the func
function with each element of the args
list as an argument.
Try it yourself: define a function called apply
that takes a function and a list of numbers, and returns a new list containing the result of applying the function to each element of the list.
def square(x):
return x ** 2
def apply(func, numbers):
result = []
for number in numbers:
result.append(func(number))
return result
numbers = [1, 2, 3, 4, 5]
squared_numbers = apply(square, numbers)
print(squared_numbers) ## Output: [1, 4, 9, 16, 25]
This step may be a bit difficult, don't worry if you can't solve it. You can skip it and come back to it later.
You should now have a good understanding of how to work with Python functions. Here are some of the key points:
return
keyword to return a value from the function.*args
syntax to define a function that can take a variable number of non-keyworded arguments, and the **kwargs
syntax to define a function that can take a variable number of keyworded arguments.