Python Function Fundamentals

PythonPythonBeginner
Practice Now

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

Define a Python Function

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.

Function Arguments

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

Argument Type Hint

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.

Docstring

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

Returning Multiple Values

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.

Keyword Arguments

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.

Args and Kwargs

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.

Lambda Functions

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.

Local and Global Scope

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.

Using a Function as an Argument to Another Function

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.

Summary

You should now have a good understanding of how to work with Python functions. Here are some of the key points:

  1. Python functions are blocks of code that can be defined, called, and reused.
  2. You can define a function using the def keyword, followed by the function name and arguments.
  3. The function body should be indented, and you can use the return keyword to return a value from the function.
  4. You can specify function arguments using "keyword arguments", which allow you to specify the argument name and value when calling the function.
  5. You can use the *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.
  6. You can use the lambda keyword to create anonymous functions that are defined and called in a single line of code.
  7. 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.
  8. It's generally a good idea to avoid using global variables, and to pass variables to functions as arguments and return them as results instead.

Other Python Tutorials you may like