Pourquoi avons-nous besoin de tuples en Python (ou de tout type de données immuable)?

140

J'ai lu plusieurs didacticiels python (Dive Into Python, pour un) et la référence du langage sur Python.org - je ne vois pas pourquoi le langage a besoin de tuples.

Les tuples n'ont pas de méthode par rapport à une liste ou à un ensemble, et si je dois convertir un tuple en ensemble ou en liste pour pouvoir les trier, quel est l'intérêt d'utiliser un tuple en premier lieu?

Immutabilité?

Pourquoi quelqu'un se soucie-t-il qu'une variable se trouve à un endroit différent de la mémoire que lorsqu'elle a été allouée à l'origine? Toute cette affaire d'immuabilité en Python semble être surestimée.

En C / C ++, si j'alloue un pointeur et que je pointe vers une mémoire valide, je me fiche de l'emplacement de l'adresse tant qu'elle n'est pas nulle avant de l'utiliser.

Chaque fois que je référence cette variable, je n'ai pas besoin de savoir si le pointeur pointe toujours vers l'adresse d'origine ou non. Je vérifie juste la valeur null et l'utilise (ou non).

En Python, lorsque j'alloue une chaîne (ou un tuple), l'assigne à x, puis modifie la chaîne, pourquoi est-ce que je me soucie si c'est l'objet d'origine? Tant que la variable pointe vers mes données, c'est tout ce qui compte.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x fait toujours référence aux données que je veux, pourquoi quelqu'un doit-il se soucier de savoir si son identifiant est le même ou différent?

pyNewGuy
la source
12
vous prêtez attention au mauvais aspect de la mutabilité: "si l'identifiant est identique ou différent" n'est qu'un effet secondaire; "si les données pointées par d'autres références qui pointaient auparavant vers le même objet reflètent maintenant des mises à jour" est critique.
Charles Duffy

Réponses:

