Pourquoi n'y a-t-il pas de compréhension de tuple en Python?

340

Comme nous le savons tous, il y a la compréhension de la liste, comme

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

et il y a la compréhension du dictionnaire, comme

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

mais

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

se retrouvera dans un générateur, pas une tuplecompréhension. Pourquoi donc?

Je suppose que a tupleest immuable, mais cela ne semble pas être la réponse.

Shady Xu
la source
16
Il y a aussi une compréhension d'ensemble - qui ressemble beaucoup à une compréhension de dict ...
mgilson
3
Il y a une erreur de syntaxe dans votre code: {i:j for i,j in {1:'a', 2:'b'}}devrait être{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Merci de l'avoir signalé
Shady Xu
Juste pour le plaisir de la postérité, il y a une discussion à ce sujet dans le chat Python
Inbar Rose
Apparemment, il y en a. stackoverflow.com/a/51811147/9627166
Super S

Réponses:

471

Vous pouvez utiliser une expression de générateur:

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

mais des parenthèses étaient déjà prises pour… les expressions génératrices.

Martijn Pieters
la source
15
Par cet argument, on pourrait dire une liste-compréhension est trop inutile: list(i for i in (1,2,3)). Je pense vraiment que c'est simplement parce qu'il n'y a pas de syntaxe propre (ou du moins personne n'y a pensé)
mgilson
79
Une compréhension de liste, d'ensemble ou de dict n'est qu'un sucre syntaxique pour utiliser une expression de générateur qui génère un type spécifique. list(i for i in (1, 2, 3))est une expression de générateur qui génère une liste, set(i for i in (1, 2, 3))génère un ensemble. Est-ce à dire que la syntaxe de compréhension n'est pas nécessaire? Peut-être pas, mais c'est terriblement pratique. Pour les rares cas où vous avez besoin d'un tuple à la place, l'expression du générateur fera l'affaire, est claire et ne nécessite pas l'invention d'une autre accolade ou d'un support.
Martijn Pieters
16
La réponse est évidemment parce que la syntaxe et la parenthèse des tuples sont ambiguës
Charles Salvia
19
La différence entre utiliser une compréhension et utiliser un constructeur + générateur est plus que subtile si vous vous souciez des performances. Les compréhensions se traduisent par une construction plus rapide par rapport à l'utilisation d'un générateur passé à un constructeur. Dans ce dernier cas, vous créez et exécutez des fonctions et les fonctions sont coûteuses en Python. [thing for thing in things]construit une liste beaucoup plus rapidement que list(thing for thing in things). Une compréhension de tuple ne serait pas inutile; tuple(thing for thing in things)a des problèmes de latence et tuple([thing for thing in things])pourrait avoir des problèmes de mémoire.
Justin Turner Arthur
9
@MartijnPieters, pouvez-vous potentiellement reformuler A list or set or dict comprehension is just syntactic sugar to use a generator expression? Cela crée de la confusion chez les gens qui considèrent ces derniers comme des moyens équivalents à une fin. Ce n'est pas du sucre techniquement syntaxique car les processus sont en fait différents, même si le produit final est le même.
jpp
77

