2010-03-09 17 views
9

ho incontrato il seguente piccolo dilemma fastidioso più e più volte in Python:Python: Quanto costa creare molte volte un piccolo elenco?

Opzione 1: (?)

più pulito ma più lento, se chiamato molte volte da a_list ottenere ri-creato per ogni chiamata di fai_qualcosa()

def do_something():  
    a_list = ["any", "think", "whatever"]  
    # read something from a_list 

Opzione 2:

più brutto ma più efficiente (risparmia la creazione a_list tutto da capo)

a_list = ["any", "think", "whatever"]  
def do_something():  
    # read something from a_list 

Cosa ne pensi?

+8

in caso di dubbio, procurati il ​​codice più leggibile, elegante e autodocumentante che puoi fare fino a quando un profiler delle prestazioni non ti dice di farlo. –

+0

Sì, lo so. Ma questa è una cosa così piccola, fastidiosa e facile da evitare ... – GabiMe

+4

"small" significa ignorarlo. Fai quello che è più chiaro. Lascia da parte le considerazioni sulle prestazioni finché non puoi * dimostrare * che è un problema. –

risposta

16

Cosa c'è di brutto?

I contenuti dell'elenco sono sempre costanti, come nell'esempio? Se è così: le versioni recenti di Python (dal 2.4) la ottimizzeranno valutando l'espressione costante e mantenendo il risultato ma solo se si tratta di una tupla. Quindi potresti trasformarlo in una tupla.O potresti smettere di preoccuparti di piccole cose del genere.

Ecco un elenco di costanti e una tupla delle costanti:

>>> def afunc(): 
... a = ['foo', 'bar', 'zot'] 
... b = ('oof', 'rab', 'toz') 
... return 
... 
>>> import dis; dis.dis(afunc) 
    2   0 LOAD_CONST    1 ('foo') 
       3 LOAD_CONST    2 ('bar') 
       6 LOAD_CONST    3 ('zot') 
       9 BUILD_LIST    3 
      12 STORE_FAST    0 (a) 

    3   15 LOAD_CONST    7 (('oof', 'rab', 'toz')) 
      18 STORE_FAST    1 (b) 

    4   21 LOAD_CONST    0 (None) 
      24 RETURN_VALUE 
>>> 
+1

Davvero? Non lo sapevo. Quelle tuple vengono ottimizzate per essere create una sola volta. Interessante .. Questo probabilmente risolverebbe il 90% dei casi dell'OP – GabiMe

+0

@AnonymousDriveByDownVoter: ti interessa lasciare un commento? –

4

Non creare mai più di una volta se non è necessario. Questa è una semplice ottimizzazione che può essere fatta da parte tua e personalmente non trovo affatto il secondo esempio brutto.

Alcuni potrebbero obiettare di non preoccuparsi dell'ottimizzazione di piccole cose come questa, ma ritengo che qualcosa di così semplice da risolvere debba essere fatto immediatamente. Mi dispiacerebbe vedere la tua applicazione creare copie multiple di tutto ciò che non è necessario semplicemente per preservare un senso arbitrario di "bellezza del codice". :)

3

se il tuo a_list non cambia, spostarlo fuori dalla funzione.

0

Se l'elenco non viene mai modificato, perché si utilizzano gli elenchi?

Senza conoscere i requisiti effettivi, consiglierei di utilizzare semplicemente alcune istruzioni if ​​per eliminare completamente la parte e la parte "leggi qualcosa dalla lista".

4

Opzione 3:

def do_something(a_list = ("any", "think", "whatever")): 
    read something from a_list 

Opzione 3 rispetto al Opzione 1:

entrambi sono ugualmente leggibili a mio parere (anche se alcuni sembrano pensare in modo diverso nei commenti :-)!). Potresti anche scrivere l'opzione 3 come questa

def do_something(
    a_list = ("any", "think", "whatever")): 
    read something from a_list 

che minimizza davvero la differenza in termini di leggibilità. A differenza dell'opzione 1, tuttavia, l'opzione 3 definisce a_list una sola volta, nel momento in cui è definito do_something. Questo è esattamente quello che vogliamo.

Opzione 3 rispetto al Opzione 2:

