Programmazione Python > Base

Gestione delle Eccezioni

<< < (3/3)

Python:
Tutti quei new mi fanno vomitare -.-
SarĂ  per quello che ho scelto Python! :D

riko:

--- Citazione da: ubuntu_of_fortune - Gennaio 26, 2011, 10:36 ---Hai centrato la questione secondo me... a parte il fatto che una scrittura di questo tipo:

--- Citazione da: riko - Gennaio 14, 2011, 18:05 ---[codice]
hash.each_pair do |key, value|
  puts "#{key} is #{value}"
end
[/codice]

--- Termina citazione ---

non mi piace proprio (ma sara` sempre per il bias di cui sopra).
--- Termina citazione ---

Esatto. Siamo completamente nel mondo dei gusti personali.
Sull'esempietto qua sopra siamo molto vicini come scrittura all'equivalente Python.
La differenza e' che in Python abbiamo una special form (e le special forms in python non si aggiungo)
e in ruby abbiamo un metodo (e i metodi in ruby -- e in python -- si aggiungono liberamente).

Per il resto e' un rimescolamento di termini...



--- Citazione ---
--- Citazione da: riko - Gennaio 14, 2011, 18:05 ---Il discorso e' che "in generale" fra blocchi e funzioni la questione e' che da un lato e' brutto dover definire
una funzione per usarla una volta sola. Dall'altro si finisce per duplicare il codice in piu' blocchi che starebbero
bene astratti in una sola funzione.

--- Termina citazione ---

Beh pensandoci, definire una funzione per utilizzarla una sola volta puo` avere i suoi perche`... Refactoring per esempio...
invece che definire una funzione/metodo con un corpo eccessivamente pregno, e` buona norma frammentare e responsabilizzare le varie parti
 - Sebbene, in generale, queste cose capitano piu` con linguaggi tipo Java che con Python/Ruby[0] - .

--- Termina citazione ---

Sono assolutamente d'accordo sul caso generale. Definire una funzione per refactoring o per documentazione e' sicuramente
una cosa buona e ha senso, anche se la funzione e' usata una sola volta (dopotutto quando si progetta una libreria, molte
funzioni sono usate 0 volte... le useranno i clients). Conversamente, immagina di "dovere" trasformare i body dei for in Python
in funzioni "obbligatoriamente".

Siamo d'accordo che ci sembrerebbe molto strana come pratica. Con il for non c'e' problema, ma se usiamo uno stile
un po' piu' funzionale di programmazione con lc e combriccola, a volte il desiderio di qualcosa di piu' ricco in una
funzione si pone. Questo essenzialmente traccia in Python il limite di buon senso su quando si *deve* usare un for.

Con i blocchi alla ruby questo limite sarebbe probabilmente 2 tacche avanti. Non credo niente di drammatico.

[codice]In generale astrarre con una funzione utilizzata una sola volta non ha tanto senso, di contro pero`, per ovviare alla mancanza in Python dei blocchi sintattici,
non si puo` all'ccorrenza ricorrere a soluzioni tipo *map*, *lamda expressions* e a "blocchi sintattici" utilizzando lo statement *with* ? [/codice]

Ci ricolleghiamo a sopra. Esatto! La differenza e' che tutta la parte funzionale di Python e' "volutamente" tenuta limitata.
E mi sta bene, per carita': quando si comincia a fare eccessivamente i fighi con quella parte di Python si scrive codice molto poco leggibile.
Credo che sia una questione sintattica: in Lisp le stesse cose sono molto piu' umane. Quindi si, sono d'accordo.

Poi with vince a mani basse sui blocchi per le cose che si fanno bene con i blocchi. *Ma* e' un po' piu' verboso da mettere giu'.

Provo a spiegare: quando si usa un "prefabbricato" with e' il massimo:

[codice]
with context() as c:
    do_something(c)
[/codice]

e' ottimo. Specie nei casi semplici:
[codice]
with file(...) as f:
    for line in f:
        process_line(line)[/codice]

e' veramente bello. Guardiamolo in Ruby:
[codice]
File.open('log.txt') do |file_object|
    file_object.each_line do |line|
        print line
    end
end
[/codice]

ancora una volta la forma e' veramente simile. Ancora una volta abbiamo un metodo
contro una special form. Qui si vedono due cose essenzialmente: io credo che il with
sia leggermente piu' chiaro.

La versione Ruby e' piu' facile da scrivere: usare i blocchi per il pattern

[codice]def f(...)
   setup
   begin
       injected_block
   ensure
       cleanup
   end
end[/codice]

e' davvero facile e comodo. In particolare i blocchi in ruby si usavano gia' per fare queste cose
quando noi non avevamo ancora with. All'epoca in effetti un po' invidiavo questa cosa. Scriversi
da 0 questo tipo di pattern (ovvero un context manager) e' parecchio piu' complesso *concettualmente*
esattamente come e' piu' comodo da usare.

Questo, se ci pensiamo, e' *esattamente* lo stesso pattern degli iteratori. Gli iteratori li abbiamo
semplificati con i generatori: due botte di yield e diventano semplici da scrivere.


--- Citazione ---Ok, concordo! Ma non credi che la questione sia piu` legata al problema che non a Python in generale?
--- Termina citazione ---

Si, certo.


--- Citazione ---Alla fine, quando cerchi soluzioni *generlaiste* che tentanto di gestire in un unica maniera (unica e apparentemente flessibile) lo stesso problema, perdi sempre qualcosa.
In questo caso la possibilita` di trattare l'errore in maniera specifica per ciascuna situazione.
--- Termina citazione ---

Ni. Qui entra l'arte del design: ci sono, immagino, esempi di problemi su cui non ci si puo' fare nulla.
Pero' spesso il problema e' che non si riesce a farlo mantenendo la feature *semplice*.

Per esempio... possiamo ridurre *ogni* forma di controllo di flusso ad un singolo costrutto? Si. Possiamo.
First class continuations. Ci fai eccezioni, backtracking, qualunque cosa. Cosa paghi? Paghi che il mondo sarebbe un posto migliore perche' i cialtroni non riuscirebbero a fare i programmatori e noi prenderemmo stipendi 10 volte piu' alti. Ma a parte questo perderemmo il fatto che la maggior parte della gente non saprebbe programmare (e di fatto qualcuno inventerebbe dei linguaggi umani per queste persone tornando al mondo odierno).


--- Citazione ---Una soluzione potrebbe essere quella di utilizzare dei decoratori che invochino un metodo di una istanza di delegate specifica
(immagino di essere stato poco chiaro -  pero` ammetto che e` una idea estemporanea e molto probabilmente eccessivamente sovraingegnerizzata)
 8)

--- Termina citazione ---

Interessante... un esempio?


--- Citazione ---------
[0] Esempio tipico
[codice]
File f=new File("test.txt");
FileInputStream fis=new FileInputStream(f);
InputStreamReader isr=new InputStreamReader(fis);
BufferedReader br=new BufferedReader(isr);
String line = br.readLine()
while(line != null) {
       System.out.println(line);
       line=br.readLine();
}
[/codice]

VS

[codice]
file f = open("test.txt")
for l in f:
    print l
[/codice]

EDIT

Vabeh, ammetto che la soluzione in Java si puo` compattare ancora.... per correttezza compatto

[codice]
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(new File("test.txt"))));
String line = br.readLine()
while(line != null) {
       System.out.println(line);
       line=br.readLine();
}
[/codice]

