Loading experience... Please wait.
Advanced Coding Tutorial

Advanced Coding Tutorials: Asyncio and more

Image description
                Section 1: Metaclasses - Customizing Class Creation+

Metaclasses are the 'classes of classes'. They allow you to intercept the creation of a class object itself, enabling advanced customization like automatically registering classes, validating class attributes upon definition, or modifying the class structure dynamically.

  

# Example: A simple metaclass to enforce uppercase attribute names
class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        print(f"Creating class: {name}")
        uppercase_attrs = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attrs[name.upper()] = val
            else:
                uppercase_attrs[name] = val
        
        # Call the parent type.__new__ with modified dictionary
        return super().__new__(cls, name, bases, uppercase_attrs)

class MyClass(metaclass=UpperAttrMetaclass):
    my_attribute = "value"
    another_one = 123

    def my_method(self):
        pass # Methods are usually kept as is

# Accessing attributes (note they are now uppercase)
instance = MyClass()
print(hasattr(instance, 'MY_ATTRIBUTE')) # Output: True
print(hasattr(instance, 'ANOTHER_ONE'))  # Output: True
print(hasattr(instance, 'my_attribute')) # Output: False




            Section 2: Advanced Decorators - With Arguments+

Decorators are a powerful way to modify or enhance functions or methods. Advanced decorators can accept arguments, allowing for more flexible and reusable wrappers, such as conditional execution, logging with specific tags, or enforcing specific types based on decorator parameters.

  

import functools

# Decorator factory that accepts arguments
def repeat(num_times):
    def decorator_repeat(func):
        @functools.wraps(func) # Preserves original function metadata
        def wrapper(*args, **kwargs):
            print(f"Running {func.__name__} {num_times} times:")
            results = []
            for _ in range(num_times):
                value = func(*args, **kwargs)
                results.append(value)
            return results
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    """Greets the person."""
    print(f"Hello {name}!")
    return f"Greeting {name}"

# Calling the decorated function
results = greet("Alice")
print(f"Results: {results}")
# Output:
# Running greet 3 times:
# Hello Alice!
# Hello Alice!
# Hello Alice!
# Results: ['Greeting Alice', 'Greeting Alice', 'Greeting Alice']

print(greet.__name__) # Output: greet (thanks to functools.wraps)
print(greet.__doc__)  # Output: Greets the person. (thanks to functools.wraps)




            Section 3: Asyncio - Concurrent I/O+

Asyncio provides infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources. It's ideal for I/O-bound tasks like network requests or file operations, allowing the program to perform other work while waiting for operations to complete.

  

import asyncio
import time

async def fetch_data(task_id, delay):
    """Simulates an asynchronous I/O operation."""
    print(f"Task {task_id}: Starting fetch, will take {delay}s...")
    await asyncio.sleep(delay) # Non-blocking sleep
    print(f"Task {task_id}: Finished fetching data.")
    return f"Data from task {task_id}"

async def main():
    """Runs multiple asynchronous tasks concurrently."""
    start_time = time.time()
    print("Starting main coroutine...")
    
    # Create multiple tasks
    task1 = asyncio.create_task(fetch_data(1, 2))
    task2 = asyncio.create_task(fetch_data(2, 1))
    task3 = asyncio.create_task(fetch_data(3, 3))

    # Wait for all tasks to complete and gather results
    results = await asyncio.gather(task1, task2, task3)

    print("\nAll tasks completed.")
    for result in results:
        print(f"Received: {result}")
        
    end_time = time.time()
    print(f"\nTotal execution time: {end_time - start_time:.2f} seconds") # Should be around 3s

# Run the main asynchronous function
if __name__ == "__main__":
    asyncio.run(main())




            Section 4: Generators and Coroutines+

Generators provide a memory-efficient way to create iterators using the `yield` keyword. Coroutines, often built on generators, allow functions to pause and resume execution, enabling cooperative multitasking and forming the basis for frameworks like asyncio.

  

# Example 1: Simple Generator for Fibonacci sequence
def fibonacci_generator(limit):
    a, b = 0, 1
    while a < limit:
        yield a  # Pauses execution and returns 'a'
        a, b = b, a + b # Resumes here on next iteration

print("Fibonacci sequence:")
for number in fibonacci_generator(100):
    print(number, end=" ")
print("\n")

# Example 2: Basic Coroutine using yield for receiving data
def simple_coroutine():
    print("Coroutine started")
    received_value = None
    try:
        while True:
            received_value = yield received_value # Yields result, receives next input
            print(f"Coroutine received: {received_value}")
            if received_value == "stop":
                print("Coroutine stopping.")
                break
    finally:
        print("Coroutine finished.")

