From 30076ba4fc5f538b7630648b527fea1943e22732 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Tue, 21 Jul 2020 19:27:09 +0200 Subject: Correct use of *protocols* and use *special method names* --- es/05_oop.md | 140 +++++++++++++++++++++++++++++++++-------------------------- 1 file 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]: +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: -- cgit v1.2.3