2015-01-06 24 views
9

Una domanda generica per qualcuno che conosce meglio le definizioni interne della funzione.Definizioni delle definizioni di funzioni Python in linea

In generale, c'è un compromesso prestazioni fuori a fare qualcosa di simile:

def my_function(): 
    def other_function(): 
     pass 

    # do some stuff 
    other_function() 

Versus: gli sviluppatori

def other_function(): 
    pass 

def my_function(): 
    # do some stuff 
    other_function() 

che ho visto le funzioni inline prima di tenere un piccolo, uso singolo funziona vicino al codice che effettivamente lo usa, ma mi sono sempre chiesto se ci fosse una penalità di prestazioni in memoria (o calcolo) per fare qualcosa del genere.

Pensieri?

+0

Che cosa mostra il tuo profilo? https://docs.python.org/3/library/profile.html –

+1

generalmente uso le funzioni inline quando voglio una chiusura. li uso anche quando la funzione inline non ha utilità al di fuori della sua funzione di chiusura. – acushner

+0

@tristan, ero meno interessato alle prestazioni di calcolo, più interni di memoria immagino? Ma entrambi sarebbero interessanti da sapere. –

risposta

7

La divisione di funzioni più grandi in funzioni più leggibili e più piccole fa parte della scrittura del codice Pythonic: dovrebbe essere ovvio ciò che si sta tentando di eseguire e le funzioni più piccole sono più facili da leggere, controllare errori, manutenzione e riutilizzo.

Come sempre, le domande "che ha prestazioni migliori" dovrebbero sempre essere risolte da profiling the code, vale a dire che spesso dipende dalle firme dei metodi e da cosa sta facendo il codice.

ad es. se stai passando un dizionario di grandi dimensioni a una funzione separata invece di fare riferimento a un frame locale, ti ritroverai con caratteristiche di performance diverse rispetto a chiamare una funzione void da un'altra.

Ad esempio, ecco alcuni comportamenti banale:

import profile 
import dis 

def callee(): 
    for x in range(10000): 
     x += x 
    print("let's have some tea now") 

def caller(): 
    callee() 


profile.run('caller()') 

let's have some tea now 
     26 function calls in 0.002 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     2 0.000 0.000 0.000 0.000 :0(decode) 
     2 0.000 0.000 0.000 0.000 :0(getpid) 
     2 0.000 0.000 0.000 0.000 :0(isinstance) 
     1 0.000 0.000 0.000 0.000 :0(range) 
     1 0.000 0.000 0.000 0.000 :0(setprofile) 
     2 0.000 0.000 0.000 0.000 :0(time) 
     2 0.000 0.000 0.000 0.000 :0(utf_8_decode) 
     2 0.000 0.000 0.000 0.000 :0(write) 
     1 0.002 0.002 0.002 0.002 <ipython-input-3-98c87a49b247>:4(callee) 
     1 0.000 0.000 0.002 0.002 <ipython-input-3-98c87a49b247>:9(caller) 
     1 0.000 0.000 0.002 0.002 <string>:1(<module>) 
     2 0.000 0.000 0.000 0.000 iostream.py:196(write) 
     2 0.000 0.000 0.000 0.000 iostream.py:86(_is_master_process) 
     2 0.000 0.000 0.000 0.000 iostream.py:95(_check_mp_mode) 
     1 0.000 0.000 0.002 0.002 profile:0(caller()) 
     0 0.000    0.000   profile:0(profiler) 
     2 0.000 0.000 0.000 0.000 utf_8.py:15(decode) 

vs.

import profile 
import dis 

def all_in_one(): 
    def passer(): 
     pass 
    passer() 
    for x in range(10000): 
     x += x 
    print("let's have some tea now")  