124
  1. les objets immuables peuvent permettre une optimisation substantielle; c'est vraisemblablement la raison pour laquelle les chaînes sont également immuables en Java, développées assez séparément mais à peu près en même temps que Python, et à peu près tout est immuable dans des langages vraiment fonctionnels.

  2. en Python en particulier, seuls les immuables peuvent être hachables (et, par conséquent, les membres des ensembles, ou les clés dans les dictionnaires). Encore une fois, cela permet une optimisation, mais bien plus que simplement "substantielle" (concevoir des tables de hachage décentes stockant des objets complètement mutables est un cauchemar - soit vous prenez des copies de tout dès que vous le hachez, soit le cauchemar de vérifier si le hachage de l'objet a changé depuis que vous avez pris une référence pour la dernière fois, il a la tête laide).

Exemple de problème d'optimisation:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop
Alex Martelli
la source
11
@musicfreak, voyez la modification que je viens de faire où la construction d'un tuple est plus de 7,6 fois plus rapide que la construction de la liste équivalente - maintenant vous ne pouvez plus dire que vous "n'avez jamais vu de différence notable", à moins que votre définition de "perceptible" "est vraiment particulier ...
Alex Martelli
11
@musicfreak Je pense que vous abusez de "l'optimisation prématurée est la racine de tout mal". Il y a une énorme différence entre faire une optimisation prématurée dans une application (par exemple, dire "les tuples sont plus rapides que les listes, donc nous n'utiliserons que des tuples dans toute l'application!") Et faire des benchmarks. Le benchmark d'Alex est perspicace et le fait de savoir que la création d'un tuple est plus rapide que la construction d'une liste pourrait nous aider dans les futures opérations d'optimisation (quand c'est vraiment nécessaire).
Virgil Dupras
5
@Alex, est-ce que "construire" un tuple est vraiment plus rapide que "construire une liste", ou voyons-nous le résultat de la mise en cache du tuple par l'exécution Python? Cela me semble.
Triptyque du
6
@ACoolie, c'est totalement dominé par les randomappels (essayez de faire juste ça, vous verrez!), Donc pas très significatif. Essayez python -mtimeit -s "x=23" "[x,x]"et vous verrez une accélération plus significative de 2-3 fois pour la construction du tuple par rapport à la construction de la liste.
Alex Martelli
9
pour quiconque se demande - nous avons pu réduire plus d'une heure de traitement de données en passant des listes aux tuples.
Mark Ribau
42

Aucune des réponses ci-dessus ne souligne le vrai problème des tuples par rapport aux listes, que de nombreux nouveaux utilisateurs de Python semblent ne pas comprendre pleinement.

Les tuples et les listes ont des objectifs différents. Les listes stockent des données homogènes. Vous pouvez et devriez avoir une liste comme celle-ci:

["Bob", "Joe", "John", "Sam"]

La raison pour laquelle une utilisation correcte des listes est due est que ce sont tous des types de données homogènes, en particulier les noms de personnes. Mais prenez une liste comme celle-ci:

["Billy", "Bob", "Joe", 42]

Cette liste est le nom complet d'une personne et son âge. Ce n'est pas un type de données. La manière correcte de stocker ces informations est soit dans un tuple, soit dans un objet. Disons que nous en avons quelques-uns:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

L'immuabilité et la mutabilité des tuples et des listes ne sont pas la principale différence. Une liste est une liste du même type d'éléments: fichiers, noms, objets. Les tuples sont un regroupement de différents types d'objets. Ils ont des utilisations différentes et de nombreux codeurs Python abusent des listes pour savoir à quoi servent les tuples.

Veuillez ne pas le faire.


Éditer:

Je pense que cet article de blog explique pourquoi je pense que c'est mieux que moi: http://news.e-scribe.com/397

Grant Paul
la source
13
Je pense que vous avez une vision qui n'est pas du moins approuvée par moi, je ne connais pas les autres.
Stefano Borini
13
Je suis également fortement en désaccord avec cette réponse. L'homogénéité des données n'a absolument rien à voir avec l'utilisation d'une liste ou d'un tuple. Rien en Python ne suggère cette distinction.
Glenn Maynard
14
Guido a également fait valoir ce point il y a quelques années. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy
11
Même si Guido (le concepteur de Python) a prévu que les listes soient utilisées pour les données homogènes et les tuples pour les hétérogènes, le fait est que le langage ne l'impose pas. Par conséquent, je pense que cette interprétation est plus une question de style qu'autre chose. Il se trouve que dans les cas d'utilisation typiques de nombreuses personnes, les listes ont tendance à être de type tableau et les tuples ont tendance à ressembler à des enregistrements. Mais cela ne devrait pas empêcher les gens d'utiliser des listes pour des données hétérogènes si cela convient mieux à leur problème. Comme le dit le Zen de Python: La praticité bat la pureté.
John Y
9
@Glenn, vous vous trompez fondamentalement. L'une des principales utilisations des tuples est en tant que type de données composite pour stocker plusieurs éléments de données liés. Le fait que vous puissiez parcourir un tuple et effectuer plusieurs des mêmes opérations ne change rien à cela. (Comme référence, considérez que les tuples dans de nombreux autres langages n'ont pas les mêmes fonctionnalités itérables que leurs homologues de liste)
HS.
22

si je dois convertir un tuple en un ensemble ou une liste pour pouvoir les trier, quel est l'intérêt d'utiliser un tuple en premier lieu?

Dans ce cas particulier, il n'y a probablement pas de raison. Ce n'est pas un problème, car ce n'est pas l'un des cas où vous envisageriez d'utiliser un tuple.

Comme vous le faites remarquer, les tuples sont immuables. Les raisons d'avoir des types immuables s'appliquent aux tuples:

  • efficacité de copie: plutôt que de copier un objet immuable, vous pouvez l'aliaser (lier une variable à une référence)
  • efficacité de comparaison: lorsque vous utilisez la copie par référence, vous pouvez comparer deux variables en comparant l'emplacement plutôt que le contenu
  • interning: vous devez stocker au plus une copie de toute valeur immuable
  • il n'est pas nécessaire de synchroniser l'accès aux objets immuables dans le code simultané
  • const exactitude: certaines valeurs ne devraient pas être autorisées à changer. C'est (pour moi) la principale raison des types immuables.

Notez qu'une implémentation Python particulière peut ne pas utiliser toutes les fonctionnalités ci-dessus.

Les clés de dictionnaire doivent être immuables, sinon la modification des propriétés d'un objet-clé peut invalider les invariants de la structure de données sous-jacente. Les tuples peuvent donc potentiellement être utilisés comme clés. C'est une conséquence de l'exactitude des const.

Voir aussi « Présentation des tuples », de Dive Into Python .

outis
la source
2
id ((1,2,3)) == id ((1,2,3)) est faux. Vous ne pouvez pas comparer les tuples simplement en comparant l'emplacement, car il n'y a aucune garantie qu'ils ont été copiés par référence.
Glenn Maynard
@Glenn: Notez la remarque de qualification "lorsque vous utilisez la copie par référence". Alors que le codeur peut créer sa propre implémentation, la copie par référence des tuples est en grande partie l'affaire de l'interpréteur / compilateur. Je faisais principalement référence à la façon dont ==est mis en œuvre au niveau de la plate-forme.
sortie le
1
@Glenn: notez également que la copie par référence ne s'applique pas aux tuples dans (1,2,3) == (1,2,3). C'est plus une question de stage.
sortie le
Comme je l'ai dit assez clairement, rien ne garantit qu'ils ont été copiés par référence . Les tuples ne sont pas internés en Python; c'est un concept de chaîne.
Glenn Maynard
Comme je l'ai dit très clairement: je ne parle pas du programmeur qui compare les tuples en comparant l'emplacement. Je parle de la possibilité que la plate-forme puisse, qui peut garantir la copie par référence. En outre, l'internalisation peut être appliquée à n'importe quel type immuable, pas seulement aux chaînes. L'implémentation principale de Python peut ne pas interner des types immuables, mais le fait que Python ait des types immuables fait de l'internement une option.
sortie le
15

Parfois, nous aimons utiliser des objets comme clés de dictionnaire

Pour ce que ça vaut, les tuples récemment (2.6+) se sont développés index()et les count()méthodes

John La Rooy
la source
5
+1: une liste mutable (ou un ensemble mutable ou un dictionnaire mutable) comme clé de dictionnaire ne peut pas fonctionner. Nous avons donc besoin de listes immuables ("tuples"), d'ensembles figés et ... enfin ... d'un dictionnaire figé, je suppose.
S.Lott
9

J'ai toujours trouvé qu'avoir deux types complètement séparés pour la même structure de données de base (tableaux) était une conception maladroite, mais pas un réel problème dans la pratique. (Chaque langage a ses verrues, Python inclus, mais ce n'est pas important.)

Pourquoi quelqu'un se soucie-t-il qu'une variable se trouve à un endroit différent de la mémoire que lorsqu'elle a été allouée à l'origine? Toute cette affaire d'immuabilité en Python semble être surestimée.

Ce sont des choses différentes. La mutabilité n'est pas liée à l'endroit où elle est stockée en mémoire; cela signifie que les choses sur lesquelles il pointe ne peuvent pas changer.

Les objets Python ne peuvent pas changer d'emplacement après leur création, modifiables ou non. (Plus précisément, la valeur de id () ne peut pas changer - même chose, en pratique.) Le stockage interne des objets mutables peut changer, mais c'est un détail d'implémentation caché.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

Cela ne modifie pas ("mute") la variable; il crée une nouvelle variable portant le même nom et supprime l'ancienne. Comparer à une opération de mutation:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

Comme d'autres l'ont souligné, cela permet d'utiliser des tableaux comme clés de dictionnaires et d'autres structures de données qui nécessitent l'immuabilité.

Notez que les clés des dictionnaires ne doivent pas être complètement immuables. Seule la partie de celui-ci utilisée comme clé doit être immuable; pour certaines utilisations, il s'agit d'une distinction importante. Par exemple, vous pouvez avoir une classe représentant un utilisateur, qui compare l'égalité et un hachage par le nom d'utilisateur unique. Vous pouvez alors suspendre d'autres données modifiables sur la classe - "l'utilisateur est connecté", etc. Puisque cela n'affecte pas l'égalité ou le hachage, il est possible et parfaitement valide de l'utiliser comme clé dans un dictionnaire. Ce n'est pas trop souvent nécessaire en Python; Je le souligne simplement car plusieurs personnes ont affirmé que les clés doivent être "immuables", ce qui n'est que partiellement correct. Cependant, je l'ai utilisé plusieurs fois avec des cartes et des ensembles C ++.

Glenn Maynard
la source
>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Vous ' Je viens de modifier un type de données mutable, donc cela n'a pas de sens - lié à la question d'origine. x = 'bonjour "id (x) 12345 x =" au revoir "id (x) 65432 Peu importe si c'est un nouvel objet ou non. Tant que x pointe vers les données que j'ai assignées, c'est tout ce qui compte.
pyNewGuy
4
Vous êtes confus bien au-delà de ma capacité à vous aider.
Glenn Maynard
+1 pour signaler la confusion dans les sous-questions, qui semblent être la principale source de difficulté à percevoir la valeur des tuples.
dehors le
1
Si je pouvais, un autre +1 pour avoir souligné que la vraie rubrique pour les clés est de savoir si l'objet est hachable ou non ( docs.python.org/glossary.html#term-hashable ).
sortie le
7

Comme gnibbler l'a proposé dans un commentaire, Guido avait une opinion qui n'est pas entièrement acceptée / appréciée: «les listes sont pour des données homogènes, les tuples sont pour des données hétérogènes». Bien sûr, de nombreux opposants ont interprété cela comme signifiant que tous les éléments d'une liste devraient être du même type.

J'aime voir les choses différemment, comme d' autres l' ont également fait dans le passé:

blue= 0, 0, 255
alist= ["red", "green", blue]

Notez que je considère alist comme homogène, même si type (alist [1])! = Type (alist [2]).

Si je peux changer l'ordre des éléments et que je n'aurai pas de problèmes dans mon code (en dehors des hypothèses, par exemple «il devrait être trié»), alors une liste devrait être utilisée. Sinon (comme dans le tuple blueci-dessus), alors je devrais utiliser un tuple.

tzot
la source
Si je pouvais, je voterais cette réponse 15 fois. C'est exactement ce que je ressens à propos des tuples.
Grant Paul
6

Ils sont importants car ils garantissent à l'appelant que l'objet qu'ils passent ne sera pas muté. Si tu fais ça:

a = [1,1,1]
doWork(a)

L'appelant n'a aucune garantie de la valeur de a après l'appel. cependant,

a = (1,1,1)
doWorK(a)

Maintenant, en tant qu'appelant ou en tant que lecteur de ce code, vous savez que a est le même. Vous pouvez toujours pour ce scénario faire une copie de la liste et la transmettre, mais maintenant vous perdez des cycles au lieu d'utiliser une construction de langage qui a plus de sens sémantique.

Matthieu Manela
la source
1
C'est une propriété très secondaire des tuples. Il y a trop de cas où vous avez un objet mutable que vous voulez passer à une fonction et ne pas le faire modifier, qu'il s'agisse d'une liste préexistante ou d'une autre classe. Il n'y a tout simplement pas de concept de "paramètres const par référence" en Python (par exemple. Const foo & en C ++). Il se trouve que les tuples vous donnent cela s'il s'avère pratique d'utiliser un tuple, mais si vous avez reçu une liste de votre appelant, allez-vous vraiment la convertir en tuple avant de la passer ailleurs?
Glenn Maynard
Je suis d'accord avec vous là-dessus. Un tuple n'est pas la même chose que le slapping sur un mot clé const. Mon point est que l'immuabilité d'un tuple a une signification supplémentaire pour le lecteur du code. Étant donné une situation où les deux fonctionneraient et où vous vous attendez à ce que cela ne change pas, l'utilisation du tuple ajoutera une signification supplémentaire pour le lecteur (tout en garantissant cela également)
Matthew Manela
a = [1,1,1] doWork (a) si dowork () est défini comme def dowork (arg): arg = [0,0,0] appeler dowork () sur une liste ou un tuple a le même résultat
pyNewGuy
1

vous pouvez voir ici pour une discussion à ce sujet

ghostdog74
la source
1

Votre question (et les commentaires de suivi) se concentrent sur le fait de savoir si l'id () change pendant une affectation. Se concentrer sur cet effet consécutif de la différence entre le remplacement d'objet immuable et la modification d'objet mutable plutôt que sur la différence elle-même n'est peut-être pas la meilleure approche.

Avant de continuer, assurez-vous que le comportement illustré ci-dessous correspond à ce que vous attendez de Python.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

Dans ce cas, le contenu de a2 a été modifié, même si seul a1 avait une nouvelle valeur attribuée. Contrairement à ce qui suit:

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

Dans ce dernier cas, nous avons remplacé la liste entière, plutôt que de mettre à jour son contenu. Avec les types immuables tels que les tuples, c'est le seul comportement autorisé.

Pourquoi est-ce important? Disons que vous avez un dict:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

En utilisant un tuple, le dictionnaire est sûr de voir ses clés changées "hors de dessous" en éléments qui ont un hachage à une valeur différente. Ceci est essentiel pour permettre une mise en œuvre efficace.

Charles Duffy
la source
Comme d'autres l'ont souligné, immuabilité! = Hashabilité. Tous les tuples ne peuvent pas être utilisés comme clés de dictionnaire: {([1], [2]): 'value'} échoue car les listes mutables dans le tuple peuvent être modifiées, mais {((1), (2)): ' value '} est OK.
Ned Deily
Ned, c'est vrai, mais je ne suis pas sûr que la distinction soit pertinente à la question posée.
Charles Duffy
@ K.Nicholas, la modification que vous avez approuvée ici a changé le code de manière à affecter un entier, pas un tuple, du tout - faisant échouer les opérations d'index ultérieures, de sorte qu'ils n'auraient pas pu tester que le nouveau transcription était en fait possible. Problème correctement identifié, bien sûr; solution invalide.
Charles Duffy
@MichaelPuckettII, de même, voir ci-dessus.
Charles Duffy