Inleiding tot Geavanceerde Python

Als je de basis van Python onder de knie hebt, is het tijd om je kennis naar het volgende niveau te tillen. In dit artikel verkennen we drie krachtige Python concepten die je code eleganter, efficiënter en professioneler maken: decorators, generators en context managers.

Deze geavanceerde technieken worden veel gebruikt in professionele Python codebases en het beheersen ervan zal je onderscheiden als een ervaren Python developer.

Decorators: Code Verfraaien en Uitbreiden

Decorators zijn een van Python's meest elegante features. Ze stellen je in staat om het gedrag van functies of klassen te wijzigen zonder de oorspronkelijke code aan te passen.

Basis Decorator Concept

Een decorator is eigenlijk een functie die een andere functie als argument neemt en een nieuwe functie teruggeeft:

def mijn_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Functie {func.__name__} wordt uitgevoerd...")
        resultaat = func(*args, **kwargs)
        print(f"Functie {func.__name__} is voltooid!")
        return resultaat
    return wrapper

# Decorator gebruiken
@mijn_decorator
def zeg_hallo(naam):
    return f"Hallo, {naam}!"

# Test
print(zeg_hallo("Marie"))
# Output:
# Functie zeg_hallo wordt uitgevoerd...
# Hallo, Marie!
# Functie zeg_hallo is voltooid!

Praktische Decorator Voorbeelden

Timing Decorator

import time
import functools

