[Python] Annidamento di funzioni
Daniele Varrazzo
piro a develer.com
Gio 21 Mar 2013 22:31:11 CET
On 2013-03-21 19:16, Matteo Boscolo wrote:
> * quante volte posso annidare una funzione dentro un altra prima che
> python si incazzi ?
Nell'ordine della dimensione dello stack direi, per cui tu ti
scoccierai molto prima di quando l'interprete perderà la pazienza.
> * c'è qualche problema di performance nell'annidare le funzioni in
> questo modo ?
No: in realtà le funzioni vengono compilate quando il modulo è
importato, insieme a quelle esterne, e non quando la funzione interna
viene chiamata: la loro compilazione è statica. Quello che fa il "def" è
solo creare una "chiusura", ovvero associare l'oggetto di codice al
valore delle variabili non-locali. Un po' di curiosità nell'interprete
interattivo aiuta a capire:
In [2]: def f(x):
a = 10
def g(y):
return x + y + a
return g
...:
In [13]: f.func_code.co_consts
Out[13]:
(None,
10,
<code object g at 0xb6b3ef98, file
"<ipython-input-2-880615467131>", line 3>)
L'oggetto di codice g è una costante della funzione f, già compilata.
In [3]: f3 = f(3)
In [18]: f3(1)
Out[18]: 14
In [4]: f3.func_closure
Out[4]:
(<cell at 0x983ad64: int object at 0x953d098>,
<cell at 0x983af5c: int object at 0x953d044>)
In [9]: f3.func_closure[0].cell_contents
Out[9]: 3
In [10]: f3.func_closure[1].cell_contents
Out[10]: 10
La funzione f è stata decorata con una "chiusura", che contiene i
valori delle variabili non locali
In [20]: import dis
In [22]: dis.dis(f3)
4 0 LOAD_DEREF 0 (x)
3 LOAD_FAST 0 (y)
6 BINARY_ADD
7 LOAD_DEREF 1 (a)
10 BINARY_ADD
11 RETURN_VALUE
Le variabili della chiusura vengono lette con un opcode che ha solo un
argomento posizionale: accedervi è veloce quanto accedere alle variabili
locali (ovvero più veloce che alle variabili globali, che richiedono un
lookup di dizionario)
In [14]: g5 = f(5)
In [17]: g5.func_code is f.func_code.co_consts[2]
Out[17]: True
Il codice della funzione restituita è proprio quello della funzione
compilata.
Ed ecco il lavoro che fa la funzione esterna: non compila niente, crea
solo la chiusura:
In [3]: dis.dis(f)
2 0 LOAD_CONST 1 (10)
3 STORE_DEREF 0 (a)
3 6 LOAD_CLOSURE 1 (x)
9 LOAD_CLOSURE 0 (a)
12 BUILD_TUPLE 2
15 LOAD_CONST 2 (<code object g at
0xb6ab1848, file "<ipython-input-1-880615467131>", line 3>)
18 MAKE_CLOSURE 0
21 STORE_FAST 1 (g)
5 24 LOAD_FAST 1 (g)
27 RETURN_VALUE
> * c'è un modo alternativo di implementare sta roba ottenendo il
> comportamento dello scope di python.
Usare classi e oggetti, facendoti le chiusure da te.
La programmazione a oggetti è un modo inferiore di fare la stessa cosa:
associare uno stato a del codice. La sto buttando un po' trollosa, ma è
così: i linguaggi funzionali non hanno mai sentito la mancanza degli
oggetti. È stato il C, che non avendo un garbage collector, ha avuto
bisogno di mettere puntatori a funzioni dentro una struttura, creando
l'implementazione degli oggetti che è stata formalizzata col C++. Java
invece, avendo un GC, non aveva nessun motivo per non introdurre le
chiusure dal giorno zero; invece hanno preferito perdere 20 anni dietro
un design che era alla moda quel mercoledì. Chi pensa che il lisp sia
inferiore perché non ha le classi non c'ha capito niente, e come al
solito chi non conosce il lisp è destinato a reinventarlo (vedi Java 7).
--
Daniele Varrazzo - Develer S.r.l.
http://www.develer.com
Maggiori informazioni sulla lista
Python