From dd51ede6460b2392afb28676ecc7a42cb8748f1c Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Wed, 4 Dec 2019 19:26:21 +0100 Subject: OOP: add sliceable and change naming --- src/05_oop.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/src/05_oop.md b/src/05_oop.md index 549b88d..0e14c02 100644 --- a/src/05_oop.md +++ b/src/05_oop.md @@ -779,7 +779,7 @@ se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`, [^pep343]: Puedes leer el contenido completo del PEP en: -### *Llamable*: `__call__` +### *Callable*: `__call__` Queda pendiente desde el capítulo sobre funciones, responder a lo que es un *callable* o *llamable*. Una vez llegados a este punto, tiene una respuesta @@ -814,7 +814,7 @@ propio objeto que está siendo llamado, el `self` que ya conocemos. Resumiendo, el método `__call__` describe cómo se comporta el objeto cuando se le aplican las paréntesis. -### *Accesible*: `__getitem__` y `__setitem__` +### *Subscriptable*: `__getitem__` y `__setitem__` Tal y como el método anterior describía cómo se aplican las paréntesis a un objeto, el protocolo que se muestra en este apartado describe el comportamiento @@ -828,6 +828,11 @@ mediante los corchetes llama automáticamente al método `__getitem__` y cuando se intenta asociar un campo a un valor llama al método `__setitem__` del objeto. +Aunque en otros protocolos aquí descritos hemos inventado un nombre para este +documento, Python a este protocolo le denomina *subscriptable* así que cuando +intentes acceder usando corchetes a un objeto que no soporta el protocolo, el +error que saltará te utilizará la misma nomenclatura que nosotros. + El siguiente ejemplo muestra el protocolo en funcionamiento en una clase sin funcionamiento alguno. Lo lógico y funcional sería utilizar estos dos métodos para facilitar el acceso a campos de estas clases o para crear clases que @@ -851,6 +856,79 @@ Fíjate en que reciben diferente cantidad de argumentos de entrada cada uno de los métodos. El método `__setitem__` necesita indicar no sólo qué *item* desea alterarse, sino su también su valor. +#### *Slice notation* + +Se trata de una forma avanzada de seleccionar las posiciones de un objeto, el +nombre viene de *slice*, rebanada, y significa que puede coger secciones del +objeto en lugar de valores únicos. Piénsalo como en una barra de pan cortada en +rebanadas de la que quieres seleccionar qué rebanadas te interesan en bloque. + +No todos los objetos soportan *slicing*, pero los que lo hacen permiten acceder +a grupos de valores en el orden en el que están indicando el inicio del grupo +(inclusive), el final (no inclusive) y el salto de un elemento al siguiente. + +Además, los valores del *slice* pueden ser negativos. Añadir un número negativo +al salto implica que el salto se hace hacia atrás. Añadirlo en cualquier de los +otros dos valores, inicio o final de grupo, implica que se cuenta el elemento +desde el final de la colección en dirección opuesta a la normal. + +La sintaxis de los *slice*s es la siguiente: `[inicio:fin:salto]`. +Cada uno de los valores es opcional y si no se añaden se comportan de la +siguiente manera: + +- Inicio: primer elemento +- Fin: último elemento inclusive +- Salto: un único elemento en orden de cabeza a cola + +> NOTA: El índice para representar el último elemento es -1, pero si se quiere +> indicar como final, usar -1 descartará el último elemento porque el final no +> es inclusivo. Para que sea inclusivo es necesario dejar el campo fin vacío. + +Dada una lista de los números naturales del 1 al 100, ambos incluidos, de +nombre `l` se muestran unos casos de *slicing*. + +``` python +>>> l[-5:] +[95, 96, 97, 98, 99] +>>> l[6:80:5] +[6, 11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 76] +>>> l[60:0:-5] +[60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5] +``` + +La sintaxis de los *slice*s mostrada sólo tiene sentido a la hora de acceder a +los campos de un objeto, si se trata de escribir suelta lanza un error de +sintaxis. Para crear *slice*s de forma separada se construyen mediante la +clase `slice` de la siguiente manera: `slice(inicio, fin, salto)`. + +En los métodos del protocolo *subscriptable* (`__getitem__` y `__setitem__`) a +la hora de elegir un *slice* se recibe una instancia del tipo *slice* en lugar +de una selección única como en el ejemplo previo: + +``` python +>>> class Dog: +... def __getitem__(self, item): +... print(item) +... +>>> bobby = Dog() +>>> bobby[1:100] +slice(1, 100, None) +>>> bobby[1:100:9] +slice(1, 100, 9) +>>> bobby[1:100:-9] +slice(1, 100, -9) +``` + +Por complicarlo todavía más, los campos del *slice* creado desde la clase +`slice` pueden ser del tipo que se quiera. El formato de los `:` es únicamente +*sintactic sugar* para crear *slices* de tipo integer o string. Aunque después +es responsabilidad del quien implemente el protocolo soportar el tipo de +*slice* definido, es posible crear *slices* de lo que sea, incluso anidarlos. + +Como ejemplo de un caso que utiliza *slices* no integer, los tipos de datos +como los que te puedes encontrar en la librería `pandas` soportan *slicing* +basado en claves, como si de un diccionario se tratara. + ### Ejemplo de uso Para ejemplificar varios de estos protocolos, tomamos como ejemplo una pieza de -- cgit v1.2.3