Archivi tag: closures

Python decorator

Python è un linguaggio che deve gran parte del suo successo alla chiarezza ed alla semplicità. Eppure, come spesso accade, sono i suoi lati oscuri ad affascinare i programmatori.

Questo è il caso dei decorator. Si trovano sulla rete un sacco di articoli che ne parlano un gran bene decantandone l’utilità e la semplicità. Non metto in discussione la loro utilità, ma nutro qualche dubbio sulla loro chiarezza. Per i miei gusti il loro comportamento è un po’ troppo “magico”. Quando incontro qualche costrutto il cui funzionamento va in qualche modo interpretato nel contesto mi metto in allarme.
A suo tempo Java fu considerato un progresso rispetto al C nella semplicità per la rinuncia alla gestione esplicita dei puntatori.
Ma mi è capitato varie volte di vedere dei programmatori Java (e non solo) alle prime armi combinare dei pasticci perché non si rendono conto che l’array o l’object che modificano dentro un metodo, è, al netto del nome, proprio lo stesso che è passato come parametro. Nel vecchio Pascal si doveva esplicitare se un parametro era passato come valore o come variabile, forse era una pedanteria e forse i puntatori del C sono scomodi, ma i comportamenti nascosti sono pericolosi.
Una descrizione molto informale dei decorator potrebbe essere:
Un decoratore è una funzione che avvolge un’altra funzione e ne può manipolare i parametri ed il risultato.
Volendo schematizzare per una funzione func.

func_decorator(func)
 qui si può agire sui parametri (args)
 ret=func(args)
 qui si può agire sul risultato (ret)
 ritorna ret modificato

È evidente che con una simile descrizione sia ha una comprensione intuitiva del funzionamento dei decorator, probabilmente sufficiente ad utilizzarli quando si trovano in qualche libreria, ma si ignora il reale meccanismo del passaggio della funzione e degli argomenti. Per sfruttarli al meglio i decorator ed evitare di cadere in abusi è opportuna una conoscenza più approfondita. L’unico modo, come sempre, è quello di implementare qualche esempio.
Per focalizzare tutta l’attenzione sulla costruzione dei decoratori ho scelto per gli esempi delle funzioni banali. Questo, in prima istanza, non aiuterà a comprendere l’utilità dei decorator ma semplifica la comprensione del loro funzionamento. Solo se si sa implementare senza sforzo un decorator si è in grado di capire fino in fondo la loro utilità e di utilizzare correttamente quelli che si incontrano nelle api delle librerie e nei framework.

es.1

def add_decorate(x, y, a, b):
    print('args : %s %s' % (x, y))
    xb = x * a
    yb = y * a
    print('args update : %s %s' % (xb, yb))

    r = add(xb, yb)

    print('response :%s' % (r))
    ra = r / b
    print('response update :%s' % (ra))

    return ra

r = add(40, 10)
print('add(40,10) =>  %s' % (r))

r = add_decorate(40, 10, 10, 2)
print('add_decorate(40,10,10,2)  =>  %s' % (r))

Output:

add(40,10) =>  50
args : 40 10
args update : 400 100
response :500
response update :250
add_decorate(40,10,10,2)  =>  250

Nell’esempio es.1 tutto è molto semplice e il decorator di fatto non è che un’organizzazione particolare del codice. Si è implementata una funzione add_decorate() che utilizza al suo interno la funzione add().
Ora cerchiamo di generalizzare.

es.2

def add(x, y):
    return x + y


def sub(x, y):
    return x - y


def mult(x, y):
    return x * y


def div(x, y):
    return x / y


def op_decorate(func):

    def wrapper(x, y):
        a = 10
        b = 2
        x_dec = x * a
        y_dec = y * a

        response = func(x_dec, y_dec)

        response_dec = response / b
        return response_dec

    return wrapper


r = add(40, 10)
print('add (40,10)=>  %s' % (r))

