O livro “Pure Python. As sutilezas da programação para profissionais »

imagem Oi, habrozhiteli! Aprender todas as possibilidades do Python é uma tarefa difícil e, com este livro, você pode se concentrar em habilidades práticas que são realmente importantes. Desenterre ouro oculto na biblioteca padrão do Python e comece a escrever hoje um código limpo.

Se você possui experiência com versões mais antigas do Python, pode acelerar o trabalho com modelos e funções modernos introduzidos no Python 3.

Se você trabalhou com outras linguagens de programação e deseja mudar para o Python, encontrará dicas práticas necessárias para se tornar um pythonist eficaz.
Se você quiser aprender a escrever código limpo, encontrará aqui os exemplos mais interessantes e truques pouco conhecidos.

Trecho "A expressão de dicionário mais louca do ocidente"


Às vezes, você encontra um pequeno exemplo de código com profundidade verdadeiramente inesperada - uma única linha de código que pode lhe ensinar muito se você pensar com cuidado. Esse código é como um koan no zen-budismo: uma pergunta ou afirmação usada na prática zen para levantar dúvidas e testar o desempenho dos alunos.

O pequeno pedaço de código que discutimos nesta seção é um exemplo. À primeira vista, pode parecer uma expressão simples de vocabulário, mas após uma inspeção mais detalhada, ele é enviado para um cruzeiro psicodélico de expansão da mente com o intérprete Python.

Recebo um burburinho dessa linha que uma vez eu o imprimi no meu crachá de participante da conferência Python como motivo para uma conversa. Isso levou a alguns diálogos construtivos com os membros da minha lista de email do Python.
Portanto, sem mais delongas, aqui está este pedaço de código. Faça uma pausa para refletir sobre a expressão do vocabulário abaixo e a que seu cálculo deve levar:

>>> {True: '', 1: '', 1.0: ''} 

Vou esperar aqui ...

OK, pronto?

A seguir, temos o resultado que obtemos ao avaliar a expressão de dicionário acima em uma sessão de intérprete Python:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

Admito que, quando vi esse resultado pela primeira vez, fiquei muito pasmo. Mas tudo se encaixará quando você realizar um estudo passo a passo do que está acontecendo aqui. Vamos refletir por que conseguimos isso, devo dizer, resultado não muito intuitivo.

Quando o Python processa nossa expressão de dicionário, ele primeiro cria um novo objeto de dicionário vazio e, em seguida, atribui chaves e valores a ele na ordem em que são passados ​​para a expressão de dicionário.

Então, quando a decompormos em partes, nossa expressão no dicionário será equivalente à seguinte sequência de instruções executadas em ordem:

 >>> xs = dict() >>> xs[True] = '' >>> xs[1] = '' >>> xs[1.0] = '' 

Curiosamente, o Python considera todas as chaves usadas neste exemplo de dicionário como equivalentes:

 >>> True == 1 == 1.0 True 

Ok, mas espere um minuto. Tenho certeza de que você pode admitir intuitivamente que 1,0 == 1, mas por que True é considerado também equivalente a 1? A primeira vez que vi essa expressão do dicionário, ela realmente me intrigou.

Revendo um pouco a documentação do Python, descobri que o Python trata o tipo bool como uma subclasse do tipo int. Este é o caso do Python 2 e Python 3:

O tipo booleano é um subtipo do tipo inteiro e os valores booleanos se comportam, respectivamente, como os valores 0 e 1 em quase todos os contextos, com a exceção de que, quando convertidos em um tipo de string, os valores da string são retornados 'False' ou 'True, respectivamente '

E, claro, isso significa que, no Python, os valores booleanos podem ser tecnicamente usados ​​como índices de lista ou tupla:

 >>> ['', ''][True] '' 

Mas você provavelmente não deve usar esse tipo de variável lógica em nome da clareza (e da saúde mental de seus colegas).

De uma forma ou de outra, de volta à expressão do nosso dicionário.

Quanto à linguagem Python, todos esses valores - True, 1 e 1.0 - representam a mesma chave de dicionário. Quando o intérprete avalia uma expressão do dicionário, substitui repetidamente o valor da chave True. Isso explica por que, no final, o dicionário resultante contém apenas uma chave.

Antes de prosseguirmos, dê uma olhada na expressão original do dicionário:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

Por que ainda estamos obtendo a Verdade como chave aqui? A chave também não deve mudar para 1,0 devido a tarefas repetidas no final?

Após uma pequena pesquisa no código fonte do interpretador Python, descobri que, quando um novo valor é associado a um objeto-chave, os próprios dicionários Python não atualizam esse objeto-chave:

 >>> ys = {1.0: ''} >>> ys[True] = '' >>> ys {1.0: ''} 

Obviamente, isso faz sentido como uma otimização de desempenho: se as chaves são consideradas idênticas, por que perder tempo atualizando o original?
No último exemplo, você viu que o objeto True original como chave nunca é substituído. Por esse motivo, a representação em cadeia do dicionário ainda imprime a chave como True (em vez de 1 ou 1.0).

Pelo que sabemos agora, aparentemente, os valores no dicionário resultante são reescritos apenas porque a comparação sempre os mostrará como equivalentes entre si. No entanto, verifica-se que esse efeito também não é consequência do teste de equivalência pelo método __eq__.

