python Por que o splatting cria uma tupla nos rhs, mas uma lista no lhs?




python-3.x tuples (5)

não é uma resposta completa, mas desmontar dá algumas pistas:

from dis import dis

def a():
    squares = (*map((2).__rpow__, range(5)),)
    # print(squares)

print(dis(a))

desmonta como

  5           0 LOAD_GLOBAL              0 (map)
              2 LOAD_CONST               1 (2)
              4 LOAD_ATTR                1 (__rpow__)
              6 LOAD_GLOBAL              2 (range)
              8 LOAD_CONST               2 (5)
             10 CALL_FUNCTION            1
             12 CALL_FUNCTION            2
             14 BUILD_TUPLE_UNPACK       1
             16 STORE_FAST               0 (squares)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

enquanto

def b():
    *squares, = map((2).__rpow__, range(5))
print(dis(b))

resulta em

 11           0 LOAD_GLOBAL              0 (map)
              2 LOAD_CONST               1 (2)
              4 LOAD_ATTR                1 (__rpow__)
              6 LOAD_GLOBAL              2 (range)
              8 LOAD_CONST               2 (5)
             10 CALL_FUNCTION            1
             12 CALL_FUNCTION            2
             14 UNPACK_EX                0
             16 STORE_FAST               0 (squares)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

o documento em UNPACK_EX declara:

UNPACK_EX (conta)

Implementa atribuição com um destino com estrela: Descompacta um iterável em TOS em valores individuais, em que o número total de valores pode ser menor que o número de itens no iterável: um dos novos valores será uma lista de todos os itens restantes.

O byte baixo de contagens é o número de valores antes do valor da lista, o byte alto conta o número de valores após ele. Os valores resultantes são colocados na pilha da direita para a esquerda.

(ênfase minha). enquanto BUILD_TUPLE_UNPACK retorna uma tuple :

BUILD_TUPLE_UNPACK (contagem)

Pops conta iterables da pilha, une-os em uma única tupla e envia o resultado. Implementa a descompactação iterável em exibições de tupla (* x, * y, * z).

Considere, por exemplo,

squares = *map((2).__rpow__, range(5)),
squares
# (0, 1, 4, 9, 16)

*squares, = map((2).__rpow__, range(5))
squares
# [0, 1, 4, 9, 16]

Então, sendo tudo o mais igual, temos uma lista quando aplicamos um splat sobre o lhs e uma tupla quando aplicamos splits nos rhs.

Por quê?

Isso é por design e, se sim, qual é a justificativa? Ou, se não, há alguma razão técnica? Ou é assim que é, sem razão particular?


Isso está especificado nas desvantagens do PEP-0448

Enquanto que *elements, = iterable faz com que os elementos sejam uma lista, elements = *iterable, faz com que os elementos sejam uma tupla. A razão para isso pode confundir pessoas não familiarizadas com o construto.

Também como por: especificação PEP-3132

Este PEP propõe uma alteração na sintaxe de desempacotamento, permitindo especificar um nome "catch-all" que será atribuído a uma lista de todos os itens não atribuídos a um nome "regular".

Também mencionado aqui: exprlists do Python-3

Exceto quando parte de uma lista ou conjunto de exibição, uma lista de expressões contendo pelo menos uma vírgula produz uma tupla.
A vírgula final é necessária apenas para criar uma única tupla (também conhecida como singleton); é opcional em todos os outros casos. Uma única expressão sem vírgula final não cria uma tupla, mas produz o valor dessa expressão. (Para criar uma tupla vazia, use um par de parênteses vazios: ().)

Isso também pode ser visto em um exemplo mais simples aqui, onde os elementos em uma lista

In [27]: *elements, = range(6)                                                                                                                                                      

In [28]: elements                                                                                                                                                                   
Out[28]: [0, 1, 2, 3, 4, 5]

e aqui, onde os elementos são uma tupla

In [13]: elements = *range(6),                                                                                                                                                      

In [14]: elements                                                                                                                                                                   
Out[14]: (0, 1, 2, 3, 4, 5)

