Por que é []
mais rápido que list()
?
O maior motivo é que o Python trata list()
exatamente como uma função definida pelo usuário, o que significa que você pode interceptá-lo alterando o nome de outra coisa list
e fazer algo diferente (como usar sua própria lista subclassificada ou talvez um deque).
Ele cria imediatamente uma nova instância de uma lista interna com []
.
Minha explicação procura dar-lhe a intuição para isso.
Explicação
[]
é comumente conhecido como sintaxe literal.
Na gramática, isso é chamado de "exibição de lista". Dos documentos :
Uma exibição de lista é uma série possivelmente vazia de expressões entre colchetes:
list_display ::= "[" [starred_list | comprehension] "]"
Uma exibição de lista gera um novo objeto de lista, sendo o conteúdo especificado por uma lista de expressões ou uma compreensão. Quando uma lista de expressões separada por vírgula é fornecida, seus elementos são avaliados da esquerda para a direita e colocados no objeto de lista nessa ordem. Quando uma compreensão é fornecida, a lista é construída a partir dos elementos resultantes da compreensão.
Em resumo, isso significa que um objeto interno do tipo list
é criado.
Não há como contornar isso - o que significa que o Python pode fazê-lo o mais rápido possível.
Por outro lado, list()
pode ser interceptado a partir da criação de um built-in list
usando o construtor da lista de built-in.
Por exemplo, digamos que queremos que nossas listas sejam criadas ruidosamente:
class List(list):
def __init__(self, iterable=None):
if iterable is None:
super().__init__()
else:
super().__init__(iterable)
print('List initialized.')
Poderíamos então interceptar o nome list
no escopo global no nível do módulo e, quando criamos um list
, criamos nossa lista de subtipos:
>>> list = List
>>> a_list = list()
List initialized.
>>> type(a_list)
<class '__main__.List'>
Da mesma forma, poderíamos removê-lo do espaço para nome global
del list
e coloque-o no espaço para nome interno:
import builtins
builtins.list = List
E agora:
>>> list_0 = list()
List initialized.
>>> type(list_0)
<class '__main__.List'>
E observe que a exibição da lista cria uma lista incondicionalmente:
>>> list_1 = []
>>> type(list_1)
<class 'list'>
Provavelmente, apenas fazemos isso temporariamente, então vamos desfazer nossas alterações - primeiro remova o novo List
objeto dos componentes internos:
>>> 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
Ah, não, perdemos a noção do original.
Não se preocupe, ainda podemos obter list
- é o tipo de uma lista literal:
>>> builtins.list = type([])
>>> list()
[]
Assim...
Por que é []
mais rápido que list()
?
Como vimos - podemos sobrescrever list
- mas não podemos interceptar a criação do tipo literal. Quando usamos list
, temos que fazer as pesquisas para ver se há algo lá.
Então, temos que ligar para qualquer coisa que possamos chamar. Da gramática:
Uma chamada chama um objeto que pode ser chamado (por exemplo, uma função) com uma série de argumentos possivelmente vazia:
call ::= primary "(" [argument_list [","] | comprehension] ")"
Podemos ver que ele faz a mesma coisa com qualquer nome, não apenas na lista:
>>> 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
Pois []
não há chamada de função no nível de bytecode do Python:
>>> dis.dis('[]')
1 0 BUILD_LIST 0
2 RETURN_VALUE
Ele simplesmente vai direto para a construção da lista sem nenhuma pesquisa ou chamada no nível do bytecode.
Conclusão
Demonstramos que list
pode ser interceptado com o código do usuário usando as regras de escopo e quelist()
procura um chamador e depois o chama.
Considerando que []
é uma exibição de lista, ou literal, e assim evita a pesquisa de nome e a chamada de função.
()
e''
são especiais, pois não são apenas vazios, são imutáveis e, como tal, é uma vitória fácil torná-los singletons; eles nem constroem novos objetos, apenas carregam o singleton para otuple
/ vaziostr
. Tecnicamente, é um detalhe de implementação, mas é difícil imaginar por que eles não armazenariam em cache o vaziotuple
/str
por motivos de desempenho. Portanto, sua intuição[]
e a{}
devolução de um literal de estoque estavam erradas, mas se aplica a()
e''
.