co = simple_coroutine()
print("Priming coroutine...")
next(co) # Start the coroutine up to the first yield

print("Sending values to coroutine:")
co.send(10)
co.send("hello")
try:
    co.send("stop")
except StopIteration:
    print("Coroutine naturally stopped after breaking loop.")




            Section 5: Custom Context Managers+

Context managers, typically used with the `with` statement, ensure resources are properly managed (e.g., files closed, locks released). You can create custom context managers by implementing the `__enter__` and `__exit__` methods in a class, providing robust setup and teardown logic.

  

import time

class TimerContextManager:
    def __init__(self, name="Operation"):
        self.name = name
        self.start_time = None

    def __enter__(self):
        """Called when entering the 'with' block. Returns the resource."""
        print(f"Starting timer for '{self.name}'...")
        self.start_time = time.perf_counter()
        return self # Can return self or another object

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Called when exiting the 'with' block. Cleans up resources."""
        end_time = time.perf_counter()
        elapsed = end_time - self.start_time
        print(f"'{self.name}' finished in {elapsed:.4f} seconds.")
        
        # Handle exceptions if needed
        if exc_type:
            print(f"An exception occurred: {exc_type.__name__}: {exc_val}")
            # Return True to suppress the exception, False (or None) to re-raise it
            return False 
        
        # Indicate successful exit without exceptions
        return True 

# Using the custom context manager
with TimerContextManager("Long Task") as timer:
    print("Inside the 'with' block, doing work...")
    time.sleep(1.5) # Simulate work
    print(f"Context manager object received: {timer.name}")

print("\nTrying with an exception:")
try:
    with TimerContextManager("Task with Error"):
        print("Doing work that will raise an error...")
        time.sleep(0.5)
        result = 1 / 0 # This will raise ZeroDivisionError
except ZeroDivisionError:
    print("Caught the expected ZeroDivisionError outside the 'with' block.")




            Section 6: Descriptors - Customizing Attribute Access+

Descriptors are objects defining `__get__`, `__set__`, or `__delete__` methods. When assigned to a class attribute, they allow fine-grained control over how that attribute is accessed, set, or deleted on instances of the class, enabling features like type validation, lazy loading, or computed properties.

  

# Example: A descriptor for type-validated attributes
class TypedAttribute:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
        self._private_name = '_' + name # Name for storing value in instance dict

    def __get__(self, instance, owner):
        """Called when accessing the attribute."""
        if instance is None:
            return self # Access via class
        print(f"Getting '{self.name}'")
        # Retrieve value from instance's dictionary
        return getattr(instance, self._private_name, None)

    def __set__(self, instance, value):
        """Called when setting the attribute."""
        print(f"Setting '{self.name}' to {value!r}")
        if not isinstance(value, self.expected_type):
            raise TypeError(f"Expected {self.expected_type.__name__}, got {type(value).__name__}")
        # Store value in instance's dictionary
        setattr(instance, self._private_name, value)

    # __delete__ could also be implemented

class Person:
    # Use the descriptor for name (string) and age (int)
    name = TypedAttribute('name', str)
    age = TypedAttribute('age', int)

    def __init__(self, name, age):
        self.name = name # Calls TypedAttribute.__set__
        self.age = age   # Calls TypedAttribute.__set__

# Usage
p = Person("Alice", 30)
# Setting 'name' to 'Alice'
# Setting 'age' to 30

print(f"Name: {p.name}") # Calls TypedAttribute.__get__
# Getting 'name'
# Name: Alice

print(f"Age: {p.age}")   # Calls TypedAttribute.__get__
# Getting 'age'
# Age: 30

try:
    p.age = "thirty" # This will fail validation
except TypeError as e:
    print(f"Error: {e}")
# Setting 'age' to 'thirty'
# Error: Expected int, got str

```





Discover Comprehensive Tutorials & Software Downloads

Unlock a wealth of tutorials and software downloads available across our platform.

Image descriptionStart Exploring
 Welcome to your all-in-one destination in the Philippines! Settle into our welcoming accommodations, then treat your taste buds at our restaurant, featuring both elegant fine dining and authentic local cuisine, including special 8-course meals. Explore the islands with our tourist tours and car rentals, or let our shuttle service handle your transport. Find unique treasures in our handcrafted clothing line, unwind with yoga and massage, or catch some waves with our surfboard rentals. Join us at the bar, order takeaway, or book us for your next celebration or corporate event – we even offer magical beach dining setups. We also have a selection of reads and software in our bookstore and software store.