Perché è []
più veloce di list()
?
Il motivo principale è che Python tratta list()
esattamente come una funzione definita dall'utente, il che significa che puoi intercettarlo aliasando qualcos'altrolist
e fare qualcosa di diverso (come usare il tuo elenco di sottoclassi o forse un deque).
Crea immediatamente una nuova istanza di un elenco predefinito con []
.
La mia spiegazione cerca di darti l'intuizione per questo.
Spiegazione
[]
è comunemente noto come sintassi letterale.
In grammatica, questo è indicato come un "elenco di visualizzazione". Dai documenti :
Una visualizzazione elenco è una serie possibilmente vuota di espressioni racchiuse tra parentesi quadre:
list_display ::= "[" [starred_list | comprehension] "]"
Una visualizzazione elenco produce un nuovo oggetto elenco, il cui contenuto viene specificato da un elenco di espressioni o da una comprensione. Quando viene fornito un elenco di espressioni separato da virgole, i suoi elementi vengono valutati da sinistra a destra e posizionati nell'oggetto elenco in quell'ordine. Quando viene fornita una comprensione, l'elenco viene costruito dagli elementi risultanti dalla comprensione.
In breve, ciò significa che list
viene creato un oggetto incorporato di tipo .
Non si può eludere questo, il che significa che Python può farlo il più rapidamente possibile.
D'altra parte, list()
può essere intercettato dalla creazione di un builtin list
usando il costruttore dell'elenco incorporato.
Ad esempio, supponiamo di voler creare rumorosamente i nostri elenchi:
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
Potremmo quindi intercettare il nome list
nell'ambito globale a livello di modulo e quindi quando creiamo un list
, creiamo effettivamente il nostro elenco sottotipato:
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
Allo stesso modo potremmo rimuoverlo dallo spazio dei nomi globale
del list
e inseriscilo nello spazio dei nomi incorporato:
import builtins
builtins.list = List
E adesso:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
E nota che la visualizzazione dell'elenco crea un elenco incondizionatamente:
>>> list_1 = []
>>> type(list_1)
<class 'list'>
Probabilmente lo facciamo solo temporaneamente, quindi annulliamo le nostre modifiche - prima rimuovi il nuovo List
oggetto dai builtin:
>>> del builtins.list
>>> builtins.list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'builtins' has no attribute 'list'
>>> list()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'list' is not defined
Oh, no, abbiamo perso la traccia dell'originale.
Non preoccuparti, possiamo ancora ottenere list
- è il tipo di un elenco letterale:
>>> builtins.list = type([])
>>> list()
[]
Così...
Perché è []
più veloce di list()
?
Come abbiamo visto - possiamo sovrascrivere list
- ma non possiamo intercettare la creazione del tipo letterale. Quando usiamo list
dobbiamo fare le ricerche per vedere se c'è qualcosa.
Quindi dobbiamo chiamare qualunque cosa abbiamo chiamato. Dalla grammatica:
Una chiamata chiama un oggetto richiamabile (ad es. Una funzione) con una serie di argomenti possibilmente vuota:
call ::= primary "(" [argument_list [","] | comprehension] ")"
Possiamo vedere che fa la stessa cosa per qualsiasi nome, non solo per l'elenco:
>>> import dis
>>> dis.dis('list()')
1 0 LOAD_NAME 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
>>> dis.dis('doesnotexist()')
1 0 LOAD_NAME 0 (doesnotexist)
2 CALL_FUNCTION 0
4 RETURN_VALUE
Non []
esiste alcuna chiamata di funzione a livello di bytecode Python:
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
Va semplicemente alla costruzione dell'elenco senza ricerche o chiamate a livello di bytecode.
Conclusione
Abbiamo dimostrato che list
può essere intercettato con il codice utente utilizzando le regole di scoping e che list()
cerca un callable e quindi lo chiama.
Considerando che []
è un elenco elenco, o letterale, e quindi evita la ricerca del nome e la chiamata di funzione.
()
e''
sono speciali, poiché non sono solo vuoti, sono immutabili e, in quanto tali, è una vittoria facile renderli singoli; non costruiscono nemmeno nuovi oggetti, caricano solo il singleton per il vuototuple
/str
. Tecnicamente un dettaglio di implementazione, ma ho difficoltà a immaginare perché non memorizzino nella cache il vuototuple
/str
per motivi di prestazioni. Quindi la tua intuizione[]
e il{}
ritorno di un titolo letterale erano sbagliati, ma si applica a()
e''
.