45 votes

Comment une chaîne non assignée en Python peut-elle avoir une adresse en mémoire ?

Quelqu'un peut-il m'expliquer ? J'ai joué avec la commande id() en python et je suis tombé sur ceci :

>>> id('cat')
5181152
>>> a = 'cat'
>>> b = 'cat'
>>> id(a)
5181152
>>> id(b)
5181152

Cela a du sens pour moi, sauf pour une partie : La chaîne 'cat' a une adresse en mémoire avant que je l'assigne à une variable. Je ne comprends probablement pas comment fonctionne l'adressage de la mémoire, mais quelqu'un peut-il m'expliquer cela ou au moins me dire que je devrais me documenter sur l'adressage de la mémoire ?

Tout cela est bien beau, mais cela m'a encore plus troublé :

>>> a = a[0:2]+'t'
>>> a
'cat'
>>> id(a)
39964224
>>> id('cat')
5181152

Ça m'a paru bizarre parce que chat est une chaîne de caractères avec une adresse de 5181152 mais le nouveau a a une adresse différente. Donc s'il y a deux chat dans la mémoire, pourquoi deux adresses ne sont-elles pas imprimées pour id('chat') ? Ma dernière idée était que la concaténation avait quelque chose à voir avec le changement d'adresse, alors j'ai essayé ceci :

>>> id(b[0:2]+'t')
39921024
>>> b = b[0:2]+'t'
>>> b
'cat'
>>> id(b)
40000896

J'aurais pensé que les identifiants seraient les mêmes, mais ce n'est pas le cas. Qu'en pensez-vous ?

52voto

kindall Points 60645

Python réutilise les chaînes de caractères de manière assez agressive. Les règles par lesquelles il le fait sont dépendantes de l'implémentation, mais CPython en utilise deux à ma connaissance :

  • Les chaînes qui ne contiennent que des caractères valides dans les identifiants Python sont les suivantes interné, ce qui signifie qu'ils sont stockés dans une grande table et réutilisés partout où ils apparaissent. Ainsi, quel que soit l'endroit où vous utilisez "cat" il fait toujours référence à la même chaîne de caractères.
  • Les chaînes littérales d'un même bloc de code sont réutilisées indépendamment de leur contenu et de leur longueur. Si vous placez un littéral de chaîne de l'intégralité du discours de Gettysburg dans une fonction, deux fois, il s'agit du même objet chaîne les deux fois. Dans des fonctions distinctes, il s'agit d'objets différents : def foo(): return "pack my box with five dozen liquor jugs" def bar(): return "pack my box with five dozen liquor jugs" assert foo() is bar() # AssertionError

Les deux optimisations sont effectuées au moment de la compilation (c'est-à-dire lorsque le bytecode est généré).

D'autre part, quelque chose comme chr(99) + chr(97) + chr(116) est une chaîne de caractères expression qui évalue la chaîne de caractères "cat" . Dans un langage dynamique comme Python, sa valeur ne peut être connue au moment de la compilation ( chr() est une fonction intégrée, mais vous l'avez peut-être réassignée), elle n'est donc normalement pas internée. Ainsi, son id() est différent de celui de "cat" . Cependant, vous pouvez forcer l'internalisation d'une chaîne de caractères en utilisant la fonction intern() fonction. Ainsi :

id(intern(chr(99) + chr(97) + chr(116))) == id("cat")   # True

Comme d'autres l'ont mentionné, l'internat est possible car les chaînes de caractères sont immuables. Il n'est pas possible de modifier "cat" à "dog" en d'autres termes. Vous devez générer un nouvel objet chaîne, ce qui signifie qu'il n'y a aucun risque que d'autres noms pointant vers la même chaîne soient affectés.

À titre d'information, Python convertit également les expressions ne contenant que des constantes (telles que "c" + "a" + "t" ) en constantes au moment de la compilation, comme le montre le désassemblage ci-dessous. Celles-ci seront optimisées pour pointer vers des objets chaîne identiques selon les règles ci-dessus.

>>> def foo(): "c" + "a" + "t"
...
>>> from dis import dis; dis(foo)
  1           0 LOAD_CONST               5 ('cat')
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE

47voto

David Heffernan Points 292687

'cat' a une adresse parce que vous la créez afin de la transmettre à id() . Vous ne l'avez pas encore lié à un nom, mais l'objet existe toujours.

Python met en cache et réutilise les chaînes courtes. Mais si vous assemblez les chaînes par concaténation, le code qui recherche dans le cache et tente de les réutiliser est contourné.

Notez que le fonctionnement interne du cache des chaînes de caractères est un pur détail d'implémentation et ne doit pas être invoqué.

17voto

Ned Batchelder Points 128913

Toutes les valeurs doivent résider quelque part en mémoire. C'est pourquoi id('cat') produit une valeur. Vous l'appelez une chaîne "inexistante", mais il est clair qu'elle existe, elle n'a juste pas encore été assignée à un nom.