Pelo que pude entender dos comentários e das outras respostas:

  • O primeiro comportamento é manter-se alinhado com as listas de argumentos arbitrários existentes usadas em funções ie. *args

  • O segundo comportamento é ser capaz de usar as variáveis ​​no LHS mais abaixo na avaliação, portanto, torná-lo uma lista, um valor mutável em vez de uma tupla faz mais sentido


Para o RHS, não há muito problema. a link diz bem:

Nós temos isso funcionando normalmente em chamadas de função. Ele expande o conteúdo do iterável ao qual está anexado. Então, a afirmação:

elements = *iterable

pode ser visto como:

elements = 1, 2, 3, 4,

que é outra maneira de uma tupla ser inicializada.

Agora, para o LHS, Sim, existem razões técnicas para o LHS usar uma lista, conforme indicado na discussão em torno do PEP 3132 inicial para estender a descompactação

As razões podem ser extraídas da conversa sobre o PEP (adicionada no final).

Essencialmente, resume-se a alguns fatores-chave:

  • O LHS precisava suportar uma "expressão marcada com estrela" que não estava necessariamente restrita apenas ao final.
  • O RHS precisava permitir que vários tipos de sequência fossem aceitos, incluindo iteradores.
  • A combinação dos dois pontos acima exigiu manipulação / mutação do conteúdo depois de aceitá-los na expressão estrelada.
  • Uma abordagem alternativa ao manuseio, uma para imitar o iterador alimentado no RHS, mesmo deixando as dificuldades de implementação de lado, foi abatida por Guido por seu comportamento inconsistente.
  • Considerando todos os fatores acima, uma tupla no LHS teria que ser uma lista primeiro e depois convertida. Essa abordagem apenas adicionaria a sobrecarga e não convidaria nenhuma discussão adicional.

Resumo : Uma combinação de vários fatores levou à decisão de permitir uma lista sobre o LHS, e os motivos se alimentaram um do outro.

Extrato relevante para não permitir tipos inconsistentes:

O caso de uso importante no Python para a semântica proposta é quando você tem um registro de comprimento variável, cujos primeiros poucos itens são interessantes, e o resto é menos, mas não é sem importância. (Se você quisesse jogar fora o resto, você escreveria a, b, c = x [: 3] ao invés de a, b, c, * d = x.) É muito mais conveniente para este caso de uso se o tipo de d é corrigido pela operação, então você pode contar com seu comportamento.

Há um bug no design do filter () no Python 2 (que será corrigido no 3.0 transformando-o em um iterador BTW): se a entrada for uma tupla, a saída também será uma tupla, mas se a entrada for uma lista ou qualquer outra coisa , a saída é uma lista. Isso é uma assinatura totalmente insana, já que significa que você não pode contar com o resultado sendo uma lista, nem sendo uma tupla - se você precisar que ela seja uma ou outra, você tem que convertê-la em uma, que é um desperdício de tempo e espaço. Por favor, não vamos repetir esse erro de design. -Guido

Eu também tentei recriar uma conversa parcialmente citada que pertence ao resumo acima. https://mail.python.org/pipermail/python-3000/2007-May/007198.html ênfase minha.

1

Nas listas de argumentos, * args esgota os iteradores, convertendo-os em tuplas. Eu acho que seria confuso se * args na tupla desempacotar não fizesse a mesma coisa.

Isso levanta a questão de por que o patch produz listas, não tuplas. Qual é o raciocínio por trás disso?

STeVe

2

IMO, é provável que você deseje continuar processando a sequência resultante, inclusive modificando-a.

Georg

3

Bem, se é isso que você está mirando, então eu esperaria que fosse mais útil ter a descompactação não gerando listas, mas o mesmo tipo que você começou, por exemplo , se eu comecei com uma string, eu provavelmente continuarei usando strings :: - texto adicional cortado

4

Ao lidar com um iterador, você não sabe o comprimento antecipadamente, então a única maneira de obter uma tupla seria produzir uma lista primeiro e depois criar uma tupla a partir dela. Greg

