Functions
## Learning Objectives
- Define and call functions
- Understand parameters and arguments
- Master return values
- Learn scope and closures
## Defining Functions
### Basic Function
```python
def greet():
print("Hello!")
greet() # Hello!
```
### Function with Parameters
```python
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob") # Hello, Bob!
```
### Function with Return Value
```python
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
```
### Function with Multiple Returns
```python
def divide(a, b):
quotient = a // b
remainder = a % b
return quotient, remainder
q, r = divide(10, 3)
print(f"Quotient: {q}, Remainder: {r}") # Quotient: 3, Remainder: 1
```
## Default Parameters
```python
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!
print(greet("Charlie", "Hey")) # Hey, Charlie!
```
### Default Parameter Gotcha
```python
# Don't use mutable default arguments!
def bad_function(items=[]): # BAD
items.append(1)
return items
# Use None instead
def good_function(items=None):
if items is None:
items = []
items.append(1)
return items
```
## Keyword Arguments
```python
def create_user(name, age, city="Unknown"):
return f"{name}, {age}, {city}"
# Positional
print(create_user("Alice", 25)) # Alice, 25, Unknown
# Keyword
print(create_user(name="Bob", age=30)) # Bob, 30, Unknown
print(create_user(age=35, name="Charlie")) # Charlie, 35, Unknown
# Mixed
print(create_user("Diana", city="Boston", age=28)) # Diana, 28, Boston
```
## *args and **kwargs
### *args (Variable Positional)
```python
def sum_all(*args):
total = 0
for num in args:
total += num
return total
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
```
### **kwargs (Variable Keyword)
```python
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="NYC")
# name: Alice
# age: 25
# city: NYC
```
### Combined
```python
def func(*args, **kwargs):
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
func(1, 2, 3, name="Alice", age=25)
# Args: (1, 2, 3)
# Kwargs: {'name': 'Alice', 'age': 25}
```
## Lambda Functions
### Basic Lambda
```python
square = lambda x: x ** 2
print(square(5)) # 25
# Equivalent to:
def square(x):
return x ** 2
```
### Lambda with Multiple Arguments
```python
add = lambda a, b: a + b
print(add(3, 5)) # 8
```
### Lambda with Sorted
```python
students = [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
# Sort by name
sorted_by_name = sorted(students, key=lambda x: x[0])
# [("Alice", 85), ("Bob", 92), ("Charlie", 78)]
# Sort by grade
sorted_by_grade = sorted(students, key=lambda x: x[1])
# [("Charlie", 78), ("Alice", 85), ("Bob", 92)]
```
### Lambda with map and filter
```python
numbers = [1, 2, 3, 4, 5]
# Map: apply function to all
squared = list(map(lambda x: x ** 2, numbers))
# [1, 4, 9, 16, 25]
# Filter: keep items where condition is True
evens = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4]
```
## Scope
### LEGB Rule
```python
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
print(x) # global
outer()
```
### global Keyword
```python
counter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2
```
### nonlocal Keyword
```python
def outer():
x = "outer"
def inner():
nonlocal x
x = "inner"
inner()
print(x) # inner
outer()
```
## First-Class Functions
Functions can be passed around like any other value:
```python
def apply_twice(func, x):
return func(func(x))
def add_five(x):
return x + 5
print(apply_twice(add_five, 10)) # 20
```
### Functions as Return Values
```python
def multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
```
## Decorators
### Basic Decorator
```python
def decorator(func):
def wrapper():
print("Before")
func()
print("After")
return wrapper
@decorator
def say_hello():
print("Hello!")
# Equivalent to: say_hello = decorator(say_hello)
```
### Decorator with Arguments
```python
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Hello, Alice! (3 times)
```
## Type Hints
### Basic Type Hints
```python
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> None:
print(f"Hello, {name}")
# No runtime effect but helps tools
```
### Complex Types
```python
from typing import List, Dict, Optional, Union
def process(numbers: List[int]) -> Dict[str, int]:
return {"count": len(numbers), "sum": sum(numbers)}
def find(user: Optional[str]) -> Union[str, None]:
return user if user else None
```
## Docstrings
```python
def add(a, b):
"""
Add two numbers.
Args:
a: First number
b: Second number
Returns:
The sum of a and b
Example:
>>> add(2, 3)
5
"""
return a + b
```
## Summary
- Define functions with `def`
- Parameters and return values
- Default parameters: `def f(x=1)`
- Keyword arguments: `f(name="Alice")`
- `*args` for positional, `**kwargs` for keyword arguments
- Lambda: `lambda x: x ** 2`
- Scope: local, enclosing, global, builtin (LEGB)
- Decorators: `@decorator` wraps a function
- Type hints for documentation
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →