Friday, March 7, 2014

Ulteriore miglioramento del codice

Avevo annunciato l'intenzione di eliminare, dalla classe SVG2Tk, l'uso di file di appoggio esterni.

Nel file che allego in questo post mostro come si puo' fare. Un difetto fondamentale della classe, come è stata progettata finora, è la pesantezza dell'analisi del file SVG, la necessità di riversare le informazioni ottenuta in file esterni, che vengono scritti e riletti senza che ce ne sia un'effettiva necessità. Un ulteriore miglioramento puo' essere realizzato con l'uso dei generatori, argomento tutt'altro che facile da capire, ma che cercherò di rendere nel modo più semplice possibile.

In primo luogo, disponendo di una metodo ricorsivo per l'identificazione degli attributi fondamentali del file SVG, utili al nostro lavoro di traduzione, possiamo cercare di fare in un'unica passata la lettura e la trascrizione dei punti di tracciato, senza salvataggi intermedi e senza creare lunghe liste in memoria, che, nonostante le notevoli migliorie delle tecnologie attualmente disponibili, rischierebbe di saturare la memoria RAM e allungare i tempi.

I generatori sono funzioni speciali, in cui i risultati ottenuti dallo scorrimento di una sequenza vengono restituiti on demand, cioè man mano che il programma ne richiede la disponibilità.

Ciò significa che il file viene letto in modo da assicurare la immediata disponibilità di un'informazione, che puo' essere trattata ignorando le informazioni successive.

Non essendo più necessari i file intermedi di appoggio, forniamo solo definizione del file SVG, e la forniamo alla classe attraverso il suo inizializzatore o costruttore.

Per agevolare la comprensione, spezzo il codice nelle sezioni corrispondenti ai metodi, che pubblico una alla volta per commentarle immediatamente dopo. Il lavoro non è ancora concluso, usero' ancora una volta (l'ultima) un file per riversare i dati sul disco fisso, ma sarà evidente il guadagno di tempo e memoria e la maggiore snellezza del codice.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# -*- coding: utf-8 -*-
#|/usr/bin/env python
import re
import xml.etree.ElementTree as ET

class SVG2Tk:
    """La classe è finalizzata ad assicurare la traduzione, per quanto possibile
    fedele alle specifiche SVG, negli oggetti caratteristici del widget Canvas
    di Tkinter"""

    def __init__(self, file_in):
        
        self.file_in = open(file_in, 'r')

La prima parte non è di difficile comprensione, riprende più o meno i precedenti contenuti. Ho eliminato i parametri riferiti ai nomi dei file di appoggio e ho inserito delle righe di commento che vanno a comporre la documentazione della classe così come si puo' ottenere con i normali strumenti di documentazione di Python: lo vedremo più chiaramente alla fine.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    def s_match(self, pattern, d):
        """restituisce una stringa contenente la substringa che soddisfa
            il pattern fornito alla funzione, e la stringa risultante dopo
            l'estrazione della substringa"""
        
        matchObj = re.match(pattern, d)
        if matchObj:
            string = matchObj.group()
            d = d.replace(string, "")
            s_string = re.split(r'[;\s]*',string)
            string = ','.join(s_string[:-1])
            return (string, d)
        return None

Anche questa sezione, che utilizza le regular expressions per il match tra stringhe di SVG e modelli, non differisce dalla precedente.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    def xmlrecur(self,x):
        "restituisce l'insieme degli attributi che soddisfano le stringhe di ricerca "
        for i in x:
            if set(['id','style','d']).issubset(i.attrib):
                yield (i.attrib['id'], i.attrib['style'], i.attrib['d'])
            elif set(['width','height']).issubset(i.attrib):
                yield ('width - height', i.attrib['width'], i.attrib['height'])
            else:
                for j in self.xmlrecur(i):
                    yield j

La routine xmlrecur è stata, invece, pesantemente rimaneggiata. Ogni volta che l'identificazione dell'attributo è positiva, uso la word yield che è simile al return, cioè alla restituzione dei risultati delle attività previste dal metodo, ma avviene ogni volta che ho necessità di fornire al codice chiamante dei valori senza interrompere la scansione dell'iterable, in questo caso del file di origine dei dati, che viene solo temporaneamente sospesa. Ad ogni successiva chiamata verrà ripresa la scansione e verranno forniti ulteriori risultati.

Ciò che rende di difficile comprensione il metodo è il fatto che apparentemente viene restituito al codice chiamante un dato, mentre in realtà viene restituito un metodo, che si chiama generatore.

Vediamo la sezione finale e ne discutiamo immediatamente.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    def re_load(self):

        tree = ET.parse(self.file_in)
        root = tree.getroot()
        generatore = self.xmlrecur(root)
        tk_out = open('tk_out','w')
        for i in generatore:
            if i[0] == 'width - height':
                self.width  = float(i[1])
                self.height = float(i[2])
            else:
                self.id    = i[0]
                self.style = i[1]
                self.d     = i[2]
                try:
                    pattern=[
                    r'([M]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]?[-\d]*[\.]?[\d]*[\s]*)',
                    r'([L]{1}[\s]*[-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*)',
                    r'([C]{1}[\s]*)(([-\d]*[\.]?[\d]*[\,\s]{1}[-\d]*[\.]?[\d]*[\s]*){3})',
                    r'([z]{1}[\s]*)'
                    ]
                    
                    while len(self.d) > 0:
                        for p in pattern:
                            ret = self.s_match(p, self.d)
                            if ret != None:
                                string = ret[0]
                                self.d = ret[1]
                                tk_out.write(string+'\n')                            
                except Exception:
                    print Exception
                    tk_out.close()
                    break

if __name__ == '__main__':
    app = SVG2Tk('EU-Italy.svg')
    app.re_load()

La funzione re_load è simile alla precedente, ma ne differisce per il fatto che i dati ottenuti dalla scansione vengono caricati, non come dati, ma come metodo, sull'item generatore da cui vengono estratti uno alla volta e sottoposti al matching. Non è intuitivo, il generatore non contiene alcun dato, non è una lista, bensì una chiamata iterativa al metodo recurxml che estrae i dati on demand. Ci ho messo parecchio a capire come funziona questo metodo e rinuncio a tentare di spiegarlo ulteriormente. Se avete voglia di discuterlo mandatemi un commento e ne parliamo insieme.

Nella sezione finale inizializzo la class col solo nome del file SVG e poi faccio una chiamata al metodo re_load che fa girare il tutto. Nel prossimo post eliminerò anche l'ultimo puntello, cioè il file di appoggio, e farò in modo che l'estrazione avvenga in streaming con la creazione degli oggetti di Tkinter.Canvas. Alla prossima

No comments:

Post a Comment

How to create a virtual linux machine with qemu under Debian or Ubuntu with near native graphics performance

It's been a long time since my latest post. I know, I'm lazy. But every now and then I like to publish something that other people c...