web-dev-qa-db-fra.com

Découpage d'une liste en Python sans générer de copie

J'ai le problème suivant.

Étant donné une liste d'entiers L, je dois générer toutes les sous-listes L[k:]for k in [0, len(L) - 1], sans générer de copies.

Comment puis-je accomplir cela en Python? Avec un objet tampon en quelque sorte?

63
Chris

La réponse courte

Le découpage des listes ne génère pas de copies des objets de la liste; il en copie simplement les références. Telle est la réponse à la question posée.

La réponse longue

Test sur des valeurs mutables et immuables

Tout d'abord, testons la revendication de base. Nous pouvons montrer que même dans le cas d'objets immuables comme les entiers, seule la référence est copiée. Voici trois objets entiers différents, chacun avec la même valeur:

>>> a = [1000 + 1, 1000 + 1, 1000 + 1]

Ils ont la même valeur, mais vous pouvez voir que ce sont trois objets distincts car ils ont des ids différents:

>>> map(id, a)
[140502922988976, 140502922988952, 140502922988928]

Lorsque vous les découpez, les références restent les mêmes. Aucun nouvel objet n'a été créé:

>>> b = a[1:3]
>>> map(id, b)
[140502922988952, 140502922988928]

L'utilisation de différents objets avec la même valeur montre que le processus de copie ne prend pas la peine de interning - il copie juste directement les références.

Le test avec des valeurs mutables donne le même résultat:

>>> a = [{0: 'zero', 1: 'one'}, ['foo', 'bar']]
>>> map(id, a)
[4380777000, 4380712040]
>>> map(id, a[1:]
... )
[4380712040]

Examen de la mémoire restante

Bien sûr, les références elles-mêmes sont copiées. Chacun coûte 8 octets sur une machine 64 bits. Et chaque liste a sa propre surcharge de mémoire de 72 octets:

>>> for i in range(len(a)):
...     x = a[:i]
...     print('len: {}'.format(len(x)))
...     print('size: {}'.format(sys.getsizeof(x)))
... 
len: 0
size: 72
len: 1
size: 80
len: 2
size: 88

Comme Joe Pinsonault nous le rappelle , ces frais généraux s'additionnent. Et les objets entiers eux-mêmes ne sont pas très grands - ils sont trois fois plus grands que les références. Donc, cela vous permet d'économiser de la mémoire dans un sens absolu, mais asymptotiquement, il pourrait être agréable de pouvoir avoir plusieurs listes qui sont des "vues" dans la même mémoire.

Économiser de la mémoire en utilisant des vues

Malheureusement, Python ne fournit aucun moyen facile de produire des objets qui sont des "vues" dans des listes. Ou peut-être devrais-je dire "heureusement"! Cela signifie que vous n'avez pas à vous soucier de la provenance d'une tranche à partir de; les modifications apportées à l'original n'affecteront pas la tranche. Dans l'ensemble, cela facilite le raisonnement sur le comportement d'un programme.

Si vous voulez vraiment économiser de la mémoire en travaillant avec des vues, pensez à utiliser les tableaux numpy. Lorsque vous découpez un tableau numpy, la mémoire est partagée entre la découpe et l'original:

>>> a = numpy.arange(3)
>>> a
array([0, 1, 2])
>>> b = a[1:3]
>>> b
array([1, 2])

Que se passe-t-il lorsque nous modifions a et réexaminons b?

>>> a[2] = 1001
>>> b
array([   1, 1001])

Mais cela signifie que vous devez être sûr que lorsque vous modifiez un objet, vous n'en modifiez pas un autre par inadvertance. C'est le compromis lorsque vous utilisez numpy: moins de travail pour l'ordinateur et plus de travail pour le programmeur!

97
senderle

Selon ce que vous faites, vous pourrez peut-être utiliser islice .

Comme il fonctionne via l'itération, il ne crée pas de nouvelles listes, mais crée simplement des itérateurs qui yield éléments de la liste d'origine comme demandé pour leurs plages.

20
Amber