let's have some tea now 
     26 function calls in 0.002 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     2 0.000 0.000 0.000 0.000 :0(decode) 
     2 0.000 0.000 0.000 0.000 :0(getpid) 
     2 0.000 0.000 0.000 0.000 :0(isinstance) 
     1 0.000 0.000 0.000 0.000 :0(range) 
     1 0.000 0.000 0.000 0.000 :0(setprofile) 
     2 0.000 0.000 0.000 0.000 :0(time) 
     2 0.000 0.000 0.000 0.000 :0(utf_8_decode) 
     2 0.000 0.000 0.000 0.000 :0(write) 
     1 0.002 0.002 0.002 0.002 <ipython-input-3-98c87a49b247>:4(callee) 
     1 0.000 0.000 0.002 0.002 <ipython-input-3-98c87a49b247>:9(caller) 
     1 0.000 0.000 0.002 0.002 <string>:1(<module>) 
     2 0.000 0.000 0.000 0.000 iostream.py:196(write) 
     2 0.000 0.000 0.000 0.000 iostream.py:86(_is_master_process) 
     2 0.000 0.000 0.000 0.000 iostream.py:95(_check_mp_mode) 
     1 0.000 0.000 0.002 0.002 profile:0(caller()) 
     0 0.000    0.000   profile:0(profiler) 
     2 0.000 0.000 0.000 0.000 utf_8.py:15(decode) 

I due utilizzare lo stesso numbe r delle chiamate di funzione e non vi è alcuna differenza di prestazioni, il che conferma la mia affermazione che è davvero importante testare in circostanze specifiche.

Si può vedere che ho un'importazione inutilizzata per il modulo disassembly. Questo è un altro utile modulo che ti permetterà di vedere cosa sta facendo il tuo codice (prova dis.dis(my_function)).Pubblicheremmo un profilo del testcode che ho generato, ma ti mostrerei solo altri dettagli che non sono rilevanti per risolvere il problema o per sapere cosa sta realmente accadendo nel tuo codice.

+0

Sono d'accordo con i tempi, ma è strano che tu abbia usato 'profile.run (f)' invece di, diciamo, 'min (timeit.Timer (f) .repeat (10, 200))'. 'profile' è bravo a dirvi approssimativamente quali sono le parti costose di un programma, ma non è così bravo a dirvi quanto velocemente il programma è dovuto al sovraccarico. – Veedrac

+0

@Veedrac Nell'esempio banale dato, è più importante vedere cosa sta succedendo piuttosto che sapere quanto velocemente una funzione non utile viene eseguita più volte. Vale a dire, conoscere la differenza di runtime tra queste due funzioni non è importante. Sapere come andare a capire perché un metodo è più veloce per un codice più realistico è importante. –

+0

Non sono sicuro di seguirlo; cosa intendo per imparare dalla traccia? – Veedrac

5

Utilizzando timeit sul mio Mac sembra favorire la definizione della funzione a livello di modulo (un po '), e ovviamente i risultati possono variare da un computer all'altro ...:

>>> import timeit 
>>> def fun1(): 
... def foo(): 
...  pass 
... foo() 
... 
>>> def bar(): 
... pass 
... 
>>> def fun2(): 
... bar() 
... 
>>> timeit.timeit('fun1()', 'from __main__ import fun1') 
0.2706329822540283 
>>> timeit.timeit('fun2()', 'from __main__ import fun2') 
0.23086285591125488 

Si noti che questa differenza è piccolo (~ 10%) quindi in realtà non farà una grande differenza nel runtime del tuo programma, a meno che non si tratti di un ciclo molto stretto.

Il motivo più frequente per definire una funzione all'interno di un altro è quello di raccogliere le variabili locali della funzione fuori in una chiusura. Se non hai bisogno di una chiusura, dovresti scegliere la variante più semplice da leggere. (La mia preferenza è quasi sempre quella di mettere la funzione a livello di modulo).

+0

Hai provato l'ottimizzazione usando 'Python -O'? Questo sarà un confronto più equo, poiché le funzioni potrebbero non essere considerate in linea a meno che non sia in modalità di ottimizzazione del compilatore. –

+0

L'ho provato usando l'ottimizzazione e il risultato è lo stesso di quello che hai segnalato. –

Problemi correlati