Python tutorials > Core Python Fundamentals > Functions > How to define functions (`def`)?

How to define functions (`def`)?

In Python, functions are first-class citizens, meaning they can be passed as arguments to other functions, returned as values from other functions, and assigned to variables. Defining functions is a core skill for any Python developer. This tutorial will guide you through the process of defining functions using the def keyword, including parameters, return values, and scope.

Basic Function Definition

The simplest function definition starts with the def keyword, followed by the function name (greet in this case), parentheses (), and a colon :. The indented block of code that follows is the function body. This function takes no arguments and simply prints "Hello, world!" when called.

def greet():
    print("Hello, world!")

greet()  # Call the function

Functions with Parameters

Functions can accept input values, known as parameters. In this example, the greet function takes a single parameter called name. When calling the function, you provide an argument (e.g., "Alice"), which is then substituted into the function body. We use an f-string to create a formatted string that includes the value of the name variable.

def greet(name):
    print(f"Hello, {name}!")

greet("Alice")  # Call the function with an argument
greet("Bob")

Functions with Return Values

Functions can return values using the return keyword. In this example, the add function takes two parameters, x and y, and returns their sum. The returned value can then be assigned to a variable or used directly in another expression.

def add(x, y):
    return x + y

result = add(5, 3)
print(result)  # Output: 8

Default Parameter Values

You can specify default values for function parameters. If a caller doesn't provide a value for a parameter with a default, the default value is used. In this example, if the greet function is called without an argument, the name parameter defaults to "Guest".

def greet(name="Guest"):
    print(f"Hello, {name}!")

greet()        # Output: Hello, Guest!
greet("Eve")   # Output: Hello, Eve!

Keyword Arguments

You can call functions using keyword arguments, which specify the parameter name along with the value. This allows you to pass arguments in any order. Keyword arguments improve code readability, especially for functions with many parameters.

def describe_person(name, age, city):
    print(f"Name: {name}, Age: {age}, City: {city}")

describe_person(name="Charlie", age=30, city="New York")
describe_person(age=25, city="London", name="David")

Variable Scope

Variables defined inside a function have local scope, meaning they are only accessible within that function. Variables defined outside any function have global scope and are accessible throughout the program. If you need to modify a global variable from within a function, you must use the global keyword.

Note that attempting to access local_var outside of my_function will result in a NameError.

global_var = 10

def my_function():
    local_var = 5
    print(f"Inside function: global_var = {global_var}, local_var = {local_var}")

my_function()
print(f"Outside function: global_var = {global_var}")
# print(local_var) # This will cause an error

def another_function():
    global global_var
    global_var = 20

another_function()
print(f"Outside function after modification: global_var = {global_var}")

Real-Life Use Case: Data Validation

Functions are extremely useful for performing data validation. This example shows how a function can be defined to check whether an email address is valid based on the presence of '@' and '.' characters. This can easily be extended for more robust validation using regular expressions.

def is_valid_email(email):
    # A simple email validation function
    if "@" in email and "." in email:
        return True
    else:
        return False

email1 = "test@example.com"
email2 = "invalid-email"

print(f"{email1} is valid: {is_valid_email(email1)}")
print(f"{email2} is valid: {is_valid_email(email2)}")

Best Practices

  • Keep functions small and focused: Each function should perform a single, well-defined task.
  • Use descriptive function names: The name should clearly indicate what the function does.
  • Document your functions with docstrings: Docstrings explain what the function does, its parameters, and its return value.
  • Use type hints (optional): Type hints improve code readability and help catch type errors early.
  • Consider error handling: Implement appropriate error handling within the function.

Interview Tip

When asked about functions in an interview, be prepared to discuss the benefits of using functions (e.g., code reusability, modularity, readability), the different types of parameters (positional, keyword, default), and the concept of scope. Also, be prepared to write a simple function on the spot.

When to use functions

Use functions whenever you have a block of code that performs a specific task and you expect to reuse it in multiple places in your program or when you want to break down a complex problem into smaller, more manageable parts.

Memory Footprint

Functions themselves do not generally consume significant memory unless they contain very large data structures. The memory footprint is primarily determined by the data they process and any local variables they create. However, recursive functions without proper base cases can lead to stack overflow errors and increased memory usage.

Alternatives

Alternatives to defining standalone functions include using lambda functions (anonymous functions) for simple, one-line operations, or using methods within classes for object-oriented programming.

Pros

  • Reusability: Functions can be called multiple times, reducing code duplication.
  • Modularity: Functions break down complex tasks into smaller, manageable units.
  • Readability: Functions improve code readability by providing clear, descriptive names for blocks of code.
  • Maintainability: Changes to a function only need to be made in one place.

Cons

  • Overhead: Function calls have a slight overhead compared to executing code directly (though this is usually negligible).
  • Complexity: Overuse of functions can sometimes lead to code that is more complex to understand if not properly structured.
  • Naming Conflicts: Potential for naming conflicts between functions and variables if not careful.

FAQ

  • What is a docstring?

    A docstring (documentation string) is a multiline string literal used to document Python code. It's placed as the first statement in a function, class, method, or module. Docstrings are used to generate documentation and provide help information to users. Access a function's docstring with `help(function_name)` or `function_name.__doc__`.

  • How do I specify the return type of a function?

    Python 3.5+ allows using type hints to specify the return type. For example: def add(x: int, y: int) -> int:. While Python doesn't enforce these types at runtime (unless you use a static type checker like MyPy), they improve code readability and help catch type-related errors during development.

  • Can a function return multiple values?

    Yes, Python functions can return multiple values as a tuple. For example: def get_coordinates(): return 10, 20. You can then unpack the tuple into individual variables: x, y = get_coordinates().

  • What is a lambda function?

    A lambda function is a small anonymous function defined using the lambda keyword. It can take any number of arguments but can only have one expression. For example: add = lambda x, y: x + y.