Evitare le variabili globali, se possibile. L'opzione 3 ti consente di farlo. Inoltre, con l'Opzione 2, nel tempo o se altre persone mantengono questo codice, la definizione di a_list potrebbe essere separata da def do_something. Questo potrebbe non essere un grosso problema, ma penso che sia in qualche modo indesiderabile.

+3

Gneee ... Siamo spiacenti, vedere un elenco come un parametro predefinito fa male. –

+1

-1 "Evita le variabili globali quando puoi" è un buon consiglio, ma "Python è forzato [d] per prima cosa cercare la variabile nei locals() dict" è completamente codswallop. Il COMPILATORE sa se è locale o globale e nel caso opzione 2 emette un'istruzione GLOBAL CARICO. La parola locale() si manifesta quando e solo quando si chiama la funzione locals() - il COMPILATORE sa dove sono memorizzati tutti i locali; non viene usato dict per eseguire il normale codice funzione/metodo. –

1

Beh, sembra si tratta di inizializzare l'array nella funzione o meno:

import time 
def fun1(): 
     a = ['any', 'think', 'whatever'] 
     sum = 0 
     for i in range(100): 
       sum += i 

def fun2(): 
     sum = 0 
     for i in range(100): 
       sum += i 


def test_fun(fun, times): 
     start = time.time() 
     for i in range(times): 
       fun() 
     end=time.time() 
     print "Function took %s" % (end-start) 

# Test 
print 'warming up' 
test_fun(fun1, 100) 
test_fun(fun2, 100) 

print 'Testing fun1' 
test_fun(fun1, 100000) 
print 'Testing fun2' 
test_fun(fun2, 100000) 

print 'Again' 
print 'Testing fun1' 
test_fun(fun1, 100000) 
print 'Testing fun2' 
test_fun(fun2, 100000) 

ei risultati:

>python test.py 
warming up 
Function took 0.000604152679443 
Function took 0.000600814819336 
Testing fun1 
Function took 0.597407817841 
Testing fun2 
Function took 0.580779075623 
Again 
Testing fun1 
Function took 0.595198154449 
Testing fun2 
Function took 0.580571889877 

Sembra che non ci siano differenze.

+1

L'uso del modulo 'timeit' sarebbe più appropriato ... – ChristopheD

+1

Potrebbe essere d'aiuto se si chiama do_fun1() da qualche parte. ;-) –

+0

Annidarlo in una funzione è qualcos'altro.Provalo nelle condizioni che stiamo discutendo: (1) locale (2) globale (3) predefinito (4) locale ma come tupla invece di una lista. Anche una parte importante del tempo viene spesa per il ciclo for che è irrilevante. Getta via il ciclo e getta in un'opzione (0) nessuna lista/tupla, quindi riporta (1) - (0), (2) - (0), ecc. –

0

Ho lavorato su sistemi automatizzati che elaborano oltre 100.000.000 di dischi al giorno, in cui un miglioramento delle prestazioni dell'1% è enorme.

Ho imparato una grande lezione lavorando su quel sistema: Più veloce è meglio, ma solo quando sai quando è abbastanza veloce.

Un miglioramento dell'1% sarebbe stata un'enorme riduzione del tempo di elaborazione totale, ma non è sufficiente per effettuare quando avremmo bisogno del nostro prossimo upgrade dell'hardware. La mia domanda è stata così veloce, che la quantità di tempo che ho speso provando a mungere l'ultimo 1% probabilmente costa più di un nuovo server.

Nel tuo caso, dovresti chiamare do_qualcosa decine di migliaia di volte prima di fare una differenza significativa nelle prestazioni. In alcuni casi ciò farebbe la differenza, in altri non lo farà.

2
  1. avete un po 'di dati
  2. Hai un metodo ad esso associati
  3. Se non si desidera conservare i dati a livello globale solo per il gusto di ottimizzare la velocità del metodo a meno che non si deve.

Penso che questo sia ciò per cui sono le classi.

class Processor: 
    def __init__(this): 
     this.data = "any thing whatever".split() 
    def fun(this,arg): 
     # do stuff with arg and list 

inst = Processor() 
inst.fun("skippy) 

Inoltre, se si desidera un giorno separare i dati in un file, si può semplicemente modificare il costruttore di farlo.

Problemi correlati