Raymond Hettinger (l'un des développeurs principaux de Python) a dit ceci à propos des tuples dans un tweet récent :

Astuce #python: en règle générale, les listes sont destinées au bouclage; tuples pour les structures. Les listes sont homogènes; tuples hétérogènes. Listes de longueur variable.

Cela (pour moi) soutient l'idée que si les éléments d'une séquence sont suffisamment liés pour être générés par un générateur, eh bien, cela devrait être une liste. Bien qu'un tuple soit itérable et semble être simplement une liste immuable, c'est vraiment l'équivalent Python d'une structure C:

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

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

devient en Python

x = (3, 'g', 5.9)
chepner
la source
26
Cependant, la propriété d'immutibilité peut être importante et souvent une bonne raison d'utiliser un tuple alors que vous utiliseriez normalement une liste. Par exemple, si vous avez une liste de 5 chiffres que vous souhaitez utiliser comme clé d'un dict, alors le tuple est le chemin à parcourir.
pavon
C'est un bon conseil de Raymond Hettinger. Je dirais toujours qu'il existe un cas d'utilisation pour utiliser le constructeur de tuple avec un générateur, comme décompresser une autre structure, peut-être plus grande, en une plus petite en itérant sur les attr que vous souhaitez convertir en un enregistrement de tuple.
Dave
2
@dave Vous pouvez probablement simplement l'utiliser operator.itemgetterdans ce cas.
chepner
@chepner, je vois. C'est assez proche de ce que je veux dire. Il retourne un callable, donc si je n'ai besoin de le faire qu'une fois, je ne vois pas beaucoup de victoire contre une utilisation tuple(obj[item] for item in items)directe. Dans mon cas, j'intégrais cela dans une compréhension de liste pour faire une liste d'enregistrements de tuple. Si je dois le faire à plusieurs reprises dans le code, itemgetter a fière allure. Peut-être que itemgetter serait plus idiomatique de toute façon?
Dave
Je vois la relation entre frozenset et set analogue à celle de tuple et list. Il s'agit moins d'hétérogénéité et plus d'immuabilité - les ensembles de frozens et les tuples peuvent être des clés de dictionnaires, les listes et les ensembles ne peuvent pas en raison de leur mutabilité.
polyglotte
56

Depuis Python 3.5 , vous pouvez également utiliser la *syntaxe de décompression de splat pour décompresser une expression de générateur:

*(x for x in range(10)),
czheo
la source
2
C'est super (et ça marche), mais je ne trouve nulle part où c'est documenté! avez vous un lien?
felixphew
8
Remarque: En tant que détail d'implémentation, cela revient essentiellement à faire tuple(list(x for x in range(10)))( les chemins de code sont identiques , les deux construisant un list, la seule différence étant que la dernière étape consiste à créer un à tuplepartir du listet à jeter le listquand une tuplesortie est requis). Cela signifie que vous n'évitez pas réellement une paire de temporaires.
ShadowRanger
4
Pour développer le commentaire de @ShadowRanger, voici une question où ils montrent que la syntaxe littérale splat + tuple est en fait un peu plus lente que de passer une expression de générateur au constructeur de tuple.
Lucubrator
J'essaye ceci dans Python 3.7.3 et *(x for x in range(10))ne fonctionne pas. Je comprends SyntaxError: can't use starred expression here. Fonctionne cependant tuple(x for x in range(10)).
Ryan H.
4
@RyanH. vous devez mettre une virgule à la fin.
czheo
27

Comme l'a macmmentionné une autre affiche , le moyen le plus rapide de créer un tuple à partir d'un générateur est tuple([generator]).


Comparaison des performances

  • Compréhension de la liste:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Tuple de la compréhension de la liste:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Tuple du générateur:

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

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

Ma version de python :

$ python3 --version
Python 3.6.3

Vous devez donc toujours créer un tuple à partir d'une liste de compréhension, sauf si les performances ne sont pas un problème.

À M
la source
10
Remarque: tuplede listcomp nécessite une utilisation maximale de la mémoire en fonction de la taille combinée du final tupleet du list. tupled'un genexpr, bien que plus lent, signifie que vous ne payez que pour la finale tuple, pas temporaire list(le genexpr lui-même occupant une mémoire à peu près fixe). Généralement non significatif, mais il peut être important lorsque les tailles impliquées sont énormes.
ShadowRanger
25

La compréhension fonctionne en faisant une boucle ou une itération sur des éléments et en les affectant dans un conteneur, un tuple est incapable de recevoir des affectations.

Une fois un tuple créé, il ne peut pas être ajouté, étendu ou attribué à. La seule façon de modifier un Tuple est si l'un de ses objets peut lui-même être affecté (est un conteneur non-tuple). Parce que le tuple ne contient qu'une référence à ce type d'objet.

Aussi - un tuple a son propre constructeur tuple()que vous pouvez donner à n'importe quel itérateur. Ce qui signifie que pour créer un tuple, vous pouvez faire:

tuple(i for i in (1,2,3))
Inbar Rose
la source
9
À certains égards, je suis d'accord (ce n'est pas nécessaire parce qu'une liste suffira), mais à d'autres égards, je ne suis pas d'accord (le raisonnement étant parce qu'il est immuable). À certains égards, il est plus logique d'avoir une compréhension des objets immuables. qui le fait lst = [x for x in ...]; x.append()?
mgilson
@mgilson Je ne sais pas comment cela se rapporte à ce que j'ai dit?
Inbar Rose
2
@mgilson si un tuple est immuable, cela signifie que l'implémentation sous-jacente ne peut pas "générer" un tuple ("génération" impliquant la construction d'une seule pièce à la fois). immuable signifie que vous ne pouvez pas construire celui avec 4 pièces en modifiant celui avec 3 pièces. au lieu de cela, vous implémentez la «génération» de tuple en créant une liste, quelque chose conçu pour la génération, puis créez le tuple comme dernière étape et supprimez la liste. La langue reflète cette réalité. Considérez les tuples comme des structures C.
Scott
2
bien qu'il soit raisonnable que le sucre syntaxique des compréhensions fonctionne pour les tuples, puisque vous ne pouvez pas utiliser le tuple tant que la compréhension n'est pas retournée. En fait, il n'agit pas comme mutable, mais une compréhension de tuple pourrait se comporter un peu comme l'ajout de chaînes.
uchuugaka
12

