2012-01-08 12 views
5

Io sostengo un progetto che ha una definizione di funzione simile a questo:La variabile `.0` inaccessibile in` locals() `influisce sulla memoria o sulle prestazioni?

def f(a, (b1, b2), c): 
    print locals() 

Durante il debug del codice ho scoperto che una chiave .1 apparso in locals(), con il valore (b1, b2). Un rapido controllo ha rivelato che una definizione di funzione come la seguente:

def f((a1, a2)): 
    print locals() 

avrà una chiave .0 in locals() con il valore (a1, a2). Sono rimasto sorpreso da questo comportamento, ma non sono riuscito a trovare alcuna informazione nella documentazione di Python.

Le mie domande sono: queste variabili posizionali altrimenti inaccessibili influenzano la memoria o le prestazioni? Sono documentati ovunque? Quale è il loro scopo?

Il progetto in questione è feedparser, che è basato su SAX e potrebbe potenzialmente contenere dozzine o centinaia di chiamate di funzione che potrebbero essere interessate da questo comportamento.

+4

Questo stile di disfaupazione automatica della tupla negli argomenti è stato esplicitamente rimosso in Python 3 e, se ricordo correttamente, a quel tempo era descritto come un incidente della grammatica. Non è certamente uno stile che mi piace vedere. –

+0

Buono a sapersi! Hai ragione, lo strumento '2to3' sostituisce automaticamente la sintassi' (b1, b2) 'con il nome della variabile' xxx_todo_changeme'. Tuttavia, hai un link che potrebbe aiutare a spiegare questo comportamento o un link riguardante la rimozione esplicita? –

+3

@ Kurt McKee [PEP 3113 - Rimozione del disimpegno dei parametri della tupla] (http://www.python.org/dev/peps/pep-3113/). –

risposta

5

quindi pep 3113, come indicato da Artur Gaspar, contiene una risposta completa. Elenca anche un sacco di motivi per cui questo probabilmente non è un buon modello da seguire. Uno di questi hai scoperto nei fastidiosi effetti collaterali del debugging. Uno più grande è che penso che il tuo codice interromperà la transizione a python3, ma non sono sicuro/sono ancora sul 2.7 personalmente.

Volevo giocare con quello che succede. Guardando come alcuni bytecode smontati possiamo vedere cosa succede con queste tre funzioni (spoiler: foo e bar hanno bytecode identici):

from dis import dis 

def foo(a, (b, c) ,d): 
    return a + b + c + d 

def bar(a, b_c, d): 
    b, c = b_c 
    return a + b + c + d 

def baz(a, b, c, d): 
    return a + b + c + d 

print '\nfoo:' 
dis(foo) 
print '\nbar:' 
dis(bar) 
print '\nbaz:' 
dis(baz) 

Resa:

foo: 
    3   0 LOAD_FAST    1 (.1) 
       3 UNPACK_SEQUENCE   2 
       6 STORE_FAST    3 (b) 
       9 STORE_FAST    4 (c) 

    4   12 LOAD_FAST    0 (a) 
      15 LOAD_FAST    3 (b) 
      18 BINARY_ADD   
      19 LOAD_FAST    4 (c) 
      22 BINARY_ADD   
      23 LOAD_FAST    2 (d) 
      26 BINARY_ADD   
      27 RETURN_VALUE   


bar: 
    7   0 LOAD_FAST    1 (b_c) 
       3 UNPACK_SEQUENCE   2 
       6 STORE_FAST    3 (b) 
       9 STORE_FAST    4 (c) 

    8   12 LOAD_FAST    0 (a) 
      15 LOAD_FAST    3 (b) 
      18 BINARY_ADD   
      19 LOAD_FAST    4 (c) 
      22 BINARY_ADD   
      23 LOAD_FAST    2 (d) 
      26 BINARY_ADD   
      27 RETURN_VALUE   


baz: 
11   0 LOAD_FAST    0 (a) 
       3 LOAD_FAST    1 (b) 
       6 BINARY_ADD   
       7 LOAD_FAST    2 (c) 
      10 BINARY_ADD   
      11 LOAD_FAST    3 (d) 
      14 BINARY_ADD   
      15 RETURN_VALUE   

Come si può vedere. foo e bar sono identici, mentre baz salta la decompressione. Quindi sì, questo influenzerà un po 'le prestazioni, ma solo fino a quando tuple prende la spacchettizzazione, che dovrebbe essere trascurabile in tutto tranne le funzioni e gli esempi di giocattolo molto piccoli (come questo; P)

+0

Non ho mai usato il modulo 'dis' prima, grazie per aver mostrato come funziona questo codice!Sfortunatamente non posso sfuggire alla disimballaggio tuple perché tutti i parser SAX usano un'API che passa le tuple, ma questo (e il collegamento che Artur ha fornito) era il tipo di analisi che stavo cercando. –

3

Sì, influiscono sulle prestazioni.

>>> import timeit 
>>> 
>>> 
>>> def function_1(a, (b1, b2), c): 
...  locals() 
... 
>>> def function_2(a, b1, b2, c): 
...  locals() 
... 
>>> 
>>> object_1 = object() 
>>> object_2 = object() 
>>> object_3 = object() 
>>> tuple_of_objects_2_and_3 = (object_2, object_3) 
>>> object_4 = object() 
>>> 
>>> n = 100000000 
>>> 
>>> time_1 = timeit.timeit(lambda: function_1(object_1, tuple_of_objects_2_and_3, 
...           object_4), 
...      number=n) 
>>> time_2 = timeit.timeit(lambda: function_2(object_1, object_2, object_3, 
...           object_4), 
...      number=n) 
>>> 
>>> print(time_1, time_2) 
(170.2440218925476, 151.92010402679443) 

Sulla loro documentazione o scopo, non lo so.

+2

Questo test include anche il tuple packing e il disimballo tuple. Probabilmente sarebbe più giusto usare un 'tuple_23 = (oggetto_2, oggetto_3)' e fare riferimento a quello nel primo lambda. – Robin

+0

@Robin Hai ragione, grazie. Ho modificato la mia risposta. –

+0

Grazie per il test dei tempi, è utile saperlo. –

Problemi correlati