Por que não há compreensão de tupla no Python?

338

Como todos sabemos, há compreensão de lista, como

[i for i in [1, 2, 3, 4]]

e há compreensão de dicionário, como

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

mas

(i for i in (1, 2, 3))

terminará em um gerador, não em uma tuplecompreensão. Por que é que?

Meu palpite é que a tupleé imutável, mas isso não parece ser a resposta.

Shady Xu
fonte
15
Há também uma compreensão set - que se parece muito com a compreensão dict ...
mgilson
3
Há um erro de sintaxe no seu código: {i:j for i,j in {1:'a', 2:'b'}}should be{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Obrigado por apontar isso -.-
Shady Xu
Apenas por uma questão de posteridade, há uma discussão sobre esta acontecendo no Python via Chat
Inbar Rose
Aparentemente existe. stackoverflow.com/a/51811147/9627166
Super S

Respostas:

468

Você pode usar uma expressão geradora:

tuple(i for i in (1, 2, 3))

mas parênteses já foram usados ​​para ... expressões geradoras.

Martijn Pieters
fonte
15
Com este argumento, poderíamos dizer uma lista-compreensão é desnecessária também: list(i for i in (1,2,3)). Eu realmente acho que é simplesmente porque não é uma sintaxe limpa para ele (ou pelo menos ninguém pensou em um)
mgilson
79
Uma compreensão de lista ou conjunto ou ditado é apenas um açúcar sintático para usar uma expressão geradora que gera um tipo específico. list(i for i in (1, 2, 3))é uma expressão geradora que gera uma lista, set(i for i in (1, 2, 3))gera um conjunto. Isso significa que a sintaxe de compreensão não é necessária? Talvez não, mas é muito útil. Nos casos raros em que você precisa de uma tupla, a expressão do gerador funcionará, é clara e não requer a invenção de outra chave ou suporte.
Martijn Pieters
16
A resposta é, obviamente, porque a sintaxe tupla e parênteses são ambíguos
Charles Salvia
19
A diferença entre usar uma compreensão e usar um construtor + gerador é mais do que sutil se você se importa com o desempenho. As compreensões resultam em construção mais rápida em comparação ao uso de um gerador passado para um construtor. No último caso, você está criando e executando funções e funções são caras no Python. [thing for thing in things]constrói uma lista muito mais rápido que list(thing for thing in things). Uma compreensão de tupla não seria inútil; tuple(thing for thing in things)tem problemas de latência e tuple([thing for thing in things])pode ter problemas de memória.
Justin Turner Arthur
9
@MartijnPieters, você pode potencialmente reformular A list or set or dict comprehension is just syntactic sugar to use a generator expression? Está causando confusão quando as pessoas vêem isso como um meio equivalente a um fim. Não é um açúcar tecnicamente sintático, pois os processos são realmente diferentes, mesmo que o produto final seja o mesmo.
jpp
77

Raymond Hettinger (um dos principais desenvolvedores do Python) disse isso sobre tuplas em um tweet recente :

#python tip: Geralmente, as listas são para loop; tuplas para estruturas. As listas são homogêneas; tuplas heterogêneas. Listas para comprimento variável.

Isso (para mim) apóia a idéia de que, se os itens em uma sequência estiverem relacionados o suficiente para serem gerados por um gerador, então deve ser uma lista. Embora uma tupla seja iterável e pareça simplesmente uma lista imutável, é realmente o equivalente em Python de uma estrutura C:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

torna-se em Python

x = (3, 'g', 5.9)
chepner
fonte
26
A propriedade de imutibilidade pode ser importante e, muitas vezes, um bom motivo para usar uma tupla quando você usaria normalmente uma lista. Por exemplo, se você tiver uma lista de 5 números que deseja usar como chave para um ditado, a tupla é o caminho a seguir.
Decon
Essa é uma boa dica de Raymond Hettinger. Eu diria ainda que há um caso de uso para usar o construtor de tupla com um gerador, como descompactar outra estrutura, talvez maior, em uma menor, repetindo os atributos que você está interessado em converter em um registro de tupla.
dave
2
@ Dave Você provavelmente pode apenas usar operator.itemgetternesse caso.
Chepner #
@chepner, entendo. Isso é bem próximo do que eu quero dizer. Ele retorna um valor que pode ser chamado, por isso, se eu precisar fazê-lo apenas uma vez, não vejo muita vitória contra apenas usando tuple(obj[item] for item in items)diretamente. No meu caso, eu estava incorporando isso em uma compreensão de lista para fazer uma lista de registros de tupla. Se eu precisar fazer isso repetidamente em todo o código, o itemgetter ficará ótimo. Talvez o itemgetter seja mais idiomático de qualquer maneira?
dave
Eu vejo a relação entre frozenset e conjunto análogo ao da tupla e da lista. É menos sobre heterogeneidade e mais sobre imutabilidade - frozensets e tuplas podem ser chaves para dicionários, listas e conjuntos, não devido à sua mutabilidade.
poliglota
56

Desde o Python 3.5 , você também pode usar a *sintaxe de descompactação splat para descompactar uma expressão de gerador:

*(x for x in range(10)),
czheo
fonte
2
Isso é ótimo (e funciona), mas não consigo encontrar em nenhum lugar que esteja documentado! Você tem um link?
Felixphew
8
Nota: Como um detalhe de implementação, é basicamente o mesmo que fazer tuple(list(x for x in range(10)))( os caminhos de código são idênticos , com os dois construindo um list, com a única diferença é que a etapa final é criar um a tuplepartir do liste jogar fora o listquando uma tuplesaída É necessário). Significa que você realmente não evita um par de temporários.
ShadowRanger 02/10
4
Para expandir o comentário de @ShadowRanger, aqui está uma pergunta em que eles mostram que a sintaxe literal splat + tupla é realmente um pouco mais lenta do que passar uma expressão de gerador para o construtor de tuplas.
Lucubrator
Estou tentando isso no Python 3.7.3 e *(x for x in range(10))não funciona. Eu entendo SyntaxError: can't use starred expression here. No entanto tuple(x for x in range(10))funciona.
Ryan H.
4
@RyanH. você precisa colocar uma vírgula no final.
precisa saber é o seguinte
27

Como outro pôster macmmencionado, a maneira mais rápida de criar uma tupla a partir de um gerador é tuple([generator]).


Comparação de desempenho

  • Compreensão da lista:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tupla da compreensão da lista:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tupla do gerador:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Tupla de desembalar:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Minha versão do python :

$ python3 --version
Python 3.6.3

Portanto, você deve sempre criar uma tupla a partir de uma compreensão da lista, a menos que o desempenho não seja um problema.

Tom
fonte
10
Nota: o tuplelistcomp requer um pico de uso de memória com base no tamanho combinado do final tuplee list. tuplede um genexpr, embora mais lento, significa que você paga apenas pelo final tuple, não temporário list(o próprio genexpr ocupa uma memória aproximadamente fixa). Geralmente não é significativo, mas pode ser importante quando os tamanhos envolvidos são enormes.
ShadowRanger
25

A compreensão funciona repetindo ou iterando itens e atribuindo-os a um contêiner, uma Tupla não pode receber atribuições.

Depois que uma tupla é criada, ela não pode ser anexada, estendida ou atribuída a. A única maneira de modificar uma tupla é se um de seus objetos pode ser atribuído a ele (é um contêiner não-tupla). Porque a Tupla está apenas mantendo uma referência a esse tipo de objeto.

Além disso - uma tupla tem seu próprio construtor, tuple()que você pode fornecer a qualquer iterador. O que significa que, para criar uma tupla, você pode fazer:

tuple(i for i in (1,2,3))
Inbar Rose
fonte
9
De certa forma, eu concordo (sobre não ser necessário porque uma lista fará), mas de outras maneiras eu discordo (sobre o raciocínio ser porque é imutável). De certa forma, faz mais sentido compreender os objetos imutáveis. que faz lst = [x for x in ...]; x.append()?
mgilson
@mgilson Não sei ao certo como isso se relaciona com o que eu disse?
Inbar Rose
2
@mgilson se uma tupla for imutável, significa que a implementação subjacente não pode "gerar" uma tupla ("geração" implicando a construção de uma peça de cada vez). imutável significa que você não pode construir aquele com 4 peças, alterando aquele com 3 peças. em vez disso, você implementa a "geração" da tupla criando uma lista, algo projetado para geração, depois constrói a tupla como uma última etapa e descarta a lista. A linguagem reflete essa realidade. Pense nas tuplas como estruturas C.
Scott
2
embora seja razoável que o açúcar sintático das compreensões funcione para tuplas, pois você não pode usá-la até que a compreensão seja retornada. Efetivamente, ele não age como mutável, ao contrário, uma compreensão de tupla pode se comportar como um apêndice.
Uchuugaka # 22/17
12

Meu melhor palpite é que eles ficaram sem colchetes e não acharam que seria útil o suficiente para garantir a adição de uma sintaxe "feia" ...

mgilson
fonte
11
Parênteses angulares não utilizados.
Uchuugaka # 22/17
@uchuugaka - Não completamente. Eles são usados ​​para operadores de comparação. Ele provavelmente poderia ainda ser feito sem ambigüidade, mas talvez não vale o esforço ...
mgilson
3
@uchuugaka Vale notar que {*()}, embora feio, funciona como um conjunto vazio literal!
MI Wright
11
Ugh. Do ponto de vista estético, eu acho que eu sou parcial para set():)
mgilson
11
@QuantumMechanic: Sim, esse é o ponto; as generalizações de desempacotamento tornaram possível o "conjunto literal" vazio. Observe que {*[]}é estritamente inferior às outras opções; a string vazia e vazia tuple, sendo imutáveis, são singletons, portanto, não é necessário temporário para construir a vazia set. Por outro lado, o vazio listnão é um singleton, então você realmente precisa construí-lo, usá-lo para construir sete destruí-lo, perdendo qualquer vantagem trivial de desempenho que o operador de um olho só oferece.
ShadowRanger 03/10
8

As tuplas não podem ser anexadas com eficiência como uma lista.

Portanto, uma compreensão da tupla precisaria usar uma lista internamente e depois converter em uma tupla.

Seria o mesmo que você faz agora: tupla ([compreensão])

macm
fonte
3

Parênteses não criam uma tupla. aka um = (dois) não é uma tupla. A única maneira de contornar isso é um = (dois) ou um = tupla (dois). Portanto, uma solução é:

tuple(i for i in myothertupleorlistordict) 
ilias iliadis
fonte
agradável. é quase o mesmo.
Uchuugaka # 22/17
-1

Creio que é simplesmente por uma questão de clareza, não queremos desordenar a linguagem com muitos símbolos diferentes. Além disso, uma tuplecompreensão nunca é necessária ; uma lista pode ser usada apenas com diferenças insignificantes de velocidade, ao contrário de uma compreensão de ditado em oposição a uma compreensão de lista.

jamylak
fonte
-2

Podemos gerar tuplas a partir de uma compreensão da lista. O seguinte adiciona dois números sequencialmente a uma tupla e fornece uma lista dos números 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Rohit Malgaonkar
fonte