Geavanceerde Python Technieken voor Professionals

Ontdek krachtige Python technieken die je code efficiënter en professioneler maken. Van decorators tot context managers - leer de tools die echte Python experts gebruiken.

Decorators: Functies Uitbreiden

Decorators zijn een van de meest krachtige features van Python. Ze stellen je in staat om functies te modificeren zonder de originele code te veranderen.

Basis Decorator Voorbeeld:

def timer_decorator(func):
    import time
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} duurde {end_time - start_time:.4f} seconden")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(1)
    return "Klaar!"

# Gebruik
slow_function()  # Output: slow_function duurde 1.0012 seconden

Decorator met Parameters:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hallo, {name}!")

greet("Python")  # Print 3 keer

Context Managers: Bronnen Beheren

Context managers zorgen ervoor dat bronnen correct worden geopend en gesloten, ook bij fouten.

Aangepaste Context Manager:

from contextlib import contextmanager

@contextmanager
def database_connection():
    print("Database verbinding openen...")
    connection = "fake_connection"
    try:
        yield connection
    finally:
        print("Database verbinding sluiten...")

# Gebruik
with database_connection() as conn:
    print(f"Werken met {conn}")
    # Automatisch gesloten, ook bij fouten

Class-based Context Manager:

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"Bestand {self.filename} openen")
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Bestand {self.filename} sluiten")
        if self.file:
            self.file.close()

# Gebruik
with FileManager("test.txt", "w") as f:
    f.write("Test inhoud")

Generators: Geheugen Efficiënt

Generators produceren waarden on-demand, wat geheugen bespaart bij grote datasets.

Generator Functie:

def fibonacci_generator(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

# Gebruik
for num in fibonacci_generator(10):
    print(num)  # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

Generator Expressie:

# Generator expressie voor kwadraten
squares = (x**2 for x in range(1000000))

# Gebruikt geen geheugen tot je het nodig hebt
first_few = [next(squares) for _ in range(5)]
print(first_few)  # [0, 1, 4, 9, 16]

Metaclasses: Classes van Classes

Metaclasses geven je controle over hoe classes worden gemaakt. Een geavanceerd maar krachtig concept.

class SingletonMeta(type):
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "Database verbinding"

# Gebruik
db1 = Database()
db2 = Database()
print(db1 is db2)  # True - zelfde instantie

Descriptors: Attribuut Toegang Controleren

Descriptors geven je controle over hoe attributen worden toegankelijk gemaakt.

class ValidatedAttribute:
    def __init__(self, name, validator):
        self.name = name
        self.validator = validator
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.__dict__.get(self.name)
    
    def __set__(self, obj, value):
        if not self.validator(value):
            raise ValueError(f"Ongeldige waarde voor {self.name}")
        obj.__dict__[self.name] = value

class Person:
    age = ValidatedAttribute('age', lambda x: isinstance(x, int) and x >= 0)
    
    def __init__(self, age):
        self.age = age

# Gebruik
person = Person(25)
person.age = 30  # OK
# person.age = -5  # Raises ValueError

Async/Await: Asynchrone Programmering

Async/await maakt het mogelijk om non-blocking code te schrijven voor I/O operaties.

import asyncio
import aiohttp

async def fetch_data(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
        'https://httpbin.org/delay/1',
        'https://httpbin.org/delay/2',
        'https://httpbin.org/delay/3'
    ]
    
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_data(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        
    print(f"Alle {len(results)} requests voltooid")

# Gebruik
asyncio.run(main())

Type Hints: Code Documentatie

Type hints maken je code zelf-documenterend en helpen bij het vinden van bugs.

from typing import List, Dict, Optional, Union, Callable

def process_data(
    data: List[Dict[str, Union[str, int]]], 
    filter_func: Callable[[Dict], bool]
) -> Optional[List[Dict[str, Union[str, int]]]]:
    """
    Verwerk een lijst van dictionaries met een filter functie.
    
    Args:
        data: Lijst van dictionaries met string/int waarden
        filter_func: Functie die bepaalt welke items te behouden
    
    Returns:
        Gefilterde lijst of None als leeg
    """
    filtered = [item for item in data if filter_func(item)]
    return filtered if filtered else None

# Gebruik
data = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35}
]

adults = process_data(data, lambda x: x["age"] >= 30)
print(adults)

Performance Optimalisatie

Profiling met cProfile:

import cProfile
import pstats

def slow_function():
    return sum(i**2 for i in range(10000))

def fast_function():
    return sum(i*i for i in range(10000))

# Profile code
cProfile.run('slow_function()', 'profile_stats')
stats = pstats.Stats('profile_stats')
stats.sort_stats('cumulative')
stats.print_stats(10)

Gebruik van __slots__ voor geheugen efficiëntie:

class RegularClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class OptimizedClass:
    __slots__ = ['x', 'y']
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

# OptimizedClass gebruikt minder geheugen
import sys
regular = RegularClass(1, 2)
optimized = OptimizedClass(1, 2)

print(f"Regular: {sys.getsizeof(regular.__dict__)} bytes")
print(f"Optimized: {sys.getsizeof(optimized)} bytes")

Conclusie

Deze geavanceerde technieken kunnen je Python code naar een hoger niveau tillen. Ze helpen je om:

  • Efficiëntere en elegantere code te schrijven
  • Betere foutafhandeling te implementeren
  • Code te schrijven die schaalbaar is
  • Professionele Python applicaties te bouwen

Wil je deze technieken in de praktijk leren? Bekijk onze gevorderde Python cursus voor hands-on training!