Defining and Calling Functions

In Python, a function is defined using the def keyword, followed by the function name and a set of parentheses. Inside the parentheses, you can define parameters that the function accepts. The function body is indented and contains the logic that executes when the function is called.

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

To call the function:

print(greet("Alice"))  # Output: Hello, Alice!

Default Arguments and Optional Parameters

Python allows you to define default values for function parameters, making them optional. This is useful when you want to provide a sensible fallback if a parameter is not supplied.

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

Now, the function can be called in multiple ways:

print(greet("Alice"))         # Output: Hello, Alice!
print(greet("Bob", "Hi"))     # Output: Hi, Bob!
Best Practice: Always place parameters with default values after those without. This prevents confusion and syntax errors.

Keyword-Only Arguments

In Python 3, you can enforce that certain parameters must be passed by keyword. This improves code clarity and prevents accidental misuse.

def connect(host, *, port, timeout=10):
    return f"Connecting to {host}:{port} with timeout {timeout}"

In this case, port is a keyword-only argument. You must call the function like this:

connect("example.com", port=8080)  # Valid
connect("example.com", 8080)       # Invalid, raises TypeError

Function Annotations

Function annotations provide metadata about the expected types of parameters and return values. They are not enforced at runtime but can improve code documentation and support tools like type checkers.

def add(a: int, b: int) -> int:
    return a + b

Annotations do not affect the function's behavior but are useful for IDEs and static analysis tools.

FeatureDescription
def keywordDefines a function
Default argsOptional parameters with default values
* parameterEnforces keyword-only arguments
AnnotationsType hints for better code clarity

Variable-Length Arguments

Python allows functions to accept variable numbers of arguments using args (for positional arguments) and *kwargs (for keyword arguments).

def log_message(*args, **kwargs):
    message = ' '.join(str(arg) for arg in args)
    level = kwargs.get('level', 'info')
    return f"[{level.upper()}] {message}"

Example usage:

log_message("User logged in", user="Alice", level="debug")
# Output: [DEBUG] User logged in
Tip: Use args and *kwargs sparingly. Overuse can make functions harder to understand and test.

Returning Multiple Values

Python functions can return multiple values as a tuple, which is particularly useful for decomposing results.

def divmod_custom(a, b):
    quotient = a // b
    remainder = a % b
    return quotient, remainder

Usage:

q, r = divmod_custom(10, 3)
print(q, r)  # Output: 3 1

Function Best Practices

  • Keep functions focused: One function should perform one task.
  • Use descriptive names: Avoid func1, do_this, or similar vague names.
  • Avoid side effects: A function should ideally return a value without modifying external state.
  • Document with docstrings: Provide a clear explanation of the function’s purpose, parameters, and return value.

Example docstring:

def factorial(n: int) -> int:
    """
    Calculate the factorial of a non-negative integer.

    Args:
        n (int): The number to compute the factorial of.

    Returns:
        int: The factorial of n.

    Raises:
        ValueError: If n is negative.
    """
    if n < 0:
        raise ValueError("Factorial is not defined for negative numbers.")
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

Learn more with useful resources