Le découpage de chaînes de caractères fait une copie en CPython.
En regardant dans la source, cette opération est gérée dans unicodeobject.c:unicode_subscript
. Il existe évidemment un cas spécial de réutilisation de la mémoire lorsque le pas est 1, le début est 0, et que le contenu entier de la chaîne est découpé en tranches - ceci entre dans la rubrique unicode_result_unchanged
et il n'y aura pas de copie. Cependant, le cas général appelle PyUnicode_Substring
où toutes les routes mènent à un memcpy
.
Pour vérifier empiriquement ces affirmations, vous pouvez utiliser un outil de profilage de la mémoire 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)
Vous devriez voir les deux premières statistiques s'afficher comme suit :
/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
Ce résultat montre deux des allocations de 7 méga, preuve évidente de la copie de mémoire, et les numéros de ligne exacts de ces allocations seront indiqués.
Essayez de changer la tranche de b = a[1:]
en b = a[0:]
pour voir que le cas spécial de la chaîne entière est effectif : il ne devrait y avoir qu'une seule grande allocation maintenant, et sys.getrefcount(a)
augmentera d'une unité.
En théorie, puisque les chaînes de caractères sont immuables, une implémentation pourrait réutiliser la mémoire pour les tranches de sous-chaînes. Cela compliquerait probablement tout processus de collecte des déchets basé sur le comptage des références, et ce n'est donc peut-être pas une idée utile en pratique. Considérez le cas où une petite tranche d'une chaîne beaucoup plus grande a été prise - à moins que vous n'implémentiez une sorte de comptage de sous-références sur la tranche, la mémoire de la chaîne beaucoup plus grande ne pourrait pas être libérée avant la fin de la durée de vie de la sous-chaîne.
Pour les utilisateurs qui ont spécifiquement besoin d'un type standard qui peut être découpé en tranches sans copier les données sous-jacentes, il y a memoryview
. Ver Quel est l'intérêt de memoryview en Python ? pour plus d'informations à ce sujet.