summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2020-07-21 19:27:09 +0200
committerEkaitz Zarraga <ekaitz@elenq.tech>2020-07-21 19:27:09 +0200
commit30076ba4fc5f538b7630648b527fea1943e22732 (patch)
treed5a2dd6c749718881203c7bc290a762e4075cc40
parent71a262472cf9c9c170e7a391fc07be953ddf7ec1 (diff)
Correct use of *protocols* and use *special method names*
-rw-r--r--es/05_oop.md140
1 files changed, 78 insertions, 62 deletions
diff --git a/es/05_oop.md b/es/05_oop.md
index dd5c27c..c5396b3 100644
--- a/es/05_oop.md
+++ b/es/05_oop.md
@@ -484,48 +484,42 @@ python no hace casi comprobaciones antes de ejecutarse, necesita un método para
mucho más directo. Para python, *si anda como un pato, vuela como un pato y
nada como un pato: es un pato*.
-Python usa lo que en la terminología del lenguaje se conoce como
-*protocolos*[^protocol] (*protocol*) para que los objetos creados por el
-programador puedan comportarse como los que el propio sistema aporta. Por
-ejemplo, que sea posible utilizarlos como iterable en un `for`, que el sistema
-pueda cerrarlos de forma automática, buscar en ellos usando el operador `in`,
-etc. Simplemente, el sistema define qué funciones se deben cumplir en cada uno
-de esos casos y cuando se encuentre con ellos intentará llamarlas
-automáticamente. Si el elemento no dispone de esas funciones lanzará una
-excepción como la que lanza cuando intentamos acceder a un método que no existe
-(que es básicamente lo que estamos haciendo en este caso).
-
-> TODO: En realidad no se llaman protocolos todos ellos. Se llama así sólo al
-> *iterator protocol*. En realidad se llaman: [Special Method
-> Names](https://docs.python.org/3/reference/datamodel.html#special-method-names)
+Para que los objetos creados por el programador puedan comportarse como los que
+el propio sistema aporta, python define unos nombres de métodos especiales
+(*special method names*). Estos métodos tienen infinidad de utilidades: que sea
+posible utilizarlos como iterable en un `for`, que el sistema pueda cerrarlos
+de forma automática, buscar en ellos usando el operador `in`, etc. Simplemente,
+el sistema define qué funciones se deben cumplir en cada uno de esos casos y
+cuando se encuentre con ellos intentará llamarlas automáticamente. Si el
+elemento no dispone de esas funciones lanzará una excepción como la que lanza
+cuando intentamos acceder a un método que no existe (que es básicamente lo que
+estamos haciendo en este caso).
En general, python, con el fin de diferenciar claramente qué nombres elige el
programador y cuales han sido seleccionados por el lenguaje, suele utilizar una
convención para la nomenclatura: comienzan y terminan por: `__`
-A continuación se describen algunos de los protocolos más comunes, algunos ya
-han aparecido a lo largo de los ejemplos del documento, otros las verás por
-primera vez ahora. Existen muchos más, y todos están extremadamente bien
-documentados. Si en algún momento necesitas crear algunos nuevos, la
+A continuación se describen algunos de los nombres especiales más comunes.
+Algunos ya han aparecido a lo largo de los ejemplos del documento, otros las
+verás por primera vez ahora. Existen muchos más, y todos están extremadamente
+bien documentados. Si en algún momento necesitas crear algunos nuevos, la
documentación de python es una buena fuente donde empezar.
-Todos los protocolos se presentan con un nombre, en muchos casos inventado,
-terminado en *-able*. Python utiliza también este tipo de nombres, como el ya
-aparecido *llamable*, o *callable* en inglés, que se refiere a cualquier cosa
-que puede ser llamada. Representar los nombres de esta manera sirve para
-expresar el interés de los protocolos. Si en algún momento necesitas crear una
-clase que defina un objeto en el que se puede buscar necesitas que sea un
-*buscable*, es decir, que soporte el protocolo que define ese comportamiento.
-
-[^protocol]: **Protocolo**: 5. m. Inform. Conjunto de reglas que se establecen
- en el proceso de comunicación entre dos sistemas. — RAE [Consultado
- 01-12-2019]: <https://dle.rae.es/protocolo>
+Todos los nombres de métodos especiales agrupan un conjunto de características
+que se presentan con una palabra, en muchos casos inventada, terminada en
+*-able*. Python utiliza también este tipo de nombres, como el ya aparecido
+*llamable*, o *callable* en inglés, que se refiere a cualquier cosa que puede
+ser llamada. Representar las capacidades de esta manera sirve para expresar el
+interés de los nombres de métodos especiales. Si en algún momento necesitas
+crear una clase que defina un objeto en el que se puede buscar necesitas que
+sea un *buscable*, es decir, que soporte el nombre de método especial que
+define ese comportamiento.
### *Representable*: `__repr__`
-Este protocolo sirve para otorgar a python una forma de representar estos
-objetos. Al ejecutar la función `print` o al exponer valores en la REPL
-(recuerda que la P significa print), python trata de visualizarlos.
+Este método sirve para otorgar a python una forma de representar estos objetos.
+Al ejecutar la función `print` o al exponer valores en la REPL (recuerda que la
+P significa print), python trata de visualizarlos.
La el método `__repr__` se ejecuta justo antes de imprimirse el objeto, de
forma automática. La función requiere que se devuelva un elemento de tipo
@@ -570,7 +564,7 @@ elemento contable. Por ejemplo:
Las objetos que soporten esta función podrán contarse para conocer su longitud
mediante la función `len`. Python llamará al método `__len__` del objeto (que
se espera que devuelva un número entero) y ésta será su longitud. Siguiendo con
-el ejemplo del protocolo anterior:
+el ejemplo anterior:
``` python
>>> Dog.__len__ = lambda self: 12 # Siempre devuelve 12
@@ -578,10 +572,10 @@ el ejemplo del protocolo anterior:
12
```
-Este protocolo permite crear elementos contables, en lugar de los típicos
+Este método permite crear elementos contables, en lugar de los típicos
diccionario, tupla y lista. Como por ejemplo los ya existentes `NamedTuple`,
-`OrderedDict` y otros. Los protocolos para el *buscable* e *iterable* también
-son muy interesantes para esta labor.
+`OrderedDict` y otros. Los métodos *buscable* e *iterable* también son muy
+interesantes para esta labor.
### *Buscable*: `__contains__`
@@ -598,16 +592,38 @@ True
True
```
+### *Hasheable*: `__hash__` y `__eq__`
+
+Los objetos *hasheables*, pueden convertirse a un valor numérico mediante una
+función *hash*. Estas funciones habilitan la existencia de los diccionarios,
+siendo el mecanismo principal para obtener una mejora en el rendimiento en el
+acceso y la inserción aunque también sirven para infinidad de aplicaciones,
+como la comparación de objetos, agrupaciones, etc.
+
+La función `__hash__` será ejecutada siempre que se intente aplicar `hash()` a
+un objeto, cosa que ocurre de forma automática en varios escenarios. Su único
+requerimiento es que retorne un número, y que el *hash* de dos objetos
+idénticos sea el mismo.
+
+Con el fin de que las comparaciones entre objetos puedan realizarse como es
+debido, es necesario implementar al menos la función `__eq__`, función que será
+llamada al realizar comparaciones como `a == b`. Ésta sirve para poder realizar
+comparaciones complejas (*rich comparisons*) en objetos que en principio no
+pueden compararse.
+
+Los objetos básicos de python son *hasheables*.
+
+
### *Iterable*: `__next__` e `__iter__`
-El protocolo iterable permite crear objetos con los que es posible iterar en
-bucles `for` y otras estructuras. Por ejemplo, los archivos de texto en python
+Estos métodos permiten crear objetos con los que es posible iterar en bucles
+`for` y otras estructuras. Por ejemplo, los archivos de texto en python
soportan este protocolo, por lo que pueden leerse línea a línea en un bucle
`for`.
-Igual que en el caso del protocolo `__len__`, que servía para habilitar la
-llamada a la función `len`, `__iter__` y `__next__` sirven, respectivamente,
-para habilitar las llamadas a `iter` y `next`.
+Igual que en el caso de `__len__`, que servía para habilitar la llamada a la
+función `len`, `__iter__` y `__next__` sirven, respectivamente, para habilitar
+las llamadas a `iter` y `next`.
La función `iter` sirve para convertir el elemento a *iterable*, que es una
clase que soporte el funcionamiento de la función `next`. Y `next` sirve para
@@ -734,10 +750,10 @@ instancia de `Dog`, `bobby`, tomará el nombre `Bobby`.
### *Abrible* y *cerrable*: `__enter__` y `__exit__`
-Este protocolo permite que los objetos puedan ser abiertos y cerrados de forma
+Estos métodos permiten que los objetos puedan ser abiertos y cerrados de forma
segura y con una sintaxis eficiente. Aunque no se van a listar en profundidad,
el objetivo de este punto es mostrar la sentencia `with` que se habilita
-gracias a estos protocolos y mostrar cómo facilitan la apertura y cierre.
+gracias a estos métodos y mostrar cómo facilitan la apertura y cierre.
El PEP 343[^pep343] muestra en detalle la implementación de la sentencia
`with`. Simplificándolo y resumiéndolo, `with` sirve para abrir elementos y
@@ -787,19 +803,18 @@ se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`,
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
-fácil: un *llamable* es un objeto que soporta el protocolo correspondiente,
-definido por el método `__call__`.
+fácil: un *llamable* es un objeto que soporta el método `__call__`.
Aunque pueda parecer sorprendente, las funciones en python también se llaman de
este modo, así que realmente son objetos que se llaman porque soportan este
-protocolo. Es lógico, porque las funciones, recuerda el capítulo previo, pueden
+método. Es lógico, porque las funciones, recuerda el capítulo previo, pueden
guardar valores, como el contexto en el que se crean (*closure*). Las funciones
son meros *llamables* y como tales se comportan.
Llevado más allá, los tipos básicos de python están definidos en clases
también, lógicamente, pero pueden ser llamados para hacer conversiones tal y
-como vimos en el capítulo sobre datos. Simplemente, soportan el protocolo
-*llamable*.
+como vimos en el capítulo sobre datos. Simplemente, soportan el método
+`__init__`.
``` python
>>> class Dog:
@@ -821,11 +836,10 @@ le aplican las paréntesis.
### *Subscriptable*: `__getitem__`, `__setitem__` y `__delitem__`
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
-del objeto cuando se le aplican los corchetes. Recordando el capítulo sobre
-datos, los corchetes sirven para acceder a valores de las listas, tuplas,
-diccionarios y sets, que resultan ser también un tipo de objeto que describe
-este comportamiento mediante el protocolo que tenemos entre manos.
+objeto, los métodos que se muestran en este apartado describen el
+comportamiento del objeto cuando se le aplican los corchetes. Recordando el
+capítulo sobre datos, los corchetes sirven para acceder a valores de las
+listas, tuplas y diccionarios.
Cuando python encuentra que se está tratando de acceder a un campo de un objeto
mediante los corchetes llama automáticamente al método `__getitem__` y cuando
@@ -833,15 +847,17 @@ se intenta asociar un campo a un valor llama al método `__setitem__` del
objeto. Al pedir la eliminación de un campo del objeto con la sentencia `del`,
se llama al método `__delitem__`.
-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
-pudiesen sustituir a listas, tuplas, diccionarios o sets de forma sencilla.
+Aunque en otros conjuntos de métodos aquí descritos hemos inventado un nombre
+para este documento, Python denomina a este comportamiento *subscriptable* así
+que cuando intentes acceder usando corchetes a un objeto que no soporta ésta
+funcionalidad, el error que saltará utilizará la misma nomenclatura que
+nosotros.
+
+El siguiente ejemplo muestra el funcionamiento en una clase que en lugar de
+usar los métodos, imprime en pantalla. 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 pudiesen sustituir a listas, tuplas o diccionarios de
+forma sencilla, pero puede hacerse cualquier porque son métodos normales.
``` python
>>> class Dog: