Functions
A function is a reusable block of code that performs a single task and can be used repeatedly in programs. Think of functions like your morning coffee routine β β you don't reinvent it every day, you just call it and let it do its thing.
What is a Function?
In Python, a basic function can be declared as follows:
In the above example, the following items should be noted:
- The
defkeyword declares the start of a function - The name of this function is
add_numbers - The function accepts two arguments,
aandb.- Each argument can be labelled to show the expected data type (i.e.
a:intshows that theaargument is expecting anint). - The
-> intdeclares that the function willreturnan integer (see below).
- Each argument can be labelled to show the expected data type (i.e.
- Similar to other Python constructs, the declaration line ends with a colon (
:). - In standard circumstances, a Python function will return a value
Functions Are Repeatable
Functions are useful for repeated actions. A famous principle of software development is "Don't Repeat Yourself" (aka DRY code). Copy-pasting is for amateurs. π As an example, writing the same message to multiple users could be performed as follows:
| Inefficient Repeated Code | |
|---|---|
The same lines are being repeated over and over. This could be re-written as a function:
| Declaring a Function to Avoid Repeated Code | |
|---|---|
Both examples will have the same output, but using the function will require less effort from the programmer and will be much more robust and maintainable.
Looping Over a Function
There are often scenarios where we must execute a function multiple times with different inputs.
This repetitive task can be efficiently accomplished using a
for loop.
Consider a situation where you have a function that performs a specific task or computation, and you need to apply this function to a collection of values or items. Instead of manually calling the function for each input, which can be tedious and error-prone, you can harness the for loopβs capabilities to automate this process. Looping over a function allows you to:
- Reuse Code: You can encapsulate a specific functionality within a function and then effortlessly apply it to multiple data points without duplicating code.
- Efficiency: Automating repetitive tasks enhances code efficiency, making it easier to maintain and less prone to errors.
- Scalability: As your data set grows, using loops to apply a function becomes indispensable, ensuring your code remains adaptable to various input sizes.
Letβs illustrate this concept with an example using a temperature conversion function,
celsius_to_kelvin(), which converts Celsius temperatures to Kelvin:
| Looping Over a Function | |
|---|---|
Would result in:
During the loop, celsius_to_kelvin() is executed with the values 9.1, 8.8, and -270.15,
respectively, demonstrating the power of automating repetitive tasks through function
iteration.
Default Parameter Values
Parameters can have default values, making them optional when calling the function:
| Default Parameters | |
|---|---|
Mutable Default Arguments
Never use mutable objects (lists, dicts) as default values β they're shared across calls!
Keyword Arguments
You can specify arguments by name, allowing you to skip defaults or reorder:
| Keyword Arguments | |
|---|---|
*args: Variable Positional Arguments
Sometimes you don't know how many arguments will be passed. The *args syntax collects
extra positional arguments into a tuple:
| *args | |
|---|---|
The name args is convention β you could use *values or *items. The * is what matters.
| Combining Regular and *args | |
|---|---|
**kwargs: Variable Keyword Arguments
Similarly, **kwargs collects extra keyword arguments into a dictionary:
| **kwargs | |
|---|---|
The Full Parameter Order
When combining all parameter types, they must appear in this order:
In Practice
You rarely need all five. The most common combinations are:
def f(a, b, c=None)β regular with optionaldef f(*args)β variable number of same-type itemsdef f(**kwargs)β configuration-style functionsdef f(*args, **kwargs)β wrapper functions that pass everything through
Returning Multiple Values
Python functions can return multiple values using tuple packing:
| Multiple Return Values | |
|---|---|
| Returning Named Data | |
|---|---|
Docstrings
Docstrings are special strings that document what a function does. They appear right after the function definition:
| Basic Docstring | |
|---|---|
For more complex functions, use a multi-line docstring:
Accessing Docstrings
You can access a function's docstring with function_name.__doc__ or help(function_name).
Lambda Functions
Lambda functions are anonymous, single-expression functions. They're useful for short operations, especially when passing functions as arguments:
| Lambda Syntax | |
|---|---|
Where Lambdas Shine
Lambdas are most useful with functions like sorted(), map(), filter():
Lambda Limitations
Lambdas are limited to a single expression β no statements, no assignments, no multiple lines.
If you need more complexity, use a regular def function. Readability counts! π
Variable Scope
Variables in Python have different scopes β regions where they're accessible.
Local Scope
Variables defined inside a function are local to that function:
| Local Scope | |
|---|---|
Global Scope
Variables defined at the module level are global:
| Global Scope | |
|---|---|
Modifying Global Variables
To modify a global variable inside a function, use the global keyword:
| The global Keyword | |
|---|---|
Use Sparingly
Modifying global variables can make code hard to reason about. Prefer returning values
and passing parameters. If you find yourself using global a lot, consider refactoring. π§
The nonlocal Keyword
For nested functions, nonlocal lets you modify variables from an enclosing (but not global) scope:
| nonlocal for Nested Functions | |
|---|---|
Scope Lookup Order (LEGB Rule)
Python looks up names in this order:
- Local β inside the current function
- Enclosing β in enclosing functions (for nested functions)
- Global β at the module level
- Built-in β Python's built-in names (
print,len, etc.)
| LEGB in Action | |
|---|---|
Type Hints
Python 3.5+ supports type hints (annotations) that document expected types:
| Type Hints | |
|---|---|
Type hints are optional and don't affect runtime behavior β Python won't raise an error if you pass the wrong type. They're primarily for:
- Documentation
- IDE autocompletion
- Static type checkers like
mypy
Key Takeaways
| Concept | What to Remember |
|---|---|
| Default parameters | def f(x, y=10) β defaults come after required |
| Keyword arguments | f(y=5, x=3) β specify by name |
| *args | Collect extra positional args into a tuple |
| **kwargs | Collect extra keyword args into a dict |
| Multiple returns | return a, b β returns a tuple |
| Docstrings | """Documentation""" right after def |
| Lambda | lambda x: x * 2 β anonymous single-expression function |
| Local scope | Variables inside function aren't visible outside |
| global | Declare intent to modify a global variable |
| nonlocal | Modify variable from enclosing function scope |
| Type hints | def f(x: int) -> str: β for documentation and tools |