¿Cuándo Python asigna nueva memoria para cadenas idénticas?

Dos cadenas de Python con los mismos caracteres, a == b, pueden compartir memoria, id (a) == id (b), o pueden estar en la memoria dos veces, id (a)! = Id (b). Tratar

ab = "ab" print id( ab ), id( "a"+"b" ) 

Aquí Python reconoce que la nueva “a” + “b” creada es la misma que la “ab” que ya está en la memoria, no está mal.

Ahora considere una lista N-larga de nombres de estados [“Arizona”, “Alaska”, “Alaska”, “California” …] (N ~ 500000 en mi caso).
Veo 50 identificaciones diferentes () s ⇒ cada cadena “Arizona” … se almacena solo una vez, está bien.
PERO escriba la lista en el disco y vuelva a leerla de nuevo: la lista “igual” ahora tiene N identificaciones diferentes () mucho más memoria, vea más abajo.

¿Cómo es posible que alguien explique la asignación de memoria de cadenas de Python?

 """ when does Python allocate new memory for identical strings ? ab = "ab" print id( ab ), id( "a"+"b" ) # same ! list of N names from 50 states: 50 ids, mem ~ 4N + 50S, each string once but list > file > mem again: N ids, mem ~ N * (4 + S) """ from __future__ import division from collections import defaultdict from copy import copy import cPickle import random import sys states = dict( AL = "Alabama", AK = "Alaska", AZ = "Arizona", AR = "Arkansas", CA = "California", CO = "Colorado", CT = "Connecticut", DE = "Delaware", FL = "Florida", GA = "Georgia", ) def nid(alist): """ nr distinct ids """ return "%d ids %d pickle len" % ( len( set( map( id, alist ))), len( cPickle.dumps( alist, 0 ))) # rough est ? # cf http://stackoverflow.com/questions/2117255/python-deep-getsizeof-list-with-contents N = 10000 exec( "\n".join( sys.argv[1:] )) # var=val ... random.seed(1) # big list of random names of states -- names = [] for j in xrange(N): name = copy( random.choice( states.values() )) names.append(name) print "%d strings in mem: %s" % (N, nid(names) ) # 10 ids, even with copy() # list to a file, back again -- each string is allocated anew joinsplit = "\n".join(names).split() # same as > file > mem again assert joinsplit == names print "%d strings from a file: %s" % (N, nid(joinsplit) ) # 10000 strings in mem: 10 ids 42149 pickle len # 10000 strings from a file: 10000 ids 188080 pickle len # Python 2.6.4 mac ppc 

Añadido 25jan:
Hay dos tipos de cadenas en la memoria de Python (o en cualquier progtwig):

  • Cadenas, en un Ucache de cadenas únicas: estas ahorran memoria y hacen a == b rápido si ambas están en Ucache
  • Ostrings, los otros, que pueden ser almacenados cualquier número de veces.

intern(astring) pone astring en el Ucache (Alex +1); aparte de eso, no sabemos nada en absoluto acerca de cómo Python mueve Ostrings a Ucache: ¿cómo entró “a” + “b” después de “ab”? (“Cadenas de archivos” no tiene sentido, no hay forma de saberlo).
En resumen, los Ucaches (pueden haber varios) permanecen turbios.

Una nota histórica: SPITBOL uniquified todas las cadenas ca. 1970.

Cada implementación del lenguaje Python es libre de hacer sus propias concesiones al asignar objetos inmutables (como cadenas), ya sea haciendo uno nuevo, o encontrando uno igual existente y usando una referencia más a él, están bien de acuerdo con el lenguaje punto de vista. En la práctica, por supuesto, la implementación en el mundo real implica un compromiso razonable: una referencia más a un objeto existente adecuado al ubicar un objeto de este tipo es fácil y barato, solo haga un nuevo objeto si la tarea de ubicar uno existente adecuado (que puede o puede que no exista) parece que podría llevar mucho tiempo la búsqueda.

Así, por ejemplo, las repeticiones múltiples de la misma cadena literal dentro de una sola función (en todas las implementaciones que conozco) usarán la estrategia de “nueva referencia al mismo objeto”, porque al crear el conjunto de constantes de esa función es bastante rápido y fácil evitar duplicados; pero hacerlo a través de funciones separadas podría ser una tarea que puede llevar mucho tiempo, por lo que las implementaciones en el mundo real no lo hacen en absoluto, o solo lo hacen en algún subconjunto de casos identificados heurísticamente donde uno puede esperar una compensación razonable de tiempo de comstackción (disminuido al buscar constantes idénticas existentes) vs consumo de memoria (se incrementa si se siguen realizando nuevas copias de constantes).

