4471 votes

Comprendre le découpage en tranches

J'ai besoin d'une bonne explication (les références sont un plus) sur le découpage en tranches en Python.

6345voto

Greg Hewgill Points 356191

La syntaxe est la suivante :

a[start:stop]  # items start through stop-1
a[start:]      # items start through the rest of the array
a[:stop]       # items from the beginning through stop-1
a[:]           # a copy of the whole array

Il y a aussi le step qui peut être utilisée avec l'une des options ci-dessus :

a[start:stop:step] # start through not past stop, by step

Le point essentiel à retenir est que le :stop représente la première valeur qui est pas dans la tranche sélectionnée. Ainsi, la différence entre stop y start est le nombre d'éléments sélectionnés (si step est 1, la valeur par défaut).

L'autre caractéristique est que start o stop peut être un négatif ce qui signifie qu'il compte à partir de la fin du tableau au lieu du début. Donc :

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

De même, step peut être un nombre négatif :

a[::-1]    # all items in the array, reversed
a[1::-1]   # the first two items, reversed
a[:-3:-1]  # the last two items, reversed
a[-3::-1]  # everything except the last two items, reversed

Python est gentil avec le programmeur s'il y a moins d'éléments que ce que vous demandez. Par exemple, si vous demandez a[:-2] y a ne contient qu'un seul élément, vous obtenez une liste vide au lieu d'une erreur. Parfois, vous préférez l'erreur, vous devez donc être conscient que cela peut arriver.

Relation avec le slice objet

A slice objet peut représenter une opération de découpage en tranches, c'est-à-dire :

a[start:stop:step]

est équivalent à :

a[slice(start, stop, step)]

Les objets de type "slice" se comportent également de manière légèrement différente selon le nombre d'arguments, de manière similaire à range() c'est-à-dire que les deux slice(stop) y slice(start, stop[, step]) sont prises en charge. Pour ne pas spécifier un argument donné, on peut utiliser None de sorte que, par exemple a[start:] est équivalent à a[slice(start, None)] o a[::-1] est équivalent à a[slice(None, None, -1)] .

Alors que le : -est très utile pour le découpage simple, l'utilisation explicite de la notation basée sur les slice() simplifie la génération programmatique du découpage.

194 votes

Slicing builtin types retourne une copie mais ce n'est pas universel. Notamment, Découpage de tableaux NumPy renvoie une vue qui partage la mémoire avec l'original.

141 votes

C'est une belle réponse avec les votes pour le prouver, mais il manque une chose : vous pouvez substituer None pour l'un des espaces vides. Par exemple [None:None] fait une copie complète. Ceci est utile lorsque vous devez spécifier la fin de la plage à l'aide d'une variable et que vous devez inclure le dernier élément.

10 votes

Notez que contrairement aux tranches Python habituelles (voir ci-dessus), dans les Dataframes Pandas, le début et la fin sont inclus lorsqu'ils sont présents dans l'index. Pour plus d'informations, voir la page Documentation sur l'indexation de Pandas .

680voto

Hans Nowak Points 1542

El Tutoriel Python en parle (faites défiler la page jusqu'à ce que vous arriviez à la partie concernant le découpage en tranches).

Le diagramme ASCII est également utile pour se souvenir du fonctionnement des tranches :

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

Une façon de se rappeler comment fonctionnent les tranches est de penser aux indices comme pointant entre avec le bord gauche du premier caractère numéroté 0. Puis le bord droit du dernier caractère d'une chaîne de n les caractères ont un indice n .

40 votes

Cette suggestion fonctionne pour une foulée positive, mais pas pour une foulée négative. D'après le diagramme, je m'attends à ce que a[-4,-6,-1] à être yP mais c'est ty . Ce qui fonctionne toujours, c'est de penser en caractères ou en créneaux et d'utiliser l'indexation comme un intervalle semi-ouvert -- ouvert à droite si le pas est positif, ouvert à gauche si le pas est négatif.

0 votes

Mais il n'y a aucun moyen de se réduire à un ensemble vide en commençant par la fin (comme x[:0] lorsqu'on recommence depuis le début), il faut donc mettre les petits tableaux dans des cas particuliers :/

2 votes

@aguadopd Vous avez tout à fait raison. La solution est d'avoir les indices décalés vers la droite, centrés juste en dessous des caractères, et de remarquer que le stop est toujours exclu. Voir une autre réponse juste en dessous.

527voto

ephemient Points 87003

Enumération des possibilités permises par la grammaire pour la séquence x :

