Otimizações usadas no Python: lista e tupla

No Python, existem dois tipos semelhantes - lista (lista) e tupla (tupla). A diferença mais famosa entre os dois é que as tuplas são imutáveis.

Você não pode alterar objetos na tupla:

>>> a = (1,2,3) >>> a[0] = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment 

Mas você pode modificar objetos mutáveis ​​dentro de uma tupla:

 >>> b = (1,[1,2,3],3) >>> b[1] [1, 2, 3] >>> b[1].append(4) >>> b (1, [1, 2, 3, 4], 3) 

Dentro do CPython (intérprete padrão), a lista e a tupla são implementadas como uma folha de ponteiros (links) para objetos Python, ou seja, fisicamente, eles não armazenam objetos próximos um do outro. Quando você exclui um objeto da lista, a referência a esse objeto é excluída. Se outra pessoa se referir ao objeto, ele continuará na memória.

Tuplas


Apesar do fato de as tuplas serem muito menos comuns no código e não tão populares, esse é um tipo muito fundamental que o Python constantemente usa para fins internos.

Você pode não perceber, mas usa tuplas quando:

  • trabalhar com argumentos ou parâmetros (eles são armazenados como tuplas)
  • retorna duas ou mais variáveis ​​de uma função
  • itere chaves de valor em um dicionário
  • use formatação de string

Normalmente, o script mais simples usa milhares de tuplas:

 >>> import gc >>> def type_stats(type_obj): ... count = 0 ... for obj in gc.get_objects(): ... if type(obj) == type_obj: ... count += 1 ... return count ... >>> type_stats(tuple) 3136 >>> type_stats(list) 659 >>> import pandas >>> type_stats(tuple) 6953 >>> type_stats(list) 2455 

Listas vazias vs tuplas vazias


Uma tupla vazia funciona como um singleton, ou seja, sempre há apenas uma tupla vazia na memória de um script Python em execução. Todas as tuplas vazias se referem simplesmente ao mesmo objeto, isso é possível porque as tuplas são imutáveis. Essa abordagem economiza muita memória e acelera o processo de trabalho com tuplas vazias.

 >>> a = () >>> b = () >>> a is b True >>> id(a) 4409020488 >>> id(b) 4409020488 >>> #  CPython,  id    . 

Mas isso não funciona com listas, porque elas podem ser alteradas:

 >>> a = [] >>> b = [] >>> a is b False >>> id(a) 4465566920 >>> id(b) 4465370632 

Otimizando a alocação de memória para tuplas


Para reduzir a fragmentação da memória e acelerar a criação de tuplas, o Python reutiliza tuplas antigas que foram excluídas. Se uma tupla consiste em menos de 20 elementos e não é mais usada, em vez de excluí-la, o Python a coloca em uma lista especial, que armazena as tuplas livres para reutilização.

Essa lista é dividida em 20 grupos, onde cada grupo é uma lista de tuplas de tamanho n, onde n é de 0 a 20. Cada grupo pode armazenar até 2.000 tuplas gratuitas. O primeiro grupo armazena apenas um elemento e é uma lista de uma tupla vazia.

 >>> a = (1,2,3) >>> id(a) 4427578104 >>> del a >>> b = (1,2,4) >>> id(b) 4427578104 

No exemplo acima, podemos ver que aeb têm o mesmo endereço na memória. Isso se deve ao fato de termos instantaneamente obtido uma tupla do mesmo tamanho.

Otimizando a alocação de memória para listas


Como as listas estão sujeitas a alterações, a mesma otimização que no caso das tuplas não pode mais ser acionada. Apesar disso, as listas usam otimizações semelhantes destinadas a listas vazias. Se a lista vazia for excluída, também poderá ser reutilizada no futuro.

 >>> a = [] >>> id(a) 4465566792 >>> del a >>> b = [] >>> id(b) 4465566792 

Redimensionar lista


Para evitar a sobrecarga das listas de redimensionamento constante, o Python não a redimensiona sempre que necessário. Em vez disso, cada lista possui um conjunto de células adicionais ocultas ao usuário, mas que posteriormente podem ser usadas para novos elementos. Assim que as células ocultas terminam, o Python adiciona espaço extra para novos elementos. E ele faz isso com uma boa margem, o número de células ocultas é selecionado com base no tamanho atual da lista - quanto maior, mais slots ocultos adicionais para novos elementos.

Essa otimização é especialmente útil quando você tenta adicionar muitos elementos em um loop.

O padrão de crescimento do tamanho da lista é mais ou menos assim: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...

Por exemplo, se você deseja adicionar um novo elemento a uma lista com 8 elementos, não haverá células livres e o Python expandirá imediatamente seu tamanho para 16 células, onde 9 delas serão ocupadas e visíveis ao usuário.

Fórmula de dimensionamento do Python:

 >>> def get_new_size(n_items): ... new_size = n_items + (n_items // 2 ** 3) ... if n_items < 9: ... new_size += 3 ... else: ... new_size += 6 ... ... return new_size ... >>> get_new_size(9) 16 

Velocidade


Se compararmos esses dois tipos em velocidade, em média, em um hospital, as tuplas são um pouco mais rápidas que as listas. Raymond Hettinger tem uma ótima explicação para a diferença de velocidade no fluxo de pilha .

PS: Eu sou o autor deste artigo, você pode fazer qualquer pergunta.

Source: https://habr.com/ru/post/pt417783/


All Articles