Today we'll cover more advanced function topics such as Scope, Defaults, and Typed Functions
<div style="position: relative; padding-bottom: 64.90384615384616%; height: 0;"><iframe src="https://www.loom.com/embed/6a6f9e7393d84a21bd8db93173421ebb?sid=60fdb5fa-198c-476d-8e8c-312d69b36b0d" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></iframe></div>
Welcome to this lesson on advanced Python functions. You've already learned the basics of functions—how to define them and call them. Now, we'll dive deeper into some of the more complex aspects that can make your functions more powerful and efficient.
#### 1. Understanding Scope: Local vs Global Variables
**Scope** refers to the region of the program where a variable is accessible. Python has two basic scopes of variables: local and global.
- **Local Variables:** A variable declared inside a function is local to that function. It is accessible only within that function and not outside of it.
```python
def function_example():
local_var = 5 # Local variable, accessible only here
print(local_var)
function_example()
# print(local_var) # This would raise an error because local_var is not accessible here.
```
- **Global Variables:** A variable declared outside any function is a global variable and is accessible from any part of the code, including inside functions.
```python
global_var = 10 # Global variable, accessible anywhere in this script
def function_example():
print(global_var) # Accessible here
function_example()
print(global_var) # And here
```
#### 2. Typed Functions
Python 3.5 introduced support for optional type hints. These hints help programmers understand what type of value should be passed to or returned from functions.
```python
def add_numbers(a: int, b: int) -> int:
return a + b
result = add_numbers(5, 3)
print(result) # Output will be 8
```
Type hints do not affect the runtime behavior of programs but are useful for type checking in larger codebases.
#### 3. Default Values for Function Parameters
You can specify default values for parameters in Python functions. This feature is helpful when you expect a function to be called with the same value frequently.
```python
def greet(name, message="Hello"):
print(f"{message}, {name}!")
greet("Alice") # Uses default message "Hello"
greet("Bob", "Good morning") # Overrides the default message
```
#### 4. Higher-Order Functions
A higher-order function is a function that takes another function as an argument, or that returns a function as its result.
- **Function as an Argument:**
```python
def greet(name):
return f"Hello {name}"
def shout(text_function, name):
result = text_function(name)
return result.upper()
print(shout(greet, "Alice")) # Outputs: HELLO ALICE
```
- **Returning a Function:**
```python
def outer_function(text):
text = text
def inner_function():
print(text)
return inner_function
my_function = outer_function("Hello")
my_function() # This will output: Hello
```
#### 5. Using *args and **kwargs in Python Functions
In Python, you can use `*args` and `**kwargs` to handle variable numbers of arguments in functions. These are particularly useful when you are not sure how many arguments might be passed to your function.
- **`*args`** is used to pass a variable number of non-keyword arguments to a function. It is typically used to capture all the arguments provided into a tuple.
```python
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3, 4)) # Outputs: 10
print(sum_all(1, 2)) # Outputs: 3
```
- **`**kwargs`** is used to pass variable number of keyword arguments to a function. This allows you to handle named arguments that you have not defined in advance. The arguments are captured into a dictionary.
```python
def print_user_data(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_user_data(name="Alice", age=30, city="New York")
```
#### 6. Practical Applications of `*args` and `**kwargs`
These features are incredibly useful in several scenarios:
- When you want to create wrapper functions that pass through arguments to another function.
- When you are working with functions that may evolve over time, where new parameters might be added in the future.
- For subclassing and extending methods in object-oriented programming without needing to replicate the full method signatures of the base class.
```python
# Wrapper function example
def wrapper(*args, **kwargs):
print("Arguments were:", args, kwargs)
result = sum_all(*args) # passing all received args to sum_all
return result
print(wrapper(1, 2, 3, 4, 5)) # Outputs: 15
```
#### Cheat Sheet
- **Scope:** Understand where your variables can be accessed.
- **Type Hints:** Use `variable: type` in function definitions to indicate expected types.
- **Default Values:** Use `parameter=default_value` in function definitions for common defaults.
- **Higher-Order Functions:** Functions that take other functions as arguments or return functions.
#### Exercise
Create a higher-order function `operate` that takes two arguments: a function `operation` and a list of numbers `nums`. The function `operation` should be capable of performing a mathematical operation (like sum, min, max). The `operate` function should apply the `operation` to the list `nums` and return the result.
```python
def operate(operation, nums):
return operation(nums)
# Example usage
result = operate(sum, [1, 2, 3, 4, 5])
print(result) # Outputs: 15
```
#### Additional Resources
1. [Real Python on Function Arguments](https://realpython.com/defining-your-own-python-function/)
2. [Python Documentation on Function Annotations](https://docs.python.org/3/tutorial/controlflow.html#function-annotations)
3. [Higher-Order Functions in Python](https://www.geeksforgeeks.org/higher-order-functions-in-python/)