r = op_decorate(add)(40, 10)
print('op_decorate(add)(40,10) =>  %s' % (r))

add_dec = op_decorate(add)
r = add_dec(40, 10)
print('add_dec(40,10) =>  %s' % (r))

print('----')

r = sub(40, 10)
print('sub (40,10)=>  %s' % (r))

r = op_decorate(sub)(40, 10)
print('op_decorate(sub)(40,10) =>  %s' % (r))

sub_dec = op_decorate(sub)
r = sub_dec(40, 10)
print('sub_dec(40,10) =>  %s' % (r))

print('----')

r = mult(40, 10)
print('mult (40,10)=>  %s' % (r))

r = op_decorate(mult)(40, 10)
print('op_decorate(mult)(40,10) =>  %s' % (r))

mult_dec = op_decorate(mult)
r = mult_dec(40, 10)
print('mult_dec(40,10) =>  %s' % (r))

print('----')

r = div(40, 10)
print('div (40,10)=>  %s' % (r))

r = op_decorate(div)(40, 10)
print('op_decorate(div)(40,10) =>  %s' % (r))

div_dec = op_decorate(div)
r = div_dec(40, 10)
print('div_dec(40,10) =>  %s' % (r))

Output:

add (40,10)=>  50
op_decorate(add)(40,10) =>  250
add_dec(40,10) =>  250
----
sub (40,10)=>  30
op_decorate(sub)(40,10) =>  150
sub_dec(40,10) =>  150
----
mult (40,10)=>  400
op_decorate(mult)(40,10) =>  20000
mult_dec(40,10) =>  20000
----
div (40,10)=>  4
op_decorate(div)(40,10) =>  2
div_dec(40,10) =>  2

L’esempio es.2 è utilizzabile con funzioni diverse.
Notate che per funzionare deve utilizzare la closurejavascript-closures ) per la funzione func.
La funzione func appartiene al Context individuato da op_decorate().
La funzione op_decorate() ritorna wrapper(), ma func definita al suo esterno “continua a vivere” perché appartiene al suo Context e quindi wrapper() la può utilizzare.
es.3

def add(x, y):
    return x + y


def sub(x, y):
    return x - y


def mult(x, y):
    return x * y


def div(x, y):
    return x / y


def op_decorate_ab(a, b):

    def op_decorate(func):

        def wrapper(x, y):
            x_dec = x * a
            y_dec = y * a
            response = func(x_dec, y_dec)
            response_dec = response / b
            return response_dec

        return wrapper

    return op_decorate


r = add(40, 10)
print('add(40,10) =>  %s' % (r))

"""
op_decorate_ab(a,b) ritorna la funzione op_decorate(func)
a,b esistono ne Context
op_decorate(func) ritorna la funzione wrapps(x,y)
func esiste nel context
wrap(x,y) utilizza le variabili del Context a,b,func
"""

r = op_decorate_ab(10, 2)(add)(40, 10)
print('op_decorate_ab(10,2)(add)(40,10) =>  %s' % (r))

"""
uso esplicito della funzione decorata add_dec(40,10)
"""

add_dec = op_decorate_ab(10, 2)(add)
r = add_dec(40, 10)
print('add_dec(40,10) =>  %s' % (r))

print('----')

r = sub(40, 10)
print('sub(40,10) =>  %s' % (r))

r = op_decorate_ab(10, 2)(sub)(40, 10)
print('op_decorate_ab(10,2)(sub)(40,10) =>  %s' % (r))

sub_dec = op_decorate_ab(10, 2)(sub)
r = sub_dec(40, 10)
print('sub_dec(40,10) =>  %s' % (r))

print('----')

r = add(40, 10)
print('mult(40,10) =>  %s' % (r))

r = op_decorate_ab(10, 2)(mult)(40, 10)
print('op_decorate_ab(10,2)(mult)(40,10) =>  %s' % (r))

