Advanced Python
Table of Contents
Annotations
Annotations in Python are a way to add metadata to functions, methods, and variables. They provide additional information about the types of arguments, return values, and other aspects of the code. Annotations do not affect the runtime behavior of the code; they are primarily used for documentation and static analysis by tools like linters, type checkers, and IDEs. Here's an overview of how annotations can be used in Python:- Function Annotations: Function annotations are specified using colons (:) after the parameter list, followed by the annotation expression. The annotation expression can be any valid Python expression.
                            def greet(name: str, age: int) -> str:
                                return f"Hello, {name}! You are {age} years old."
                        
                            # Annotations are optional and not enforced by Python
                            # They are primarily used for documentation and type hinting
                        
                            def add(a: int, b: int) -> int:
                                return a + b
                        
                            # Type annotations can be simple types (int, str, float, etc.)
                            # They can also be complex types (List, Tuple, Dict, etc.) from the typing module
                        
                            # Inline variable annotation
                            x: int = 10
                            
                            # Separate variable annotation
                            y: str
                            y = "Hello"
                        
                            class MyClass:
                                def __init__(self, x: int, y: str) -> None:
                                    self.x = x
                                    self.y = y
                            
                                def method(self, z: float) -> None:
                                    pass
                        __annotations__ attribute of functions and classes.
                            print(greet.__annotations__)  # {'name': , 'age': , 'return': }
                           mypy to perform static type checking on Python code.
                            # Use mypy to perform static type checking
                            # Install mypy using pip: pip install mypy
                            # Run mypy on a Python file: mypy filename.py
                        
                            def calculate_area(radius: float) -> float:
                                """Calculate the area of a circle given its radius.
                            
                                Args:
                                    radius: The radius of the circle.
                            
                                Returns:
                                    The area of the circle.
                            
                                """
                                return 3.14 * radius ** 2
                        Annotations in Python are a powerful tool for improving code readability, facilitating type checking, and enhancing documentation. While they are not enforced by the Python interpreter, they are widely used in the Python community to improve code quality and maintainability.
Decorators
Decorators are a powerful feature in Python that allow you to modify or extend the behavior of functions or methods without changing their source code. Decorators are implemented using functions or classes, and they provide a clean and concise way to add functionality to existing code. Here's an overview of how decorators work in Python:- Basic Decorator Syntax: Decorators are defined using the @decorator_namesyntax, wheredecorator_nameis the name of the decorator function or class.
                            def decorator(func):
                                def wrapper(*args, **kwargs):
                                    print("Before calling the function")
                                    result = func(*args, **kwargs)
                                    print("After calling the function")
                                    return result
                                return wrapper
                        
                            @decorator
                            def my_function():
                                print("Inside the function")
                            
                            my_function()
                        
                            def log_time(func):
                                def wrapper(*args, **kwargs):
                                    import time
                                    start_time = time.time()
                                    result = func(*args, **kwargs)
                                    end_time = time.time()
                                    print(f"Execution time: {end_time - start_time} seconds")
                                    return result
                                return wrapper
                        @decorator_name line immediately before the function or method definition.
                            @log_time
                            def calculate_sum(n):
                                return sum(range(n+1))
                            
                            calculate_sum(10000)
                        @decorator_name lines.
                            @decorator1
                            @decorator2
                            def my_function():
                                pass
                        Example:
                            def uppercase_decorator(func):
                                def wrapper(*args, **kwargs):
                                    result = func(*args, **kwargs)
                                    return result.upper()
                                return wrapper
                        
                            def bold_decorator(func):
                                def wrapper(*args, **kwargs):
                                    result = func(*args, **kwargs)
                                    return f"{result}"
                                return wrapper
                        
                            @bold_decorator
                            @uppercase_decorator
                            def greet(name):
                                return f"Hello, {name}!"
                            
                            print(greet("John"))
                        Output:HELLO JOHN
__call__ method.
                            class DecoratorClass:
                                def __init__(self, func):
                                    self.func = func
                            
                                def __call__(self, *args, **kwargs):
                                    print("Before calling the function")
                                    result = self.func(*args, **kwargs)
                                    print("After calling the function")
                                    return result
                        Use cases for Decorators:
- Logging and profiling
- Authentication and authorization
- Caching
- Rate limiting
- Error handling
References
Some other interesting things to know:
- Visit my website on For Data, Big Data, Data-modeling, Datawarehouse, SQL, cloud-compute.
- Visit my website on Data engineering
 
            