Simón
¿Cómo funciona Python? notación de corte ¿trabajar? Es decir: cuando escribo código como a[x:y:z]
, a[:]
, a[::2]
etc., ¿cómo puedo entender qué elementos terminan en el segmento? Incluya referencias cuando corresponda.
Consulte ¿Por qué los límites superiores de división y rango son exclusivos? para obtener más información sobre las decisiones de diseño detrás de la notación.
Consulte la forma Pythonic de devolver la lista de cada enésimo elemento en una lista más grande para conocer el uso práctico más común de la división (y otras formas de resolver el problema): obtener cada enésimo elemento de una lista. Utilice esa pregunta en su lugar como un objetivo duplicado cuando corresponda.
Para respuestas más específicas sobre asignación de rebanadas, consulte ¿Cómo funciona la asignación con sectores de lista? (aunque esto también se aborda aquí).
Hans Nowak
El Tutorial de Python habla de ello (desplácese un poco hacia abajo hasta llegar a la parte sobre el corte).
El diagrama de arte ASCII también es útil para recordar cómo funcionan los cortes:
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5 6
-6 -5 -4 -3 -2 -1
Una forma de recordar cómo funcionan los cortes es pensar en los índices como apuntando entre caracteres, con el borde izquierdo del primer carácter numerado 0. Luego, el borde derecho del último carácter de una cadena de norte caracteres tiene índice norte.
-
Esta sugerencia funciona para una zancada positiva, pero no para una zancada negativa. Del diagrama, espero
a[-4,-6,-1]
seryP
pero esty
. Lo que siempre funciona es pensar en caracteres o ranuras y usar la indexación como un intervalo semiabierto: abierto a la derecha si es un paso positivo, abierto a la izquierda si es un paso negativo.– aguadopd
27 mayo 2019 a las 20:05
-
Pero no hay forma de colapsar a un conjunto vacío comenzando desde el final (como
x[:0]
hace cuando se comienza desde el principio), por lo que tiene que hacer arreglos pequeños de casos especiales. :/– endolito
6 de julio de 2019 a las 20:07
-
@aguadopd Tienes toda la razón. La solución es desplazar los índices a la derecha, centrados justo debajo de los caracteres, y notar que la parada siempre está excluida. Vea otra respuesta justo debajo.
–Javier Ruíz
5 abr 2021 a las 21:32
-
Anexo a mi comentario: vea mi respuesta con los diagramas a continuación: stackoverflow.com/a/56332104/2343869
– aguadopd
15 de abril de 2021 a las 1:04
-
En realidad, todavía queda algo fuera, por ejemplo, si escribo ‘manzana'[4:-4:-1] Obtengo ‘ayuda’, ¿python está traduciendo el -4 a un 1 tal vez?
– liyuan
1 de enero de 2018 a las 16:39
-
tenga en cuenta que los acentos graves están en desuso en favor de
repr
– wjandrea
27 de enero de 2019 a las 1:36
-
@liyuan El tipo de implementación
__getitem__
es; tu ejemplo es equivalente aapple[slice(4, -4, -1)]
.– Chepner
10 de septiembre de 2019 a las 14:26
-
Las dos primeras tablas son de oro puro.
– Bananeen
20 de diciembre de 2021 a las 4:16
David M Perlman
Las respuestas anteriores no discuten la asignación de rebanadas. Para entender la asignación de cortes, es útil agregar otro concepto al arte 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']
Una heurística es, para un segmento de cero a n, piense: “cero es el comienzo, comience desde el principio y tome n elementos en una lista”.
>>> 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']
Otra heurística es, “para cualquier porción, reemplace el inicio por cero, aplique la heurística anterior para obtener el final de la lista, luego cuente el primer número hacia atrás para cortar los elementos del principio”.
>>> 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 primera regla de la asignación de cortes es que, dado que cortar devoluciones una lista, asignación de sectores requiere una lista (u otra iterable):
>>> 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 segunda regla de la asignación de sectores, que también puede ver arriba, es que cualquier parte de la lista que se devuelva mediante la indexación de sectores, esa es la misma parte que cambia mediante la asignación de sectores:
>>> p[2:4]
['T','h']
>>> p[2:4] = ['t','r']
>>> p
['P','y','t','r','o','n']
La tercera regla de la asignación de segmentos es que la lista asignada (iterable) no tiene que tener la misma longitud; el segmento indexado simplemente se corta y se reemplaza en masa por lo que se le asigna:
>>> 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 parte más complicada a la que hay que acostumbrarse es la asignación a sectores vacíos. Usando las heurísticas 1 y 2 es fácil entenderlo indexación una rebanada vacía:
>>> 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]
[]
Y luego, una vez que haya visto eso, la asignación de sectores al sector vacío también tiene sentido:
>>> 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
Tenga en cuenta que, dado que no estamos cambiando el segundo número de la porción (4), los elementos insertados siempre se apilan contra la ‘o’, incluso cuando estamos asignando a la porción vacía. Entonces, la posición para la asignación de sectores vacíos es la extensión lógica de las posiciones para las asignaciones de sectores no vacíos.
Retrocediendo un poco, ¿qué sucede cuando continúa con nuestra procesión de contar el comienzo de la rebanada?
>>> 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]
[]
Con el corte, una vez que haya terminado, ya está; no comienza a cortar hacia atrás. En Python, no obtiene avances negativos a menos que los solicite explícitamente utilizando un número negativo.
>>> p[5:3:-1]
['n','o']
Hay algunas consecuencias extrañas en la regla “una vez que hayas terminado, estás listo”:
>>> 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
De hecho, en comparación con la indexación, el corte de Python es extrañamente a prueba de errores:
>>> p[100:200]
[]
>>> p[int(2e99):int(1e99)]
[]
Esto puede resultar útil a veces, pero también puede conducir a un comportamiento un tanto extraño:
>>> 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']
Dependiendo de su aplicación, eso podría… o no… ¡ser lo que esperaba allí!
A continuación se muestra el texto de mi respuesta original. Ha sido útil para muchas personas, por lo que no quería eliminarlo.
>>> 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]
Esto también puede aclarar la diferencia entre rebanar e indexar.
-
Si quisiera eliminar los 1ros elementos x de una lista, qué sería mejor:
l = l[6:]
ol[:] = l[6:]
?– Alex O.
24/07/2022 a las 18:00
-
La primera forma funciona para una lista o una cadena; la segunda forma solo funciona para una lista, porque la asignación de segmentos no está permitida para cadenas. Aparte de eso, creo que la única diferencia es la velocidad: parece que es un poco más rápido de la primera manera. Pruébelo usted mismo con timeit.timeit() o preferiblemente timeit.repeat(). Ellos son súper fáciles de usar y muy educativos, ¡vale la pena acostumbrarse a jugar con ellos todo el tiempo!
– David M. Perlman
25 de julio de 2022 a las 19:46
-
Curioso acerca de cuál es la complejidad del tiempo de hacer
r[1:1]=['blah']
? ¡gracias!– Edamame
1 de septiembre de 2022 a las 17:21
-
pag[2:3] = ¡’t’ funciona bien! ¡no debería haber TypeError!
– Simo
6 abr a las 11:08
Miguel
Explicar la notación de corte de Python
En resumen, los dos puntos (:
) en notación de subíndices (subscriptable[subscriptarg]
) hacer notación de corte, que tiene los argumentos opcionales start
, stop
y step
:
sliceable[start:stop:step]
El corte de Python es una forma computacionalmente rápida de acceder metódicamente a partes de sus datos. En mi opinión, para ser incluso un programador intermedio de Python, es un aspecto del lenguaje con el que es necesario estar familiarizado.
Definiciones importantes
Para empezar, definamos algunos términos:
start
: el índice inicial del sector, incluirá el elemento en este índice a menos que sea el mismo que detener, por defecto es 0, es decir, el primer índice. Si es negativo, significa empezarn
elementos del final.
stop
: el índice final de la rebanada, lo hace no incluir el elemento en este índice, el valor predeterminado es la longitud de la secuencia que se está cortando, es decir, hasta el final incluido.
step
: la cantidad por la que aumenta el índice, por defecto es 1. Si es negativo, está cortando el iterable a la inversa.
Cómo funciona la indexación
Puedes hacer cualquiera de estos números positivos o negativos. El significado de los números positivos es sencillo, pero para los números negativos, al igual que los índices en Python, se cuenta hacia atrás desde el final para el comenzar y detenery para el paso, simplemente disminuye su índice. Este ejemplo es del tutorial de la documentaciónpero lo he modificado ligeramente para indicar a qué elemento de una secuencia hace referencia cada índice:
+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5
-6 -5 -4 -3 -2 -1
Cómo funciona el corte
Para usar la notación de división con una secuencia que la admita, debe incluir al menos dos puntos entre los corchetes que siguen a la secuencia (que en realidad implementar el __getitem__
método de la secuencia, según el modelo de datos de Python.)
La notación de corte funciona así:
sequence[start:stop:step]
Y recuerde que hay valores predeterminados para comenzar, detenery pasopor lo que para acceder a los valores predeterminados, simplemente omita el argumento.
La notación de corte para obtener los últimos nueve elementos de una lista (o cualquier otra secuencia que la admita, como una cadena) se vería así:
my_list[-9:]
Cuando veo esto, leo la parte entre paréntesis como “9 desde el final hasta el final”. (En realidad, lo abrevio mentalmente como “-9, on”)
Explicación:
La notación completa es
my_list[-9:None:None]
y para sustituir los valores predeterminados (en realidad cuando step
es negativo, stop
el valor predeterminado es -len(my_list) - 1
entonces None
para detener realmente solo significa que va al paso final al que lo lleve):
my_list[-9:len(my_list):1]
El colon, :
, es lo que le dice a Python que le estás dando un segmento y no un índice regular. Es por eso que la forma idiomática de hacer una copia superficial de las listas en Python 2 es
list_copy = sequence[:]
Y limpiarlos es con:
del my_list[:]
(Python 3 obtiene un list.copy
y list.clear
método.)
Cuando step
es negativo, los valores predeterminados para start
y stop
cambiar
Por defecto, cuando el step
argumento está vacío (o None
), se asigna a +1
.
Pero puede pasar un número entero negativo, y la lista (o la mayoría de las otras divisiones estándar) se dividirá desde el final hasta el principio.
Por lo tanto, un corte negativo cambiará los valores predeterminados para start
y stop
!
Confirmando esto en la fuente.
Me gusta animar a los usuarios a leer la fuente así como la documentación. El código fuente para objetos de segmento y esta lógica se encuentra aquí. Primero determinamos si step
es negativo:
step_is_negative = step_sign < 0;
Si es así, el límite inferior es -1
lo que significa que cortamos todo el camino hasta el principio incluido, y el límite superior es la longitud menos 1, lo que significa que comenzamos al final. (Tenga en cuenta que la semántica de este -1
es diferente a partir de una -1
que los usuarios pueden pasar índices en Python que indican el último elemento).
if (step_is_negative) { lower = PyLong_FromLong(-1L); if (lower == NULL) goto error; upper = PyNumber_Add(length, lower); if (upper == NULL) goto error; }
De lo contrario step
es positivo, y el límite inferior será cero y el límite superior (al que vamos hacia arriba pero sin incluir) la longitud de la lista dividida.
else { lower = _PyLong_Zero; Py_INCREF(lower); upper = length; Py_INCREF(upper); }
Entonces, es posible que necesitemos aplicar los valores predeterminados para start
y stop
—el valor predeterminado entonces para start
se calcula como el límite superior cuando step
es negativo:
if (self->start == Py_None) { start = step_is_negative ? upper : lower; Py_INCREF(start); }
y stop
el límite inferior:
if (self->stop == Py_None) { stop = step_is_negative ? lower : upper; Py_INCREF(stop); }
¡Dé a sus rebanadas un nombre descriptivo!
Puede que le resulte útil separar la formación del corte de pasarlo al list.__getitem__
método (eso es lo que hacen los corchetes). Incluso si no es nuevo en esto, mantiene su código más legible para que otros que puedan tener que leer su código puedan entender más fácilmente lo que está haciendo.
Sin embargo, no puede simplemente asignar algunos enteros separados por dos puntos a una variable. Necesitas usar el objeto slice:
last_nine_slice = slice(-9, None)
El segundo argumento, None
es necesario, de modo que el primer argumento se interprete como el start
argumento de lo contrario sería el stop
argumento.
Luego puede pasar el objeto de corte a su secuencia:
>>> list(range(100))[last_nine_slice]
[91, 92, 93, 94, 95, 96, 97, 98, 99]
Es interesante que los rangos también tomen porciones:
>>> range(100)[last_nine_slice]
range(91, 100)
Consideraciones de memoria:
Dado que los segmentos de las listas de Python crean nuevos objetos en la memoria, otra función importante a tener en cuenta es itertools.islice
. Por lo general, querrá iterar sobre un segmento, no solo crearlo estáticamente en la memoria. islice
es perfecto para esto. Una advertencia, no admite argumentos negativos para start
, stop
o step
por lo que si eso es un problema, es posible que deba calcular índices o invertir el iterable por adelantado.
length = 100
last_nine_iter = itertools.islice(list(range(length)), length-9, None, 1)
list_last_nine = list(last_nine_iter)
y ahora:
>>> list_last_nine
[91, 92, 93, 94, 95, 96, 97, 98, 99]
El hecho de que los segmentos de lista hagan una copia es una característica de las listas mismas. Si está cortando objetos avanzados como Pandas DataFrame, puede devolver una vista del original y no una copia.
-
Si quisiera eliminar los 1ros elementos x de una lista, qué sería mejor:
l = l[6:]
ol[:] = l[6:]
?– Alex O.
24/07/2022 a las 18:00
-
La primera forma funciona para una lista o una cadena; la segunda forma solo funciona para una lista, porque la asignación de segmentos no está permitida para cadenas. Aparte de eso, creo que la única diferencia es la velocidad: parece que es un poco más rápido de la primera manera. Pruébelo usted mismo con timeit.timeit() o preferiblemente timeit.repeat(). Ellos son súper fáciles de usar y muy educativos, ¡vale la pena acostumbrarse a jugar con ellos todo el tiempo!
– David M. Perlman
25 de julio de 2022 a las 19:46
-
Curioso acerca de cuál es la complejidad del tiempo de hacer
r[1:1]=['blah']
? ¡gracias!– Edamame
1 de septiembre de 2022 a las 17:21
-
pag[2:3] = ¡’t’ funciona bien! ¡no debería haber TypeError!
– Simo
6 abr a las 11:08
Dana
Y un par de cosas que no fueron inmediatamente obvias para mí cuando vi por primera vez la sintaxis de corte:
>>> x = [1,2,3,4,5,6]
>>> x[::-1]
[6,5,4,3,2,1]
¡Manera fácil de invertir secuencias!
Y si quisiera, por alguna razón, cada segundo elemento en la secuencia inversa:
>>> x = [1,2,3,4,5,6]
>>> x[::-2]
[6,4,2]