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.
fonte
2015-01-06 19:02:51
Che cosa mostra il tuo profilo? https://docs.python.org/3/library/profile.html –
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
@tristan, ero meno interessato alle prestazioni di calcolo, più interni di memoria immagino? Ma entrambi sarebbero interessanti da sapere. –