No conozco ninguna implementación de Python (o, de hecho, otros lenguajes con cadenas constantes, como Java) que se toma la molestia de identificar posibles duplicados (para reutilizar un solo objeto a través de varias referencias) al leer datos de un archivo: – simplemente no parece ser una compensación prometedora (y aquí estaría pagando el tiempo de ejecución , no el tiempo de comstackción , por lo que la compensación es aún menos atractiva). Por supuesto, si sabe (gracias a las consideraciones de nivel de la aplicación) que tales objetos inmutables son grandes y bastante propensos a muchas duplicaciones, puede implementar su propia estrategia de “grupo de constantes” con bastante facilidad ( intern puede ayudarlo a hacerlo por cadenas, pero no es difícil hacer rodar las suyas propias (por ejemplo, tuplas con elementos inmutables, enteros largos y enormes, etc.).

Sospecho fuertemente que Python se está comportando como muchos otros idiomas aquí: reconoce las constantes de cadena dentro de su código fuente y usa una tabla común para ellas, pero no aplica las mismas reglas al crear cadenas dinámicamente. Esto tiene sentido ya que solo habrá un conjunto finito de cadenas dentro de su código fuente (aunque Python le permite evaluar el código dinámicamente, por supuesto), mientras que es mucho más probable que cree un gran número de cadenas en el curso de su progtwig. .

Este proceso generalmente se denomina interning y, de hecho, por el aspecto de esta página también se denomina interning en Python.

Una nota al margen: es muy importante conocer la vida útil de los objetos en Python. Tenga en cuenta la siguiente sesión:

 Python 2.6.4 (r264:75706, Dec 26 2009, 01:03:10) [GCC 4.3.4] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> a="a" >>> b="b" >>> print id(a+b), id(b+a) 134898720 134898720 >>> print (a+b) is (b+a) False 

Su pensamiento de que al imprimir las ID de dos expresiones separadas y al anotar “son iguales ergo las dos expresiones deben ser iguales / equivalentes / iguales” es defectuoso . Una sola línea de salida no implica necesariamente que todos sus contenidos hayan sido creados y / o coexistieron en el mismo momento en el tiempo.

Si desea saber si dos objetos son el mismo objeto, pregúntele a Python directamente (usando el operador is ).

 x = 42 y = 42 x == y #True x is y #True 

En esta interacción, X e Y deben ser == (mismo valor), pero no es (mismo objeto) porque ejecutamos dos expresiones literales diferentes. Sin embargo, dado que los enteros y las cadenas pequeñas se almacenan en caché y se reutilizan , se nos dice que hacen referencia al mismo objeto único.

De hecho, si realmente quiere mirar debajo del capó, siempre puede preguntarle a Python cuántas referencias hay a un objeto que usa la función getrefcount en el módulo sys estándar devuelve el conteo de referencias del objeto. Este comportamiento refleja una de las muchas formas en que Python optimiza su modelo para la velocidad de ejecución.

Aprendiendo Python

Encontré un buen artículo para explicar el comportamiento intern de CPython: http://guilload.com/python-string-interning/

En breve:

  1. El objeto String en CPython tiene un indicador para indicar que está en intern .
  2. La cadena interna almacenándola en un diccionario normal con claves y valores son los punteros de cadena. Esto acepta solo la clase de string .
  3. Interning ayuda a Python a reducir el consumo de memoria porque los objetos pueden referirse a la misma dirección de memoria y acelerar la velocidad de comparación porque solo tiene que comparar los punteros de la cadena.
  4. Python hace el intern en el proceso de comstackción, lo que significa que solo las cadenas literales (o cadena se pueden calcular en tiempo de comstackción, como ‘hola’ + ‘mundo’)
  5. Para su pregunta: solo las cadenas con longitud 0 o longitud 1 o que contiene solo letras ASCII (az, AZ, 0-9) están internadas
  6. Intern trabajos Intern en Python debido a que las cadenas son inmutables, de lo contrario no tiene sentido.

Este es un muy buen artículo, le sugiero que visite su sitio y busque otros que valgan la pena.