17 settembre, 2019

Visibilità (scope) delle variabili - 1


Continuo da qui la serie di post elementari sui linguaggi con visibilità delle variabili lessicale e/o dinamica. Però ormai parlo di un argomento collegato, più personale, probabilmente interessa solo me.

Ah! una cosa in parte OT: mi sono letto i documenti citati in bibliografia da Ming-Ho (non tutti, confesso), avendo tempo ci sono cose interessanti, qualcuna la sapevo, altre meno. Ma il solito problema: il tempo.

Questo post arriva in cascata alla serie ma è diverso, forse sono solo io che ho fatto confusione, voglio chiarirmi un po' meglio come Python e JavaScript considerano le variabili, globali e locali. Un post molto semplice, didascalico, solo per me (probabilmente, come già detto).

Normalmente le variabili (numeriche, vedremo prossimamente che per gli oggetti valgono altre leggi) sono passate come valore alle funzioni e nel chiamante il loro valore non muta. Esempio, in Python:

def f():
    print('f()', x)
    return x

def g(n):
    x = n
    print('g()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())
x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())


$ py3 s-n.py
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5
$


Stesso comportamento con JavaScript con l'avvertenza di definite con var le variabili locali, altrimenti sono globali:

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    var x = n
    console.log('g()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())


$ node s-n.js
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5
$


OK, finora tutte le variabili sono locali (in realtà c'è la sola x in g()); Per operare con variabili globali in Python uso l'instruzione global globalmente, cioè esterna alle funzioni. Ma in questo caso il comportamento è diverso da come potrebbe apparire (è un errore!): la x in g() è locale:

global x

def f():
    print('f()', x)
    return x

def g(n):
    x = n
    print('g()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())
x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())


$ py3 s-ge.py
start x = 1
f() 1
f() 1
g() 2 1
f() 1
f() 1
1 1 1 1 1
cambio x = 5
f() 5
f() 5
g() 3 5
f() 5
f() 5
5 5 5 5 5

$

Risulta che il valore di x in g() non ha influenza su f() perché questa è globale e non relativa a g().

Per chiarire uno script ancora più focalizzato

global x
x = 1

def g(n):
    print(x) # <- errore qui, x non è definita
    x = n
    print(x)

#main
g(2)
print(x)


eseguendola si ottiene un errore:

$ py3 gl.py
Traceback (most recent call last):
  File "gl.py", line 10, in <module>
    g(2)
  File "gl.py", line 5, in g
    print(x)
UnboundLocalError: local variable 'x' referenced before assignment
$


commentando la print iniziale in g() risulta come in g() x sia locale:

$ py3 gl.py
2
1
$

In JavaScript, dove le variabili sono globali di default:

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    x = n // modif qui, x è globale
    console.log('g()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())


$ node s-ge.js
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3

$

OOPS! in questo caso la x in g() determina il valore di f(). Ho cioè un comportamento diverso per i due linguaggi, cosa che almeno per me induce errori se non si presta la dovuta attenzione. (Sì, sono ripetitivo).

Da sempre si usano variabili globali ma per quanto è possibile queste vanno limitate per quanto possibile. La variabile x in g() che voglio globale la dichiaro global all'interno della funzione; la chiamata a g() sarà diversa a quella ad h() dove x è locale. Peraltro diventa importante l'ordine delle chiamate:

def f():
    print('f()', x)
    return x

def g(n):
    global x
    x = n
    print('g()', n, f())
    return f()

def h(n):
    x = n
    print('h()', n, f())
    return f()

# main
x = 1
print('start x =', x)
print(x, f(), g(2), x, f())

x = 5
print('cambio x =', x)
print(x, f(), g(3), x, f())

x = 8
print('cambio x =', x)
print(x, f(), h(4), g(3), x, h(6), f())


$ py3 s-gl.py
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3
cambio x = 8
f() 8
f() 8
h() 4 8
f() 8
f() 3
g() 3 3
f() 3
f() 3
h() 6 3
f() 3
f() 3
8 8 8 3 3 3 3

$
JavaScript, per quanto precedentemente detto è in questo caso più intuitivo

function f() {
    console.log('f()', x)
    return x
}

function g(n) {
    x = n // modif qui, x è globale
    console.log('g()', n, f())
    return f()
}

function h(n) {
    var x = n // x è locale
    console.log('h()', n, f())
    return f()
}

// main
x = 1
console.log('start x =', x)
console.log(x, f(), g(2), x, f())
x = 5
console.log('cambio x =', x)
console.log(x, f(), g(3), x, f())
x = 8
console.log('cambio x =', x)
console.log(x, f(), h(4), g(3), x, h(6), f())


$ node s-gl.js
start x = 1
f() 1
f() 2
g() 2 2
f() 2
f() 2
1 1 2 2 2
cambio x = 5
f() 5
f() 3
g() 3 3
f() 3
f() 3
5 5 3 3 3
cambio x = 8
f() 8
f() 8
h() 4 8
f() 8
f() 3
g() 3 3
f() 3
f() 3
h() 6 3
f() 3
f() 3
8 8 8 3 3 3 3
$


OK, ma le liste? Uh! per le liste (gli oggetti) questo non vale, come vedremo prossimament, non cambiate canale 🙂
🔴

Nessun commento:

Posta un commento