Os dicionários Python dependem de uma estrutura de dados da tabela de hash. Quando vi pela primeira vez essa incrível expressão de dicionário, meu primeiro pensamento foi que esse comportamento estava de alguma forma relacionado a conflitos de hash.

O fato é que a tabela de hash na representação interna armazena as chaves disponíveis nela em várias "cestas" de acordo com o valor de hash de cada chave. O valor do hash é derivado da chave como um valor numérico de comprimento fixo que identifica exclusivamente a chave.

Esse fato permite que você execute operações de pesquisa rápida. Encontrar o valor do hash da chave na tabela de pesquisa é muito mais rápido do que comparar o objeto de chave completo com todas as outras chaves e executar uma verificação de equivalência.

No entanto, métodos para calcular valores de hash geralmente não são ideais. E, finalmente, duas ou mais chaves realmente diferentes terão o mesmo valor de hash derivado e acabarão na mesma cesta da tabela de pesquisa.
Quando duas chaves têm o mesmo valor de hash, essa situação é chamada de conflito de hash e é um caso especial com o qual os algoritmos para inserir e localizar elementos em uma tabela de hash devem ser tratados.

Com base nessa avaliação, é muito provável que o hash esteja de alguma forma relacionado ao resultado inesperado que obtivemos da expressão do dicionário. Portanto, vamos descobrir se os principais valores de hash também desempenham um certo papel aqui.
Defino a classe abaixo como uma pequena ferramenta de detetive:

 class AlwaysEquals: def __eq__(self, other): return True def __hash__(self): return id(self) 

Esta classe é caracterizada por dois aspectos.

Primeiramente, como o método __eq__ Dunder sempre retorna True, todas as instâncias dessa classe fingem ser equivalentes a qualquer objeto:

 >>> AlwaysEquals() == AlwaysEquals() True >>> AlwaysEquals() == 42 True >>> AlwaysEquals() == '?' True 

E segundo, cada instância do AlwaysEquals também retornará um valor de hash exclusivo gerado pela função interna id ():

 >>> objects = [AlwaysEquals(), AlwaysEquals(), AlwaysEquals()] >>> [hash(obj) for obj in objects] [4574298968, 4574287912, 4574287072] 

No Python, a função id () retorna o endereço de um objeto na RAM, que é garantido como único.

Usando essa classe, agora você pode criar objetos que pretendem ser equivalentes a qualquer outro objeto, mas ao mesmo tempo ter um valor de hash exclusivo associado a eles. Isso permitirá que você verifique se as chaves do dicionário foram reescritas, baseando-se apenas no resultado de sua comparação na equivalência.

E, como você pode ver, as chaves no exemplo a seguir não correspondem, embora a comparação sempre as mostre como equivalentes entre si:

 >>> {AlwaysEquals(): '', AlwaysEquals(): ''} { <AlwaysEquals object at 0x110a3c588>: '', <AlwaysEquals object at 0x110a3cf98>: '' } 

Também podemos olhar para essa ideia do outro lado e verificar se o retorno do mesmo valor de hash é motivo suficiente para forçar a reescrita das chaves:

 class SameHash: def __hash__(self): return 1 

A comparação de instâncias da classe SameHash as mostrará como não equivalentes entre si, mas todas terão o mesmo valor de hash 1:

 >>> a = SameHash() >>> b = SameHash() >>> a == b False >>> hash(a), hash(b) (1, 1) 

Vamos ver como os dicionários Python respondem quando tentamos usar instâncias de classe SameHash como chaves de dicionário:

 >>> {a: 'a', b: 'b'} { <SameHash instance at 0x7f7159020cb0>: 'a', <SameHash instance at 0x7f7159020cf8>: 'b' } 

Como este exemplo mostra, o efeito de "chaves sendo substituídas" é causado não apenas por conflitos de valor de hash.

Os dicionários executam uma verificação de equivalência e comparam o valor do hash para determinar se as duas chaves são iguais. Vamos tentar resumir os resultados do nosso estudo.

A expressão do dicionário {True: 'yes', 1: 'no', 1.0: 'maybe'} é calculada como {True: 'possible'}, porque a comparação de todas as chaves deste exemplo, True, 1 e 1.0, as mostrará como equivalentes entre si e todos têm o mesmo valor de hash:

 >>> True == 1 == 1.0 True >>> (hash(True), hash(1), hash(1.0)) (1, 1, 1) 

Talvez agora não seja mais tão surpreendente que tenhamos apenas um resultado como o estado final do dicionário:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

Aqui abordamos muitos tópicos, e esse truque em Python em particular pode não caber na cabeça no início - é por isso que no começo da seção eu o comparei com um koan zen.

Se você está com dificuldades para entender o que está acontecendo nesta seção, tente experimentar todos os exemplos de código em uma sessão de intérprete de Python. Você será recompensado expandindo seu conhecimento dos mecanismos internos da linguagem Python.

»Mais informações sobre o livro podem ser encontradas no site do editor
» Conteúdo
» Trecho

Cupom de 20% de desconto para vendedores ambulantes - Python

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


All Articles