Les chaînes de caractères sont immuables, de sorte que l'interpréteur peut faire des choses astucieuses comme faire en sorte que toutes les instances du littéral 'cat' sont le même objet, de sorte que id(a) y id(b) sont les mêmes.

Opérer sur des cordes produira de nouvelles cordes. Il peut s'agir ou non des mêmes chaînes que les chaînes précédentes avec le même contenu.

Notez que tous ces détails sont des détails d'implémentation de CPython, et qu'ils peuvent changer à tout moment. Vous n'avez pas besoin de vous préoccuper de ces questions dans les programmes réels.

8voto

IfLoop Points 59461

Les variables Python sont assez différentes des variables d'autres langages (comme le C).

Dans de nombreux autres langages, une variable est le nom d'un emplacement en mémoire. Dans ces langues, différents types de variables peuvent faire référence à différents types d'emplacements, et le même emplacement peut recevoir plusieurs noms. Dans la plupart des cas, les données d'un emplacement mémoire donné peuvent changer de temps en temps. Il existe également des moyens de faire référence à des emplacements mémoire de manière indirecte ( int *p contiendrait l'adresse, et dans l'emplacement mémoire à cette adresse, il y a un entier). Mais l'emplacement réel auquel une variable fait référence ne peut pas changer ; la variable est l'emplacement. Une affectation de variable dans ces langues est en fait "Cherchez l'emplacement de cette variable, et copiez ces données dans cet emplacement".

Python ne fonctionne pas de cette manière. En Python, les objets réels sont placés dans un emplacement mémoire, et les variables sont comme des étiquettes pour les emplacements. Python gère les valeurs stockées de manière distincte de la façon dont il gère les variables. Essentiellement, une affectation en Python signifie "Cherchez les informations relatives à cette variable, oubliez l'emplacement auquel elle se réfère déjà et remplacez-le par ce nouvel emplacement". Aucune donnée n'est copiée.

Une caractéristique commune aux langages qui fonctionnent comme python (par opposition au premier type dont nous avons parlé plus tôt) est que certains types d'objets sont gérés d'une manière spéciale ; les valeurs identiques sont mises en cache pour ne pas occuper de mémoire supplémentaire et pour pouvoir être comparées très facilement (si elles ont la même adresse, elles sont égales). Ce processus est appelé stage Tous les littéraux de chaînes python sont internés (en plus de quelques autres types), bien que les chaînes créées dynamiquement puissent ne pas l'être.

Dans votre code exact, le dialogue sémantique serait :

# before anything, since 'cat' is a literal constant, add it to the intern cache
>>> id('cat') # grab the constant 'cat' from the intern cache and look up 
              # it's address
5181152
>>> a = 'cat' # grab the constant 'cat' from the intern cache and 
              # make the variable "a" point to it's location 
>>> b = 'cat' # do the same thing with the variable "b"
>>> id(a) # look up the object "a" currently points to, 
          # then look up that object's address
5181152
>>> id(b) # look up the object "b" currently points to, 
          # then look up that object's address
5181152

1voto

Heath Hunnicutt Points 9801

Le code que vous avez posté crée de nouvelles chaînes de caractères comme objets intermédiaires. Ces chaînes créées ont finalement le même contenu que vos originaux. Dans la période intermédiaire, elles ne correspondent pas exactement à l'original, et doivent être conservées à une adresse distincte.

>>> id('cat')
5181152

Comme d'autres l'ont répondu, en émettant ces instructions, vous demandez à la VM Python de créer un objet chaîne contenant la chaîne "cat". Cet objet chaîne est mis en cache et se trouve à l'adresse 5181152.

>>> a = 'cat'
>>> id(a)
5181152

Encore une fois, a a été attribué pour faire référence à cet objet chaîne mis en cache à 5181152, contenant "cat".

>>> a = a[0:2]
>>> id(a)
27731511

À ce stade, dans ma version modifiée de votre programme, vous avez créé deux petits objets de type chaîne : 'cat' y 'ca' . 'cat' existe toujours dans le cache. La chaîne de caractères à laquelle a renvoie à un objet chaîne différent et probablement nouveau, contenant les caractères 'ca' .

>>> a = a + 't'
>>> id(a)
39964224

Vous avez maintenant créé un nouvel objet chaîne. Cet objet est la concaténation de la chaîne de caractères 'ca' à l'adresse 27731511, et la chaîne de caractères 't' . Cette concaténation correspond à la chaîne précédemment mise en cache. 'cat' . Python ne détecte pas automatiquement ce cas. Comme l'a indiqué kindall, vous pouvez forcer la recherche avec l'option intern() méthode.

Nous espérons que cette explication éclaire les étapes par lesquelles l'adresse de a changé.

Votre code n'a pas inclus l'état intermédiaire avec a a attribué la chaîne de caractères 'ca' . La réponse est toujours valable, car l'interpréteur Python génère un nouvel objet chaîne pour contenir le résultat intermédiaire. a[0:2] que vous assigniez ou non ce résultat intermédiaire à une variable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X