[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