Google+ Followers

woensdag 23 december 2015

Python multithreading in een notendop

Een van de vele redenen waarom ik PHP heb laten vallen voor Python is dat dingen die in PHP praktisch onmogelijk waren in Python een peulenschil zijn.

Een van die dingen is multithreading; een taak verdelen over meerdere CPU's.

Onderstaande is een tekstboek voorbeeld van multithreading in Python voor Python3. Knipt en plak en roep aan met python3.4 geplaktscript.py


#!python3

"""
Multi-threading werkt met een thread pool en een data-queue.
De thread pool wordt gevuld met een aantal 'workers' die het echte werk doen,
zij halen de data waar ze mee moeten werken uit een 'queue'.
De workers gaan door tot ze geen data meer in de queue zien en als alle workers klaar zijn
stopt het script.

Zoals het script nu is maakt het vier workers aan en vult het de queue met 20 items.
De 'do_work' method wacht .1 seconde dus de twintig items in de queue worden uitgevoerd in (20/4)*.1=0.5 seconde.
Verander het aantal workers van 4 naar 10 en zie dat het script nu klaar is in 0.2 seconde (20/10)*.1=0.2

Noot: je kunt dit niet ongestraft blijven opschalen. Als het werk dat je doet erg CPU-intensief is dan kun je niet meer workers opstarten
dan je cores hebt, anders gaan de taken alsnog elkaar in de weg zitten en dan helpt het niet meer.
Op een dedicated machine kun je gaan tot cores-1 workers, zodat er een één core vrij blijft voor het moederprocess en overige taken.

"""
import threading
from queue import Queue
import time

q = Queue()

# Maak een lock object om de uitvoer te synchroniseren, is alleen nodig voor dit voorbeeld.
lock = threading.Lock()


def do_work(item):
    """
    De do_work method simuleert het verwerken van de data uit de queue
    Hij wacht nu alleen 0.1 seconde.
    """
    time.sleep(.1)

    # Zorg dat de threads op elkaar wachten bij het printen.
    with lock:
        print(threading.current_thread().name, item)


def worker():
    """
    De worker method haalt data uit de queue en laat de data verwerken door de 'do_work' method,
    dat zou dus ook een method in een class van je applicatie kunnen zijn.
    """
    # Ga door tot de queue leeg is.
    while True:
        # Haal de volgende taak uit de wachtrij
        item = q.get()

        # Verwerk de data.
        do_work(item)

        # Sluit de taak.
        q.task_done()


def main():
    # Maak een thread-pool met een aantal threads die de 'worker' method gaan aanroepen.
    for i in range(4):
        t = threading.Thread(target=worker)
        t.daemon = True  # thread stopt als het moederproces stopt.
        t.start()

    # Vul de queue met data waar de workers mee gaan werken.
    for item in range(200):
        q.put(item)

    # Noteer de huidige tijd
    start_time = time.perf_counter()
    # Start de thread-pool
    q.join()
    # Print de verstreken tijd
    print('time:', time.perf_counter() - start_time)


if __name__ == '__main__':
    main()

woensdag 9 december 2015

CSV inlezen met Python

Python heeft natuurlijk CSV functies, maar in tegenstelling tot PHP heeft Python ook standaard een Sniffer class die het formaat van een CSV bestand kan analyseren. Dat betekent dat je voor goed-geformatteerde CSV bestanden niet meer hoeft op te  geven welke scheidingsteken en welke quote er wordt gebruikt.

Een voorbeeldje:

import csv


class MyCSVReader():
    @staticmethod
    def read(filename):
        with open(filename, 'r') as csvfile:
            # Detecteer het scheidingsteken en de gebruikte quotes.
            dialect = csv.Sniffer().sniff(csvfile.read(1024))

            # Breng de filepointer van het bestand terug naar het begin
            # zodat het niet opnieuw geopend hoeft te worden.
            csvfile.seek(0)

            # Maak een csv reader met het gevonden dialect
            my_reader = csv.reader(csvfile, dialect)

            # Sla de header over
            next(my_reader)

            # Lees de rest van het bestand.
            rows = []
            for row in my_reader:
                # Print de eerste twee kolommen en vat kolommen 3 t/m 6 (2 t/m 5) samen in een string die PostgreSQL begrijpt als een "int[][]".
                rows.append([row[0], row[1], '{' + ','.join(row[2:5]) + '}'])
            return rows


def main():
    print('-'*20)
    rows = MyCSVReader.read('komma_tick.csv')
    for r in rows:
        print(r)

    print('-'*20)
    rows = MyCSVReader.read('puntkomma_dubbelquote.csv')
    for r in rows:
        print(r)


# Vertel de commandline-interpreter van Python dat hij moet beginnen met uitvoeren bij de main() functie.
if __name__ == '__main__':
    main()


"""
Inhoud van "puntkomma_doublequote.csv":
"greeting";"test number";"value a";"value b";"value c";"value d"
"hello";1;7;9;6;4
"hi";1;5;8
"goodbye";87;8;7;9;2

Inhoud van "komma_tick.csv"
'greeting','test number','value a','value b','value c','value d'
'hello',1,7,9,6,4
'hi',1,5,8
'goodbye',87,8,7,9,2
"""