mult_dec = op_decorate_ab(10, 2)(mult)
r = mult_dec(40, 10)
print('mult_dec(40,10) =>  %s' % (r))

print('----')

r = div(40, 10)
print('div(40,10) =>  %s' % (r))

r = op_decorate_ab(10, 2)(div)(40, 10)
print('op_decorate_ab(10,2)(div)(40,10) =>  %s' % (r))

div_dec = op_decorate_ab(10, 2)(div)
r = div_dec(40, 10)
print('div_dec(40,10) =>  %s' % (r))

Output:

add(40,10) =>  50
op_decorate_ab(10,2)(add)(40,10) =>  250
add_dec(40,10) =>  250
----
sub(40,10) =>  30
op_decorate_ab(10,2)(sub)(40,10) =>  150
sub_dec(40,10) =>  150
----
mult(40,10) =>  50
op_decorate_ab(10,2)(mult)(40,10) =>  20000
mult_dec(40,10) =>  20000
----
div(40,10) =>  4
op_decorate_ab(10,2)(div)(40,10) =>  2
div_dec(40,10) =>  2

Nell’esempio es.3 si implementa un decorator con parametri per aumentarne la flessibilità.
Anche in questo caso si sfruttano le proprietà delle variabili di Context. Prima per i parametri x,y della funzione op_decorate_ab(x,y), poi per il parametro func di op_decorate(func).
es.4

def op_decorator(a, b):

    def op_decorate(func):

        def wrapper(x, y):
            x_dec = x * a
            y_dec = y * a

            response = func(x_dec, y_dec)

            response_dec = response / b
            return response_dec

        return wrapper

    return op_decorate


@op_decorator(10, 2)
def add(x, y):
    return x + y


@op_decorator(10, 2)
def sub(x, y):
    return x - y


@op_decorator(10, 2)
def mult(x, y):
    return x * y


@op_decorator(10, 2)
def div(x, y):
    return x / y

r = add(40, 10)
print('add(40,10) =>  %s' % (r))
print('add =>  %s ' % (add))

print('----')

r = sub(40, 10)
print('sub(40,10) =>  %s' % (r))
print('sub  =>  %s ' % (sub))

print('----')

r = mult(40, 10)
print('mult(40,10) =>  %s' % (r))
print('mult  =>  %s ' % (mult))

print('----')

r = div(40, 10)
print('div(40,10) =>  %s' % (r))
print('div  =>  %s ' % (div))

Output:

add(40,10) =>  250
add =>  function wrapper at 0x7f3f29a48848
----
sub(40,10) =>  150
sub  =>  function wrapper at 0x7f3f29a48938
----
mult(40,10) =>  20000
mult  =>  function wrapper at 0x7f3f29a48a28
----
div(40,10) =>  2
div  =>  function wrapper at 0x7f3f29a48b18

 

Finalmente nell’esempio es.4 fa la sua comparsa la notazione specifica di decorator.
Rispetto all’esempio es.3 è cambiato solo il modo di invocare la funzione.
Con la nuova notazione si modifica il comportamento della funzione decorata senza cambiarne il nome e senza modificare la funzione stessa.
Nell’esempio è stata aggiunta la stampa della funzione per evidenziare che viene sempre restituita la funzione wrapper(), questo può causare qualche problema, specialmente nel debug.
es.5

import functools

def op_decorator(a, b):

    def op_decorate(func):

        @functools.wraps(func)
        def wrapper(x, y):
            x_dec = x * a
            y_dec = y * a

            response = func(x_dec, y_dec)

            response_dec = response / b
            return response_dec

        return wrapper

    return op_decorate

Output:

add(40,10) =>  250
add  =>  function add at 0x7f371cd5da28
----
sub(40,10) =>  150
sub  =>  function sub at 0x7f371cd5db18
----
mult(40,10) =>  20000
mult  =>  function mult at 0x7f371cd5dc08
----
div(40,10) =>  2
div  =>  function div at 0x7f371cd5dcf8

