# 7. Function Annotations

These examples demonstrate how function annotations can be used to improve code readability, documentation, and static analysis. Although Python itself doesn’t enforce type annotations at runtime, tools like `mypy` can use these annotations for type checking.

***

**1. Basic Function Annotations**

```python
def greet(name: str) -> str:
    return f"Hello, {name}!"

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

***

**2. Annotating Multiple Arguments**

```python
def add(x: int, y: int) -> int:
    return x + y

print(add(10, 20))  # Output: 30
```

***

**3. Using Annotations with Default Values**

```python
def power(base: int, exponent: int = 2) -> int:
    return base ** exponent

print(power(5))       # Output: 25
print(power(5, 3))    # Output: 125
```

***

**4. Annotations for Custom Types**

```python
from typing import List

def total(scores: List[int]) -> int:
    return sum(scores)

print(total([90, 85, 88]))  # Output: 263
```

***

**5. Annotations for Optional Parameters**

```python
from typing import Optional

def find_user(username: str, age: Optional[int] = None) -> str:
    return f"User: {username}, Age: {age if age else 'Unknown'}"

print(find_user("Alice"))           # Output: User: Alice, Age: Unknown
print(find_user("Bob", age=25))     # Output: User: Bob, Age: 25
```

***

**6. Annotations for Callable Objects**

```python
from typing import Callable

def execute(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

print(execute(lambda x, y: x + y, 10, 20))  # Output: 30
```

***

**7. Annotations with Dictionaries**

```python
from typing import Dict

def user_info(data: Dict[str, str]) -> str:
    return f"Name: {data['name']}, Age: {data['age']}"

print(user_info({"name": "Alice", "age": "25"}))  # Output: Name: Alice, Age: 25
```

***

**8. Annotations with Tuples**

```python
from typing import Tuple

def get_coordinates() -> Tuple[float, float]:
    return 40.7128, -74.0060

print(get_coordinates())  # Output: (40.7128, -74.0060)
```

***

**9. Annotations for Generator Functions**

```python
from typing import Generator

def count_up_to(max_val: int) -> Generator[int, None, None]:
    count = 1
    while count <= max_val:
        yield count
        count += 1

for num in count_up_to(5):
    print(num, end=" ")  # Output: 1 2 3 4 5
```

***

**10. Accessing Function Annotations**

```python
def multiply(a: int, b: int) -> int:
    return a * b

print(multiply.__annotations__)
# Output: {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
```

***