Ecco... leggibile soprattutto! :D

--- Termina citazione ---
[/quote]

Lol. Quoto.

Ma in Java anche questo ha un suo fascino (ecco il lato dinamico e funzionale di Java).

[codice=Java]
import java.io.*;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class Prova {
    static public void main(String[] s) throws FileNotFoundException {
        final BufferedReader br = new BufferedReader(new FileReader("/Users/enrico/log.txt"));
        for(String line : new Iterable<String>() {
            public Iterator<String> iterator() {
                return new Iterator<String>() {
                    private String bufferedLine = null;
                    private boolean good = true;
                    public boolean hasNext() {
                        if(bufferedLine == null) {
                            try {
                                bufferedLine = br.readLine();
                            } catch (IOException e) {
                                good = false;
                                return false;
                            }
                        }
                        return good && (bufferedLine != null);
                    }

                    public String next() {
                        if(!good) { throw new NoSuchElementException(); }
                        if(bufferedLine != null) {
                            String tmp = bufferedLine;
                            bufferedLine = null;
                            return tmp;
                        } else {
                            try {
                                String tmp = br.readLine();
                                if(tmp != null) {
                                    return tmp;
                                } else {
                                    good = false;
                                    throw new NoSuchElementException();
                                }
                            } catch (IOException e) {
                                good = false;
                                throw new NoSuchElementException();
                            }
                        }
                    }

                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        }) {
            System.out.println(line);

        }
       
    }
}
[/codice]

In clojure...
[codice]
(import [java.io BufferedReader FileReader])

(with-open [f (BufferedReader. (FileReader. "log.txt"))]
  (doseq [line (line-seq f)]
    (println line)))
[/codice]

ubuntu_of_fortune:

--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---La differenza e' che in Python abbiamo una special form (e le special forms in python non si aggiungo)
e in ruby abbiamo un metodo (e i metodi in ruby -- e in python -- si aggiungono liberamente).

Per il resto e' un rimescolamento di termini...

--- Termina citazione ---

Si esatto! Siamo perfettamente concordi allora :)


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---Conversamente, immagina di "dovere" trasformare i body dei for in Python in funzioni "obbligatoriamente".

Siamo d'accordo che ci sembrerebbe molto strana come pratica. Con il for non c'e' problema, ma se usiamo uno stile
un po' piu' funzionale di programmazione con lc e combriccola, a volte il desiderio di qualcosa di piu' ricco in una
funzione si pone. Questo essenzialmente traccia in Python il limite di buon senso su quando si *deve* usare un for.

--- Termina citazione ---
Si certo! Come sopra, assolutamente d'accordo!


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---Poi with vince a mani basse sui blocchi per le cose che si fanno bene con i blocchi. *Ma* e' un po' piu' verboso da mettere giu'.
io credo che il with sia leggermente piu' chiaro.

La versione Ruby e' piu' facile da scrivere: usare i blocchi per il pattern

[codice]def f(...)
   setup
   begin
       injected_block
   ensure
       cleanup
   end
end[/codice]

e' davvero facile e comodo.

--- Termina citazione ---

Si infatti! Trovo la sintassi del *with* notevolmente piu` chiara e intuitiva dei blocchi alla Ruby - ma, per ribadire: questione di abitudini e gusti :)


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---Questo, se ci pensiamo, e' *esattamente* lo stesso pattern degli iteratori. Gli iteratori li abbiamo
semplificati con i generatori: due botte di yield e diventano semplici da scrivere.

--- Termina citazione ---
+1


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---Per esempio... possiamo ridurre *ogni* forma di controllo di flusso ad un singolo costrutto? Si. Possiamo.
First class continuations. Ci fai eccezioni, backtracking, qualunque cosa. Cosa paghi? Paghi che il mondo sarebbe un posto migliore perche' i cialtroni non riuscirebbero a fare i programmatori e noi prenderemmo stipendi 10 volte piu' alti. Ma a parte questo perderemmo il fatto che la maggior parte della gente non saprebbe programmare (e di fatto qualcuno inventerebbe dei linguaggi umani per queste persone tornando al mondo odierno).

--- Termina citazione ---

LOL


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---Ma in Java anche questo ha un suo fascino (ecco il lato dinamico e funzionale di Java).
[codice=Java]
import java.io.*;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class Prova {
    static public void main(String[] s) throws FileNotFoundException {
        final BufferedReader br = new BufferedReader(new FileReader("/Users/enrico/log.txt"));
        for(String line : new Iterable<String>() {
            public Iterator<String> iterator() {
                return new Iterator<String>() {
                    private String bufferedLine = null;
                    private boolean good = true;
                    public boolean hasNext() {
                        if(bufferedLine == null) {
                            try {
                                bufferedLine = br.readLine();
                            } catch (IOException e) {
                                good = false;
                                return false;
                            }
                        }
                        return good && (bufferedLine != null);
                    }

                    public String next() {
                        if(!good) { throw new NoSuchElementException(); }
                        if(bufferedLine != null) {
                            String tmp = bufferedLine;
                            bufferedLine = null;
                            return tmp;
                        } else {
                            try {
                                String tmp = br.readLine();
                                if(tmp != null) {
                                    return tmp;
                                } else {
                                    good = false;
                                    throw new NoSuchElementException();
                                }
                            } catch (IOException e) {
                                good = false;
                                throw new NoSuchElementException();
                            }
                        }
                    }

                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
            }
        }) {
            System.out.println(line);

        }
       
    }
}
[/codice]

--- Termina citazione ---

Si certo, ha un suo fascino... il fascino del surreale! :D
Scherzi a parte, trovo che l'introduzione del *for each* in Java5+ abbia notevolmente semplificato e alleggerito (soprattutto) l'iterazione sulle collezioni.
In Java una scrittura del genere ha i suoi perche` con un vasto numero di classi interne... certo che replicare le stesse istruzioni per ogni singola lettura di file e` da manicomio! :)


--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---In clojure...
[codice]
(import [java.io BufferedReader FileReader])

(with-open [f (BufferedReader. (FileReader. "log.txt"))]
  (doseq [line (line-seq f)]
    (println line)))
[/codice]

--- Termina citazione ---

Questa, invece la trovo una *Grande* alternativa! ;)
Bello Clojure... devo dargli una occhiata! :)

[... continuo a rispondere in un altro post ...]

ubuntu_of_fortune:

--- Citazione da: riko - Gennaio 30, 2011, 15:12 ---
--- Citazione ---Una soluzione potrebbe essere quella di utilizzare dei decoratori che invochino un metodo di una istanza di delegate specifica
(immagino di essere stato poco chiaro -  pero` ammetto che e` una idea estemporanea e molto probabilmente eccessivamente sovraingegnerizzata)
 8)

--- Termina citazione ---

Interessante... un esempio?

--- Termina citazione ---

Non ricordo esattamente cosa avevo in mente, ma ho cercato di ricostruire - la mia idea estemporanea era quella di giocare con una gerarchia di gestori di eccezioni, ciascuno responsabilizzato alla gestione di una determinata eccezione e assegnare il controllo delle eccezioni via decoratori.

Ancora una volta, questo avvalora la tesi di Riko che in Python per "iniettare" codice da eseguire, si devono utilizzare funzioni o *riferimenti a funzioni* (in questo caso decoratori).

Questa la mia soluzione (provo a spiegarla passo passo):

* 1) Definizione di Eccezioni Custom
Come prima cosa, definiamo le Eccezioni personalizzate

[codice]
# Custom Exceptions
class GenericException(Exception):
    pass

class CustomException(Exception):
    pass
[/codice]


* 2) Definizione dei Gestori delle Eccezioni
A questo punto, una volta definite le eccezioni custom, e` necessario definire le classi delegate alla gestione delle eccezioni le quali rappresenteranno l'input da passare al decoratore

[codice]
# Custom Exception Handlers
class GenericExceptionHandler(object):
   
    def do_try(self, func, *pargs, **kwargs):
        return func(*pargs, **kwargs)
   
    def __call__(self, func, *pargs, **kwargs):
        try:
            self.do_try(func, *pargs, **kwargs)
        except GenericException as gex:
            print 'Generic Exception:', gex
        except Exception as ex:
            print 'Simple Exception:', ex
           
class CustomExceptionHandler(GenericExceptionHandler):
   
    def do_try(self, func, *pargs, **kwargs):
        try:
            return func(*pargs, **kwargs)
        except CustomException as cex:
            print 'Custom Exception:', cex
[/codice]

L'idea era quella di definire una gerarchia di Handler di eccezioni (eventualmente estendibili e ulteriormente customizzabili con un Template Method Pattern)
che racchiudono le esecuzioni della funzione (successivamente decorata) in una serie di *try-except* innestati.

Per come sono implementati, gli handler sono dei callable che attivano il controllo delle eccezioni quando sono invocati.


* 3) Definizione del decoratore
[codice]
#decorator
def check_exceptions(handler):
    class Dispatcher(object):
        def __init__(self, func):
            self.func = func
            self.handler = handler()
        def __call__(self, *pargs, **kwargs):
            return self.handler(self.func, *pargs, **kwargs)
    return Dispatcher
