Me pregunto si:
a = "abcdef" b = "def" if a[3:] == b: print("something")
¿realmente realiza una copia de la parte "def" de a
lugar en la memoria, o si la verificación de letras se realiza en el lugar?
Nota: estoy hablando de una cadena, no de una lista (para la cual sé la respuesta)
Posible punto de conversación (siéntase libre de editar agregando información).
Acabo de escribir esta prueba para verificar empíricamente cuál podría ser la respuesta a la pregunta (esto no puede y no quiere ser una respuesta cierta).
import sys a = "abcdefg" print("a id:", id(a)) print("a[2:] id:", id(a[2:])) print("a[2:] is a:", a[2:] is a) print("Empty string memory size:", sys.getsizeof("")) print("a memory size:", sys.getsizeof(a)) print("a[2:] memory size:", sys.getsizeof(a[2:]))
Producción:
a id: 139796109961712 a[2:] id: 139796109962160 a[2:] is a: False Empty string memory size: 49 a memory size: 56 a[2:] memory size: 54
Como podemos ver aquí:
a
y a[2:]
son diferentesa
y a[2:]
es consistente con la memoria ocupada por una cadena con ese número de caracteres asignadosEl corte de cadenas hace una copia en CPython.
Mirando en la fuente, esta operación se maneja en unicodeobject.c:unicode_subscript
. Evidentemente, existe un caso especial para reutilizar la memoria cuando el paso es 1, el inicio es 0 y todo el contenido de la cadena se divide; esto entra en unicode_result_unchanged
y no habrá una copia. Sin embargo, el caso general llama a PyUnicode_Substring
donde todos los caminos conducen a un memcpy
.
Para verificar empíricamente estas afirmaciones, puede usar una herramienta de generación de perfiles de memoria stdlib tracemalloc
:
# s.py import tracemalloc tracemalloc.start() before = tracemalloc.take_snapshot() a = "." * 7 * 1024**2 # 7 MB of ..... # line 6, first alloc b = a[1:] # line 7, second alloc after = tracemalloc.take_snapshot() for stat in after.compare_to(before, 'lineno')[:2]: print(stat)
Debería ver las dos estadísticas principales como esta:
/tmp/s.py:6: size=7168 KiB (+7168 KiB), count=1 (+1), average=7168 KiB /tmp/s.py:7: size=7168 KiB (+7168 KiB), count=1 (+1), average=7168 KiB
Este resultado muestra dos asignaciones de 7 megas, fuerte evidencia de la copia de memoria, y se indicarán los números de línea exactos de esas asignaciones.
Intente cambiar el segmento de b = a[1:]
a b = a[0:]
para ver el caso especial de cadena completa en efecto: ahora solo debería haber una asignación grande, y sys.getrefcount(a)
aumentar en uno.
En teoría, dado que las cadenas son inmutables, una implementación podría reutilizar la memoria para segmentos de subcadena. Esto probablemente complicaría cualquier proceso de recolección de basura basado en el conteo de referencias, por lo que puede no ser una idea útil en la práctica. Considere el caso en el que se tomó un pequeño segmento de una cadena mucho más grande; a menos que haya implementado algún tipo de subreferencia contando en el segmento, la memoria de la cadena mucho más grande no se podría liberar hasta el final de la vida útil de la subcadena.
Para los usuarios que necesitan específicamente un tipo estándar que se pueda dividir sin copiar los datos subyacentes, existe memoryview
. Consulte ¿Cuál es exactamente el punto de la vista de memoria en Python para obtener más información al respecto?