def meet_tijd(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_tijd = time.time()
        resultaat = func(*args, **kwargs)
        eind_tijd = time.time()
        print(f"{func.__name__} duurde {eind_tijd - start_tijd:.4f} seconden")
        return resultaat
    return wrapper

@meet_tijd
def langzame_berekening():
    total = 0
    for i in range(1000000):
        total += i * i
    return total

# Test
result = langzame_berekening()
# Output: langzame_berekening duurde 0.1234 seconden

Caching Decorator

def cache(func):
    cache_data = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args in cache_data:
            print(f"Cache hit voor {args}")
            return cache_data[args]
        
        print(f"Cache miss voor {args}, berekening uitvoeren...")
        resultaat = func(*args)
        cache_data[args] = resultaat
        return resultaat
    
    return wrapper

@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Test
print(fibonacci(10))  # Eerste keer: veel berekeningen
print(fibonacci(8))   # Tweede keer: meeste waarden uit cache

Authentication Decorator

def vereist_login(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Simuleer user check
        gebruiker_ingelogd = True  # In werkelijkheid: check session/token
        
        if not gebruiker_ingelogd:
            return {"error": "Login vereist"}
        
        return func(*args, **kwargs)
    return wrapper

@vereist_login
def gevoelige_data():
    return {"data": "Geheime gebruikersinformatie"}

# Test
print(gevoelige_data())

Generators: Geheugen-Efficiënte Iteratie

Generators zijn een krachtige manier om iterables te maken die waarden on-demand produceren, wat geheugen bespaart bij grote datasets.

Basis Generator Syntaxis

def tel_tot(maximum):
    """Generator die telt van 1 tot maximum"""
    nummer = 1
    while nummer <= maximum:
        yield nummer
        nummer += 1

# Generator gebruiken
teller = tel_tot(5)
print(type(teller))  # 

# Door generator itereren
for getal in teller:
    print(getal)
# Output: 1, 2, 3, 4, 5

Generator Expressions

# List comprehension (gehele lijst in geheugen)
kwadraten_lijst = [x**2 for x in range(1000000)]

# Generator expression (waarden on-demand)
kwadraten_gen = (x**2 for x in range(1000000))

print(f"Lijst grootte: {len(kwadraten_lijst)} items")
print(f"Generator object: {kwadraten_gen}")

# Memory usage verschil is enorm!
import sys
print(f"Lijst geheugen: {sys.getsizeof(kwadraten_lijst)} bytes")
print(f"Generator geheugen: {sys.getsizeof(kwadraten_gen)} bytes")

Praktische Generator Voorbeelden

Bestand Lezen Generator

def lees_grote_bestand(bestandsnaam):
    """Generator voor het lezen van grote bestanden regel voor regel"""
    with open(bestandsnaam, 'r', encoding='utf-8') as bestand:
        for regel in bestand:
            yield regel.strip()

def filter_regels_met_woord(bestandsnaam, zoekwoord):
    """Generator die alleen regels met specifiek woord teruggeeft"""
    for regel in lees_grote_bestand(bestandsnaam):
        if zoekwoord.lower() in regel.lower():
            yield regel

# Gebruik (werkt met bestanden van gigabytes!)
# for regel in filter_regels_met_woord('groot_bestand.txt', 'python'):
#     print(regel)

Fibonacci Generator

def fibonacci_generator():
    """Oneindige Fibonacci generator"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

def fibonacci_tot(limiet):
    """Fibonacci getallen tot een bepaalde limiet"""
    for getal in fibonacci_generator():
        if getal > limiet:
            break
        yield getal

# Test
print("Fibonacci getallen tot 100:")
for fib in fibonacci_tot(100):
    print(fib, end=" ")
# Output: 0 1 1 2 3 5 8 13 21 34 55 89

Context Managers: Netjes Resource Beheer

Context managers zorgen ervoor dat resources correct worden opgezet en opgeruimd, zelfs wanneer er fouten optreden.

De 'with' Statement

# Traditionele manier (foutgevoelig)
bestand = open('data.txt', 'r')
content = bestand.read()
bestand.close()  # Kan vergeten worden!

# Met context manager (veilig)
with open('data.txt', 'r') as bestand:
    content = bestand.read()
# Bestand wordt automatisch gesloten, zelfs bij fouten!

Eigen Context Manager Maken

Met klasse (__enter__ en __exit__)

class DatabaseVerbinding:
    def __init__(self, db_naam):
        self.db_naam = db_naam
        self.verbinding = None
    
    def __enter__(self):
        print(f"Verbinding maken met database: {self.db_naam}")
        # Simuleer database verbinding
        self.verbinding = f"Verbinding met {self.db_naam}"
        return self.verbinding
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Database verbinding sluiten: {self.db_naam}")
        if exc_type:
            print(f"Fout opgetreden: {exc_val}")
        self.verbinding = None
        return False  # Propageer eventuele exceptions

# Gebruik
with DatabaseVerbinding("klanten_db") as db:
    print(f"Werken met: {db}")
    # Simuleer database operaties
    
# Output:
# Verbinding maken met database: klanten_db
# Werken met: Verbinding met klanten_db
# Database verbinding sluiten: klanten_db

Met contextlib decorator

from contextlib import contextmanager
import time

@contextmanager
def meet_uitvoeringstijd():
    start = time.time()
    print("Timer gestart...")
    try:
        yield start
    finally:
        eind = time.time()
        print(f"Uitvoeringstijd: {eind - start:.4f} seconden")

# Gebruik
with meet_uitvoeringstijd():
    # Simuleer wat werk
    time.sleep(1)
    total = sum(range(100000))
    
# Output:
# Timer gestart...
# Uitvoeringstijd: 1.0056 seconden

Geavanceerde Context Manager

import tempfile
import os
from contextlib import contextmanager

@contextmanager
def tijdelijk_bestand(inhoud, suffix='.txt'):
    """Context manager voor tijdelijke bestanden"""
    temp_bestand = None
    try:
        # Maak tijdelijk bestand
        with tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False) as f:
            f.write(inhoud)
            temp_bestand = f.name
        
        print(f"Tijdelijk bestand aangemaakt: {temp_bestand}")
        yield temp_bestand
        
    finally:
        # Ruim op
        if temp_bestand and os.path.exists(temp_bestand):
            os.unlink(temp_bestand)
            print(f"Tijdelijk bestand verwijderd: {temp_bestand}")

# Gebruik
with tijdelijk_bestand("Hello, World!\nDit is een test.") as bestand:
    with open(bestand, 'r') as f:
        print("Bestandsinhoud:")
        print(f.read())

# Bestand wordt automatisch verwijderd na gebruik

Alles Samen: Praktisch Voorbeeld

Hier is een voorbeeld dat alle drie concepten combineert:

import functools
import time
from contextlib import contextmanager

# Decorator voor logging
def log_functie_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] Aanroep: {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"[LOG] Resultaat: {result}")
        return result
    return wrapper

# Context manager voor performance monitoring
@contextmanager
def monitor_performance():
    start = time.time()
    geheugen_start = get_memory_usage()  # Hypothetische functie
    
    yield
    
    eind = time.time()
    geheugen_eind = get_memory_usage()
    
    print(f"Performance Report:")
    print(f"- Tijd: {eind - start:.4f}s")
    print(f"- Geheugen: {geheugen_eind - geheugen_start}MB")

# Generator voor data processing
def proces_grote_dataset(data_generator):
    """Generator die grote datasets efficiënt verwerkt"""
    for item in data_generator:
        # Simuleer complexe verwerking
        verwerkt_item = item ** 2 + item
        yield verwerkt_item

@log_functie_calls
def data_analyse_pipeline():
    """Hoofdfunctie die alles combineert"""
    # Grote dataset generator
    data = (x for x in range(1000000))
    
    with monitor_performance():
        # Verwerk data met generator
        resultaten = proces_grote_dataset(data)
        
        # Neem alleen eerste 10 resultaten
        eerste_resultaten = []
        for i, resultaat in enumerate(resultaten):
            if i >= 10:
                break
            eerste_resultaten.append(resultaat)
        
        return eerste_resultaten

# Test
resultaten = data_analyse_pipeline()

Best Practices en Tips

Decorators

  • Gebruik altijd @functools.wraps om metadata te behouden
  • Maak decorators herbruikbaar en parametriseerbaar
  • Test decorators grondig, vooral edge cases
  • Documenteer wat je decorator doet en waarom

Generators

  • Gebruik generators voor grote datasets om geheugen te besparen
  • Generators zijn eenmalig - maak nieuwe instance voor hergebruik
  • Combineer generators voor complexe data pipelines
  • Test met next() voor debugging

Context Managers

  • Gebruik altijd context managers voor resource management
  • Handle exceptions correct in __exit__
  • Maak herbruikbare context managers voor veel voorkomende taken
  • Test wat er gebeurt bij exceptions

Veelgemaakte Fouten

Decorator Valkuilen

# FOUT: Decorator wordt uitgevoerd bij import
def slechte_decorator(func):
    print("Deze print wordt uitgevoerd bij import!")  # FOUT
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# GOED: Print alleen bij functie aanroep
def goede_decorator(func):
    def wrapper(*args, **kwargs):
        print("Deze print wordt uitgevoerd bij aanroep")  # GOED
        return func(*args, **kwargs)
    return wrapper

Generator Valkuilen

# FOUT: Generator proberen hergebruiken
gen = (x for x in range(5))
print(list(gen))  # [0, 1, 2, 3, 4]
print(list(gen))  # [] - Generator is uitgeput!

# GOED: Nieuwe generator maken
def maak_generator():
    return (x for x in range(5))

gen1 = maak_generator()
gen2 = maak_generator()
print(list(gen1))  # [0, 1, 2, 3, 4]
print(list(gen2))  # [0, 1, 2, 3, 4]

Volgende Stappen

Nu je deze geavanceerde concepten beheerst, kun je:

  • Metaclasses: Nog geavanceerder - klassen die klassen maken
  • Async/Await: Asynchrone programmering voor performance
  • Type Hints: Betere code documentatie en IDE support
  • Design Patterns: Klassieke patronen implementeren in Python
  • C Extensions: Python uitbreiden met C code voor snelheid

Conclusie

Decorators, generators en context managers zijn essentiële tools in de gereedschapskist van elke gevorderde Python developer. Ze maken je code niet alleen eleganter en efficiënter, maar ook professioneler en onderhoudsbaarder.

Het beheersen van deze concepten opent de deur naar geavanceerde Python programmering en helpt je om complexe problemen op te lossen met elegante oplossingen.

Wil je Meer Leren?

Onze geavanceerde Python cursussen behandelen deze onderwerpen in detail met praktische oefeningen en echte projecten.

Neem Contact Op Bekijk Cursussen