5

Sim. Essa foi uma das razões pelas quais foi sugerido que os * args devam aparecer apenas no final da descompactação da tupla.

STeVe

convos casal pulados

6

Eu não acho que retornar o tipo dado é um objetivo que deve ser tentado, porque ele só pode funcionar para um conjunto fixo de tipos conhecidos. Dado um tipo de sequência arbitrária, não há como saber como criar uma nova instância com o conteúdo especificado.

- Greg

convocados

7

Eu estou sugerindo que:

  • lista listas de retorno
  • tuplas retornam tuplas
  • Recipientes XYZ retornam recipientes XYZ
  • iteráveis ​​não contêineres retornam iteradores.

Como você propõe distinguir entre os dois últimos casos? Tentar dividi-lo e capturar uma exceção não é aceitável , IMO, pois ele pode facilmente mascarar bugs.

- Greg

8

Mas espero menos útil. Não suportará "a, * b, c =" também. A partir de um POV de implementação , se você tiver um objeto desconhecido no RHS, tente cortá-lo antes de tentar iterar sobre ele; isso pode causar problemas, por exemplo, se o objeto for um defaultdict - já que x [3:] é implementado como x [slice (None, 3, None)], o defaultdict lhe dará seu valor padrão. Eu prefiro definir isso em termos de iteração sobre o objeto até que ele esteja esgotado, o que pode ser otimizado para certos tipos conhecidos, como listas e tuplas.

- - Guido van Rossum


TLDR: Você recebe uma tuple no RHS porque pediu uma. Você recebe uma list no LHS porque é mais fácil.

É importante ter em mente que o RHS é avaliado antes do LHS - é por isso que a, b = b, a funciona. A diferença se torna aparente ao dividir a atribuição e usar recursos adicionais para o LHS e o RHS:

# RHS: Expression List
a = head, *tail
# LHS: Target List
*leading, last = a

Em suma, enquanto os dois parecem semelhantes, eles são coisas completamente diferentes. O RHS é uma expressão para criar uma tuple de todos os nomes - o LHS é uma ligação para vários nomes de uma tuple . Mesmo se você vir o LHS como uma tupla de nomes, isso não restringirá o tipo de cada nome.

O RHS é uma lista de expressões - um literal de tuple sem os parênteses opcionais () . Isso é o mesmo que 1, 2 cria uma tupla, mesmo sem parênteses, e como delimitar [] ou {} cria uma list ou set . O *tail significa apenas descompactar essa tuple .

Novo na versão 3.5: Iterable descompactando em listas de expressão, originalmente proposto pelo PEP 448 .

O LHS não cria um valor, ele associa valores a vários nomes. Com um nome genérico como *leading , a ligação não é conhecida em todos os casos. Em vez disso, o pega-tudo contém tudo o que resta.

Usar uma list para armazenar valores torna isso simples - os valores dos nomes à direita podem ser removidos com eficiência do final. A list restante, em seguida, contém exatamente os valores para o nome pega-tudo. Na verdade, isso é exatamente o que o CPython faz :

  • coletar todos os itens para metas obrigatórias antes da estrela
  • coletar todos os itens restantes do iterável em uma lista
  • itens pop para destinos obrigatórios depois da estrela da lista
  • empurre os itens individuais e a lista redimensionada na pilha

Mesmo quando o LHS tem um nome pega-tudo sem nomes à direita, é uma list de consistência.


Há uma indicação do motivo pelo qual no final do PEP 3132 - Extended Iterable Unpacking :

Aceitação

Após uma breve discussão sobre a lista python-3000 [1], o PEP foi aceito por Guido em sua forma atual. Possíveis mudanças discutidas foram:

[...]

Faça o alvo com estrela uma tupla em vez de uma lista. Isso seria consistente com os * args de uma função, mas dificultaria ainda mais o processamento do resultado.

[1] https://mail.python.org/pipermail/python-3000/2007-May/007198.html

Assim, a vantagem de ter uma lista mutável em vez de uma tupla imutável parece ser a razão.





splat