[/codice]

Il decoratore ha come parametro aggiuntivo l'handler a cui delegare la gestione delle eccezioni.


* 4) Codice di Esempio
[codice]
#decorated Functions
@check_exceptions(GenericExceptionHandler)
def raise_generic_exception():
    raise GenericException('Generic Exception and Generic Handler')

@check_exceptions(CustomExceptionHandler)
def raise_custom_exception():
    raise CustomException('Custom Exception and Custom Handler')

@check_exceptions(CustomExceptionHandler)
def raise_generic_exception_with_custom_handler():
    raise GenericException('Generic Exception and Custom Handler (nested try works)')
   
@check_exceptions(GenericExceptionHandler)
def raise_custom_exception_with_generic_handler():
    raise CustomException('Custom Exception and Generic Handler (caught Simple Exception)')

def no_exception_handling():
    raise Exception('Not handled Exception')
   
if __name__ == '__main__':
    raise_generic_exception()
    raise_custom_exception()
    raise_generic_exception_with_custom_handler()
    raise_custom_exception_with_generic_handler()
    no_exception_handling()

[/codice]

da notare un paio di cose:
prima di tutto e` importante notare che la classe GenericExceptionHandler ha un except anche per Exception. Questo di solito e` sempre buona norma farlo per la gestione di eccezioni generiche.
Di contro pero`, evitare (come la peste) l'uso di except generici senza specificare alcun tipo di eccezione.
Non e` un buon idioma e si presta alla gestione di qualunque (troppe) situazione.

Ancora sulla gestione delle eccezione: le ultime due funzioni decorate invertono l'handler con il tipo di Eccezione lanciata.
Se nel primo caso (Handler Specializzato e Eccezione Generica), l'eccezione Generica viene catturata - provando di fatto l'effettivo funzionamento dei try innestati; nel secondo caso (Handler Generico ed Eccezione Specializzata)
interviene l'except sul tipo *Exception* descritto prima che salva capra e cavoli!

Infine, la seconda cosa che vorrei evidenziare e` che gli handler, in quanto classi Callable, potevano tranquillamente essere loro stessi eletti a decoratori (modificati in modo che nel metodo costruttore memorizzassero il riferimento alla funzione decorata, esattamente come fatto dalla classe Dispatcher nel decoratore).
Non l'ho fatto, in quanto avrebbero funzionato (as-is) solo nel caso di funzioni ma non per la decorazione di metodi - invito tutte le nuove leve a capire il perche` !

Spero di essermi riuscito a spiegare!
Btw, questa e` solo una idea estemporanea, ma credo esponga il fianco ad ogni possibile margine di miglioramento! :)

Navigazione

[0] Indice dei post

[*] Pagina precedente

Vai alla versione completa