>>> x[:]                # [x[0],   x[1],          ..., x[-1]    ]
>>> x[low:]             # [x[low], x[low+1],      ..., x[-1]    ]
>>> x[:high]            # [x[0],   x[1],          ..., x[high-1]]
>>> x[low:high]         # [x[low], x[low+1],      ..., x[high-1]]
>>> x[::stride]         # [x[0],   x[stride],     ..., x[-1]    ]
>>> x[low::stride]      # [x[low], x[low+stride], ..., x[-1]    ]
>>> x[:high:stride]     # [x[0],   x[stride],     ..., x[high-1]]
>>> x[low:high:stride]  # [x[low], x[low+stride], ..., x[high-1]]

Bien sûr, si (high-low)%stride != 0 alors le point d'arrivée sera un peu plus bas que le point de départ. high-1 .

Si stride est négatif, l'ordre est un peu modifié puisque nous comptons vers le bas :

>>> x[::-stride]        # [x[-1],   x[-1-stride],   ..., x[0]    ]
>>> x[high::-stride]    # [x[high], x[high-stride], ..., x[0]    ]
>>> x[:low:-stride]     # [x[-1],   x[-1-stride],   ..., x[low+1]]
>>> x[high:low:-stride] # [x[high], x[high-stride], ..., x[low+1]]

Les découpages étendus (avec des virgules et des ellipses) ne sont généralement utilisés que par des structures de données spéciales (comme NumPy) ; les séquences de base ne les prennent pas en charge.

>>> class slicee:
...     def __getitem__(self, item):
...         return repr(item)
...
>>> slicee()[0, 1:2, ::5, ...]
'(0, slice(1, 2, None), slice(None, None, 5), Ellipsis)'

0 votes

En fait, il y a toujours quelque chose qui n'est pas pris en compte. Par exemple, si je tape 'apple'[4:-4:-1], j'obtiens 'elp', python traduit le -4 en 1, peut-être ?

0 votes

Notez que les backticks sont dépréciés en faveur de repr

0 votes

@liyuan Le type de mise en œuvre __getitem__ est ; votre exemple est équivalent à apple[slice(4, -4, -1)] .

435voto

David Perlman Points 641

Les réponses ci-dessus ne traitent pas de l'affectation des tranches. Pour comprendre l'affectation des tranches, il est utile d'ajouter un autre concept à l'art ASCII :

                +---+---+---+---+---+---+
                | P | y | t | h | o | n |
                +---+---+---+---+---+---+
Slice position: 0   1   2   3   4   5   6
Index position:   0   1   2   3   4   5

>>> p = ['P','y','t','h','o','n']
# Why the two sets of numbers:
# indexing gives items, not lists
>>> p[0]
 'P'
>>> p[5]
 'n'

# Slicing gives lists
>>> p[0:1]
 ['P']
>>> p[0:2]
 ['P','y']

Une heuristique consiste, pour une tranche de zéro à n, à penser : "zéro est le début, commencez au début et prenez n éléments dans une liste".

>>> p[5] # the last of six items, indexed from zero
 'n'
>>> p[0:5] # does NOT include the last item!
 ['P','y','t','h','o']
>>> p[0:6] # not p[0:5]!!!
 ['P','y','t','h','o','n']

Une autre heuristique est la suivante : "pour n'importe quelle tranche, remplacez le début par zéro, appliquez l'heuristique précédente pour obtenir la fin de la liste, puis comptez le premier nombre en remontant pour enlever des éléments du début".

>>> p[0:4] # Start at the beginning and count out 4 items
 ['P','y','t','h']
>>> p[1:4] # Take one item off the front
 ['y','t','h']
>>> p[2:4] # Take two items off the front
 ['t','h']
# etc.

La première règle de l'affectation des tranches est que, puisque le découpage en tranches renvoie à une liste, une affectation de tranche nécessite une liste (ou autre itérable) :

>>> p[2:3]
 ['t']
>>> p[2:3] = ['T']
>>> p
 ['P','y','T','h','o','n']
>>> p[2:3] = 't'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable

La deuxième règle de l'affectation de tranche, que vous pouvez également voir ci-dessus, est que quelle que soit la portion de la liste renvoyée par l'indexation de tranche, c'est la même portion qui est modifiée par l'affectation de tranche :

>>> p[2:4]
 ['T','h']
>>> p[2:4] = ['t','r']
>>> p
 ['P','y','t','r','o','n']

La troisième règle de l'assignation de tranche est que la liste assignée (itérable) ne doit pas nécessairement avoir la même longueur ; la tranche indexée est simplement découpée et remplacée en masse par ce qui est assigné :

>>> p = ['P','y','t','h','o','n'] # Start over
>>> p[2:4] = ['s','p','a','m']
>>> p
 ['P','y','s','p','a','m','o','n']