Nell’esempio es.5 si risolve il problema con l’aggiunta di una semplice riga fornita dalla libreria functools. Con la modifica viene restituita la funzione decorata con il suo nome originale.
Basta decorare la funzione wrapper() con @functools.wraps(func).
Un decorator viene in aiuto per risolvere un problema nella costruzione del decorator stesso.
Un esercizio interessante sarebbe quello di capire cosa fa @functools.wraps().

 

JavaScript closures

Nella letteratura su JavaScript mi sono imbattuto varie volte nelle “closures” soprattutto in riferimento al concetto di “Scope” di una variabile e di “Context“. Ma non avevo mai approfondito più di tanto l’argomento. Poi mi sono imbattuto in un  articolo –  javascript-closure – dove si dice :

 “ritengo che esistano due tipologie di programmatori JavaScript: chi sa come usare le closure e chi no”

La cosa mi ha incuriosito, forse le ho sempre usate senza rendermene conto forse mi sono perso qualche cosa delle potenzialità di JavaScript. Nel dubbio mi sono proposto di approfondire la questione, nell’unico modo che ritengo valido per ben comprendere i concetti in informatica: scrivere esempi esplicativi. Fortunatamente al termine del mio approfondimento mi sono reso conto che le ho sempre usate senza problemi anche se probabilmente non avevo ben chiaro il loro aspetto teorico.
Quest’uso di una risorsa senza aver chiara la sua natura è qualcosa che ha a che fare con la natura di JavaScript: un linguaggio molto flessibile e libero che si presta a diversi stili di utilizzo.
Provenendo da Java ho applicato pattern tipici di questo linguaggio e in questo modo ho utilizzato in modo, per me naturale, queste misteriose “closures”.
In questo articolo cercherò di spiegare come.
Nei vari articoli sulle “closures” che ho consultato si parte sempre dalla distinzione tra “Context” e “Scope”.
Semplificando al massimo con “Scope” ci si riferisce all’ambito di validità di una variabile che può essere “locale” – definita all’interno della funzione – o globale – definita al suo esterno – . JavaScript non è molto raffinato nella gestione dei diversi ambiti di validità delle variabili. Non esistono per esempio le variabili locali a livello di blocco per non parlare di quelle di classe ( le classi semplicemente non esistono in JavaScript) . Semplificando molto si può dire che le variabili locali di una funzione vivono all’interno di ogni singola istanza della funzione stessa.
Con il “Context”  le cose si complicano; per poterne dare un esempio bisogna considerare una funzione nidificata in un altra: tutte le variabile definite all’interno della funzione contenitore e esterne alla funzione nidificata appartengono al “Context”.
Nell’esempio es1. ‘n‘ è una variabile di “Context”.

es1.

var Add = function() {
    var n = 0;
    return function() {
        n+=1;
        return n;
    };
};

var add_es1=Add();
add_es1();
add_es1();
add_es1();
add_es1();
var x=add_es1();

var add_es2=Add();
x=add_es2();
x=add_es2();

Permette di implementare un contatore senza far riferimento ad una variabile globale in quanto la  vita di ‘n‘  sopravvive per così dire alle diverse chiamate della funzione, questo è l’esempio più banale e più diffuso sull’uso delle “closures“, nel caso specifico individuano l’ambito di validità della variabile ‘n‘.
L’esempio es2. è solo un caso un poco più complesso.

es2.

var Add = function() {
    var n = 0;
    return {
        sum: function(x) {
            n += x;
        },
        get: function() {
            return n;
        }
    };
};

var add1 = Add();
add1.sum(10);
add1.sum(15);
add1.sum(50);
var x = add1.get();

var add2 = Add();
add2.sum(100);
add2.sum(1);
var x = add2.get();

