[Corso Python3] Fwd: Velocità esecuzione blocchi if, try

Gianluca Biondi gluca.biondi a gmail.com
Sab 11 Giu 2016 18:04:04 CEST


Il 24 maggio 2016 13:03, Gianluca Biondi <gluca.biondi a gmail.com> ha scritto:
> Buonasera a tutti!!
>
> Istruzioni:
>
> creare un ambiente virtuale (prima lezione -> virtualenv --python=python3 env
> env (env è la cartella in cui si sta installando)
> source bin/activate (attiva l'ambiente virtuale)
> pip install pygal (serve a generare i grafici)
> copiare nella cartella virtuale il file allegato (test_execution_speed_class.py)
> dare il comando python test_execution_speed_class.py
> nella cartella dove avete messo il file verrà creata una cartella con il
> nome 'grafici'
> apriteli con un browser (al visualizzatore di immagini di debian non
> piacciono tanto)
>

Come mi è stato fatto notare sabato, c'era qualcosa che non andava
(in pratica avevo dimostrato solo che le istruzioni ci mettono sempre
lo stesso tempo ad eseguire.... :( )
Ho fatto alcune modifiche (che trovate elencate ad inizio script),
e ho aggiunto altre funzioni.

Enjoy,
Gluca

A quanto pare gli allegati non passano per la mailing list...
# ###################################################

"""
Genera delle liste di dati casuali con alcune eccezioni all'interno
(stringhe vuote) e fa dei calcoli sui valori allo scopo di scoprire
se sia più efficiente usare un blocco if, else un blocco Try, except
o una list comprehension, etc... per filtrare le eccezioni.
#
I dati vanno raggruppati per probabilità, che va testata da 0 a 1.
Modifico il metodo "prova_tempi" per passare due liste invece che 6 parametri.
Aggiunto controllo parametri in "prova_tempi".
Aggiunto metodo per stampare i grafici per numero di campioni.
Corretto sul grafico la stampa della prob, non più da 0 a 1000 ma da 0 a 1
"""
# Mettiamo i grafici in una cartella
from os import makedirs
# Mi serve per dividere gli elementi quando genero il grafico
from collections import Counter
from random import randint
from time import time
# Un grafico 3D non è così semplice da realizzare. Mi accontento del 2D
import pygal
from pygal.style import LightSolarizedStyle


class Test_execution_speed():

    def __init__(self):
        pass

    def _lista_dati_casuali(self, num_valori, probabilita):
        """
        Crea un array di num_valori, con 'probabilità'su 1000 di possibilità di
        avere una lista vuota al suo interno. Ritorna la lista.
        """
        lista_valori = []
        for i in range(num_valori):
            numero_random = randint(1, 1000)
            if numero_random <= probabilita:     # < -> <=, prob=1->tutti i val
                lista_valori.append('')
            else:
                lista_valori.append(numero_random)

        return lista_valori                      # tuple(lista_valori)

    def _test_con_if(self, lista):
        """
        Scansiona i dati della lista e, se c'è un numero esegue una divisione.
        l'eccezzione è gestita da if, else.
        Ritorna il numero delle volte che non effettua operazioni
        """
        num_errori = 0
        for elemento in lista:
            if type(elemento) == type(1):
                1 / elemento
            else:
                num_errori += 1

        return num_errori

    def _test_con_if_is(self, lista):
        """
        Scansiona i dati della lista e, se c'è un numero esegue una divisione.
        l'eccezzione è gestita da if, else.
        Ritorna il numero delle volte che non effettua operazioni
        Al posto di ==, is
        """
        num_errori = 0
        for elemento in lista:
            if type(elemento) is type(1):
                1 / elemento
            else:
                num_errori += 1

        return num_errori

    def _test_con_if_isinstance(self, lista):
        """
        Scansiona i dati della lista e, se c'è un numero esegue una divisione.
        l'eccezzione è gestita da if, else ed il controllo da isinstance.
        Ritorna il numero delle volte che non effettua operazioni.
        Al posto di usare type()==type() uso la funzione isinstance().
        """
        num_errori = 0
        for elemento in lista:
            if isinstance(elemento, int):
                1 / elemento
            else:
                num_errori += 1

        return num_errori

    def _test_con_try(self, lista):
        """
        Scansiona i dati della lista e, se c'è un numero esegue una divisione.
        l'eccezzione è gestita da try ed except.
        Ritorna il numero delle volte che non effettua operazioni
        """
        num_errori = 0
        for elemento in lista:
            try:
                1 / elemento
            except:
                num_errori += 1

        return num_errori

    def _test_con_lC(self, lista):
        """
        Crea una nuova lista con i valori della list comprehension.
        Ritorna il numero delle volte che non effettua operazioni
        """
        num_errori = (len(lista) -
                      len([1/i for i in lista if type(i) == type(1)]))
        return num_errori

    def _test_con_lC_meno_type(self, lista):
        """
        Crea una nuova lista con i valori della list comprehension.
        Ritorna il numero delle volte che non effettua operazioni
        Ho tolto una chiamata a type().
        """
        ctr = type(1)
        num_errori = len(lista) - len([1/i for i in lista if type(i) == ctr])
        return num_errori

    def _test_con_lC_meno_type_is(self, lista):
        """
        Crea una nuova lista con i valori della list comprehension.
        Ritorna il numero delle volte che non effettua operazioni.
        Ho tolto una chiamata a type() e al posto di ==, is.
        """
        ctr = type(1)
        num_errori = len(lista) - len([1/i for i in lista if type(i) is ctr])
        return num_errori

    def _test_con_FG_meno_type_is(self, lista):
        """
        Crea una nuova lista con i valori della Function Generator.
        Ritorna il numero delle volte che non effettua operazioni.
        Ho tolto una chiamata a type() e al posto di ==, is.
        """
        ctr = type(1)
        num_errori = (len(lista) -
                      len(list((1/i for i in lista if type(i) is ctr))))
        return num_errori

    def prova_tempi(self, lista_valori_da_testare, lista_prob_da_testare):
        """
        Cicla tra i valori di num_valori e probabilità e segna i tempi di
        elaborazione. Ritorna una lista di tuple contenente i risultati
        ordinati come:
        Nome Funzione, Prob, Nr_valori, Nr_errori, tempo_exec
        """
        # Controllo se i dati in ingresso sono liste e > 0
        if ((not isinstance(lista_valori_da_testare, list)) or
                (len(lista_valori_da_testare) <= 0)):
            print('La lista dei valori da testare non è corretta!!')
            raise SystemExit                     # Problema?Esco dal programma!

        if ((not isinstance(lista_prob_da_testare, list)) or
                (len(lista_prob_da_testare) <= 0)):
            print('La lista delle probabilità non è corretta!!')
            raise SystemExit                     # Problema?Esco dal programma!

        # Lista dei risultati e coordinate
        risultati = []

        # Funzioni da chiamare: aggiungere una 'chiave: valore'
        # con valore = nome funzione per aggiungere altri test
        func_to_call = {'if':          self._test_con_if,
                        'if is':       self._test_con_if_is,
                        'if_istance':  self._test_con_if_isinstance,
                        'try':         self._test_con_try,
                        'lc':          self._test_con_lC,
                        'lc - type':   self._test_con_lC_meno_type,
                        'lc,-type,is': self._test_con_lC_meno_type_is,
                        'Func Gener':  self._test_con_FG_meno_type_is}

        # Ciclo tra i valori di prob, di quantità e cronometro l'esecuzione
        for prob in lista_prob_da_testare:

            for num_valori in lista_valori_da_testare:
                lista_valori = self._lista_dati_casuali(num_valori, prob)

                # Per ogni nome key, chiama la funzione associata nel dizio
                for key in func_to_call:
                    tempo_prima = time()                    # cronometro start
                    num_errori = func_to_call[key](lista_valori)
                    cronometro = time() - tempo_prima       # cronometro stop
                    # cronometro è in secondi...*1000 mi da millisecondi
                    # unità più ragionevole
                    risultati.append((str(key), prob, num_valori, num_errori,
                                      cronometro*1000))

        return risultati

    def stampa_grafici_per_prob(self, lista):
        """
        Crea una cartella dove salvare i grafici.
        Estrae i valori dalla lista dei risultati e genera il grafico
        per probabilità.
        Implementare una funzione che raggruppi i dati in un altro modo
        è semplice.
        """
        # Crea la cartella dove mettere i grafici
        # exist_ok non genera errori se esiste
        makedirs('grafici', exist_ok=True)

        # Configurazione del grafico
        config = pygal.Config()
        config.style = LightSolarizedStyle
        config.show_legend = True
        config.human_readable = True
        config.x_title = 'Numero campioni'
        config.y_title = 'Tempo ms'
        # config.fill = True

        # Counter mi torna una ricorrenza per elemento prob testato
        prob_testate = list(Counter([ele[1] for ele in lista]))
        # Lista con i nomi delle funzioni testate
        indici_funzioni = list(Counter([ele[0] for ele in lista]))

        for i in prob_testate:
            chart = pygal.XY(config)
            nome_file = ' '.join(['Grafico con probabilità =', str(i/1000)])
            chart.title = nome_file
            for j in indici_funzioni:
                # Aggiungo al grafico (x=num_val, y=t_exec) solo se
                # nome_funzione e prob_testata coincidono con j, i
                chart.add(j, [(ele[2], ele[4]) for ele in lista
                          if (ele[0] == j and ele[1] == i)])     # Damn pep8!!

            chart.render_to_file(''.join(['grafici/', nome_file, '.svg']))
        # print(indici_funzioni, prob_testate, dati_if)

    def stampa_grafici_per_num_valori(self, lista):
        """
        Crea una cartella dove salvare i grafici.
        Estrae i valori dalla lista dei risultati e genera i grafici
        divisi per numero di valori testati.
        Implementare una funzione che raggruppi i dati in un altro modo
        è semplice.
        """
        # Crea la cartella dove mettere i grafici
        # exist_ok non genera errori se esiste
        makedirs('grafici', exist_ok=True)

        # Configurazione del grafico
        config = pygal.Config()
        config.style = LightSolarizedStyle
        config.show_legend = True
        config.human_readable = True
        config.x_title = 'Valori Probabilità'
        config.y_title = 'Tempo ms'
        # config.fill = True

        # Counter mi torna una ricorrenza per campioni testati
        num_valori = list(Counter([ele[2] for ele in lista]))
        # Lista con i nomi delle funzioni testate
        indici_funzioni = list(Counter([ele[0] for ele in lista]))

        for i in num_valori:
            chart = pygal.XY(config)
            nome_file = 'Test con {} campioni'.format(str(i))
            chart.title = nome_file
            for j in indici_funzioni:
                # Aggiungo al grafico (x=prob_test, y=t_exec) solo se
                # nome_funzione e campioni_testati coincidono con j, i
                chart.add(j, [((ele[1]/1000), ele[4]) for ele in lista
                          if (ele[0] == j and ele[2] == i)])   # Damn pep8!!

            chart.render_to_file(''.join(['grafici/', nome_file, '.svg']))
        # print(indici_funzioni, prob_testate, dati_if)

# #############################################################################
# ##                                                                        ###
# #############################################################################

tempo_prima = time()
test = Test_execution_speed()
# I dati da passare sono una lista con il numero di test da fare
# una lista con il numero di probabilità da testare
# 0 <= prob <= 1 -> prob * 1000
# le probabilità sono espresse con un numero tra 0 e 1000
# ritorna una lista di tuple
valori = test.prova_tempi([1000],
                          list(range(0, 1001, 100)))      # <- SMANETTARE QUI

# stampo a terminale i valori generati
for i in valori:
    print("".join(["Funzione: {0[0]:12s};",
                   "\tProbabilità: {0[1]:4d};",
                   "\tNumero valori: {0[2]:4d};",
                   "\tNumero errori: {0[3]:4d};",
                   "\tIn tempo: {0[4]:.6f} ms"]).format(i))

# Genero i grafici
test.stampa_grafici_per_num_valori(valori)
# test.stampa_grafici_per_prob(valori)

# Satmpa in quanto tempo ho eseguito il programma
cronometro = time() - tempo_prima
print('Ci ho messo {:6f} secondi.'.format(cronometro))

# #######################################################



Maggiori informazioni sulla lista fsug-corso-python3