La partie la plus délicate à laquelle il faut s'habituer est l'affectation à des tranches vides. En utilisant les heuristiques 1 et 2, il est facile de s'y retrouver. indexation une tranche vide :

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []

Et une fois que vous avez vu cela, l'affectation de la tranche à la tranche vide a également du sens :

>>> p = ['P','y','t','h','o','n']
>>> p[2:4] = ['x','y'] # Assigned list is same length as slice
>>> p
 ['P','y','x','y','o','n'] # Result is same length
>>> p = ['P','y','t','h','o','n']
>>> p[3:4] = ['x','y'] # Assigned list is longer than slice
>>> p
 ['P','y','t','x','y','o','n'] # The result is longer
>>> p = ['P','y','t','h','o','n']
>>> p[4:4] = ['x','y']
>>> p
 ['P','y','t','h','x','y','o','n'] # The result is longer still

Notez que, puisque nous ne changeons pas le deuxième numéro de la tranche (4), les éléments insérés s'empilent toujours contre le "o", même lorsque nous assignons à la tranche vide. La position pour l'affectation à la tranche vide est donc l'extension logique des positions pour les affectations aux tranches non vides.

En revenant un peu en arrière, que se passe-t-il si l'on poursuit notre procession de comptage du début de la tranche ?

>>> p = ['P','y','t','h','o','n']
>>> p[0:4]
 ['P','y','t','h']
>>> p[1:4]
 ['y','t','h']
>>> p[2:4]
 ['t','h']
>>> p[3:4]
 ['h']
>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []

Avec le découpage en tranches, une fois que vous avez terminé, vous avez terminé ; il ne commence pas à découper en tranches à l'envers. En Python, vous n'obtenez pas de strides négatifs, sauf si vous le demandez explicitement en utilisant un nombre négatif.

>>> p[5:3:-1]
 ['n','o']

Il y a des conséquences bizarres à la règle "une fois qu'on a fini, on a fini" :

>>> p[4:4]
 []
>>> p[5:4]
 []
>>> p[6:4]
 []
>>> p[6]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

En fait, comparé à l'indexation, le découpage Python est bizarrement à l'abri des erreurs :

>>> p[100:200]
 []
>>> p[int(2e99):int(1e99)]
 []

Cela peut parfois s'avérer utile, mais cela peut aussi conduire à un comportement quelque peu étrange :

>>> p
 ['P', 'y', 't', 'h', 'o', 'n']
>>> p[int(2e99):int(1e99)] = ['p','o','w','e','r']
>>> p
 ['P', 'y', 't', 'h', 'o', 'n', 'p', 'o', 'w', 'e', 'r']

En fonction de votre demande, cela pourrait... ou ne pourrait pas... être ce que vous espériez là !


Vous trouverez ci-dessous le texte de ma réponse initiale. Elle a été utile à de nombreuses personnes, je ne voulais donc pas la supprimer.

>>> r=[1,2,3,4]
>>> r[1:1]
[]
>>> r[1:1]=[9,8]
>>> r
[1, 9, 8, 2, 3, 4]
>>> r[1:1]=['blah']
>>> r
[1, 'blah', 9, 8, 2, 3, 4]

Cela peut également clarifier la différence entre le découpage et l'indexation.

4 votes

Excellente explication. Mais la logique derrière le découpage est très peu intuitive.

0 votes

Si je veux supprimer les x premiers éléments d'une liste, qu'est-ce qui sera le mieux : l = l[6:] o l[:] = l[6:] ?

0 votes

La première méthode fonctionne pour une liste ou une chaîne de caractères ; la deuxième méthode ne fonctionne que pour une liste, car l'affectation de tranches n'est pas autorisée pour les chaînes de caractères. À part cela, je pense que la seule différence est la vitesse : il semble que la première méthode soit un peu plus rapide. Essayez vous-même avec timeit.timeit() ou de préférence timeit.repeat(). Ils sont super facile à utiliser et très éducatif, ça vaut la peine de s'habituer à jouer avec eux tout le temps !

158voto

Dana Points 9876

Et deux choses qui n'étaient pas immédiatement évidentes pour moi lorsque j'ai vu la syntaxe de découpage pour la première fois :

>>> x = [1,2,3,4,5,6]
>>> x[::-1]
[6,5,4,3,2,1]

Un moyen facile d'inverser les séquences !

Et si vous vouliez, pour une raison quelconque, un élément sur deux dans la séquence inversée :

>>> x = [1,2,3,4,5,6]
>>> x[::-2]
[6,4,2]

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