Notate che ad ogni nuova istanza ( var add= Add(n); ) si crea un nuovo “Context” completamente indipendente.
Chi viene da Java può considerare la variabile ‘n‘ come una variabile d’istanza.
Credo che la difficoltà a comprendere il “Context” in JavaScript dipenda dal fatto che è un linguaggio ad oggetti senza una definizione esplicita di classe. Ma forse questo è solo un codizionamento che deriva dalle mie esperienze.
L’esempio es3. ,che usa una notazione di moda in JavaScript, serve a confondere un poco le acque e quindi a provocare lo sforzo per meglio comprendere. Sembra funzionare perfettamente, ma se eseguite l’esercizio noterete che è possibile avere una sola istanza, in altri termini si comporta come un “singleton”.

es3.

var Add = (function() {
    var n = 0;
    return  function(x) {
        n += x;
        return n;
    };
})();

var add_es1=Add;
add_es1(10);
add_es1(20);
add_es1(30);
add_es1(100);
var x=add_es1(1);

var add_es2=Add;
add_es2(100);
x=add_es2(100);

Gli esempi es4. e es5. son quasi eguali, l’unica differenza è nella definizione della variabile ‘num‘. Nel primo è definita come variabile di “Context” , quindi utilizzando le “closures“, nel secondo è definita all’interno dell’oggetto ‘op‘. Si potrebbe definire in termini intuitivi come locale rispetto all’oggetto ‘op’ e “contestuale” rispetto ai metodi  – funzioni –  definite in ‘op‘. Notate che in questo caso si deve utilizzare la variabile ‘num‘ utilizzando la parola chiave ‘this‘.

es4:

var Operator = function() {
    var num = 0;
    var op = {
        set: function(n) {
            num = n;
            return this;
        },
        get: function() {
            return num;
        },
        add: function(n) {
            num = num + n;
            return this;
        },
        mult: function(n) {
            num = num * n;
            return this;
        },
        sub: function(n) {
            num = num - n;
            return this;
        },
        div: function(n) {
            num = num / n;
            return this;
        }
    };
    return op;
};

var op_es1 = Operator();
op_es1.set(1);
op_es1.add(10).mult(20);
var x = op_es1.get();

var op_es2 = Operator();
op_es2.set(1000);
op_es2.sub(300);
op_es2.div(10);
x = op_es2.get();

es5.

var Operator = function() {
    var op = {
        num:0,
        set: function(n) {
            this.num = n;
            return this;
        },
        get: function() {
            return this.num;
        },
        add: function(n) {
            this.num = this.num + n;
            return this;
        },
        mult: function(n) {
            this.num = this.num * n;
            return this;
        },
        sub: function(n) {
           this.num = this.num - n;
            return this;
        },
        div: function(n) {
            this.num = this.num / n;
            return this;
        }
    };
    return op;
};

var op_es1 = Operator();
op_es1.set(1);
op_es1.add(10).mult(20);
var x = op_es1.get();

var op_es2 = Operator();
op_es2.set(1000);
op_es2.sub(300);
op_es2.div(10);
x = op_es2.get();

La scelta fra le due soluzioni in questo caso è una questione di gusti, ma si possono presentare delle soluzioni nelle quali utilizzare una tecnica o l’altra permette di avere codice più leggibile. Concludendo si può dire che, al di là della diversa notazione, le “closures” permettono di definire delle variabile che possono considerarsi come le variabili d’ istanza di object in Java ed altri linguaggi ad oggetti (python, c++, ruby, ..).
Questa non è certo la spiegazione più rigorosa delle “closures” ma in un linguaggio “libero” come JavaScript forse la cosa più saggia è costruirsi degli schemi  di organizzazione del codice sulla base delle proprie esperienze con linguaggi più “disciplinati”.

Per provare gli esempi (uaclosure_es.tar.gz) è disponibile un semplice server.py; è necessario solo con IE (restituisce errore 200 quando la response alle request AJAX è un file locale) per tutti gli altri è sufficiente caricare il file index.html.