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
_______________________________________________
Python mailing list
Python@lists.python.it
http://lists.python.it/mailman/listinfo/python

Rispondere a