Ma meilleure supposition est qu'ils ont manqué de crochets et ne pensaient pas que ce serait assez utile pour justifier l'ajout d'une syntaxe "laide" ...

mgilson
la source
1
Équerres non utilisées.
uchuugaka
@uchuugaka - Pas complètement. Ils sont utilisés pour les opérateurs de comparaison. Cela pourrait probablement encore se faire sans ambiguïté, mais cela ne vaut peut-être pas la peine ...
mgilson
3
@uchuugaka Il convient de noter que {*()}, bien que laid, il fonctionne comme un ensemble vide littéral!
MI Wright
1
Pouah. D'un point de vue esthétique, je pense que je suis set()
partisan
1
@QuantumMechanic: Ouais, c'est le point; les généralisations de déballage ont rendu possible le "set literal" vide. Notez que {*[]}c'est strictement inférieur aux autres options; la chaîne vide et empty tuple, étant immuables, sont des singletons, donc aucun temporaire n'est nécessaire pour construire le vide set. En revanche, le vide listn'est pas un singleton, vous devez donc le construire, l'utiliser pour construire le set, puis le détruire, perdant ainsi tout avantage de performance insignifiant fourni par l'opérateur singe borgne.
ShadowRanger
8

Les tuples ne peuvent pas être efficacement ajoutés comme une liste.

Ainsi, une compréhension de tuple devrait utiliser une liste en interne, puis la convertir en tuple.

Ce serait la même chose que ce que vous faites maintenant: tuple ([compréhension])

macm
la source
3

Les parenthèses ne créent pas de tuple. aka one = (two) n'est pas un tuple. Le seul moyen de contourner est soit un = (deux,) ou un = tuple (deux). Une solution est donc:

tuple(i for i in myothertupleorlistordict) 
ilias iliadis
la source
agréable. c'est presque pareil.
uchuugaka
-1

Je crois que c'est simplement par souci de clarté, nous ne voulons pas encombrer la langue avec trop de symboles différents. De plus, une tuplecompréhension n'est jamais nécessaire , une liste peut simplement être utilisée à la place avec des différences de vitesse négligeables, contrairement à une compréhension dict par opposition à une compréhension liste.

jamylak
la source
-2

Nous pouvons générer des tuples à partir d'une compréhension de liste. Le suivant ajoute deux nombres séquentiellement dans un tuple et donne une liste des nombres 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
la source