# Programación Orientada a Objetos La *programación orientada a objetos* u *object oriented programming* (OOP) es un paradigma de programación que envuelve python de pies a cabeza. A pesar de que python se define como un lenguaje de programación multiparadigma, la programación orientada a objetos es el paradigma principal de éste. A pesar de que varias de las características que tratamos en el apartado anterior se corresponden más con un lenguaje de programación funcional, en python **todo** (o casi todo) es una clase. Python usa una programación orientada a objetos basada en clases[^class], a diferencia de otros lenguajes como JavaScript, donde la orientación a objetos está basada en prototipos[^proto]. No es el objetivo de este documento el de contarte cuales son las diferencias entre ambas, pero es interesante que sepas de su existencia, ya que es una de las pocas diferencias que existen entre estos dos lenguajes de amplio uso en la actualidad. [^class]: [^proto]: ## Programación basada en clases Tras haber hecho una afirmación tan categórica como que en python todo son clases, es nuestra obligación entrar a definir lo que son y qué implica la programación basada en clases. Los objetos, *objects*, son entidades que encapsulan un estado, un comportamiento y una identidad capaz de separarlos de otras entidades. Una clase, *class*, es la definición de estos objetos. Saliendo de la definición filosófica y trayéndola a un nivel de andar por casa, puedes aclararte sabiendo que las clases son la definición enciclopédica de algo, mientras que los objetos son el propio objeto, persona o animal descrito. Llevándolo al ejemplo de un perro, la clase es la definición de qué es un perro y los objetos son los distintos perros que te puedes encontrar en el mundo. La definición de perro indica qué características ha de tener un ente para ser un perro, como ser un animal, concretamente doméstico, qué anatomía debe tener, cómo debe comportarse, etc. Mientras que el propio perro es uno de los casos de esa definición. Cada perro tiene una **identidad propia** y es independiente de los otros, tiene un **comportamiento** concreto (corre, salta, ladra...) y tiene un **estado** (está despierto o dormido, tiene una edad determinada...). La diferencia entre una clase y un objeto tiene lógica si lo piensas desde la perspectiva de que python no tiene ni idea de lo que es un perro y tú tienes que explicárselo. Una vez lo haces, declarando tu clase, puedes crear diferentes perros y ponerlos a jugar. Lo bonito de programar es que tu programa es tu mundo y tú decides lo que es para ti (o para tu programa) un perro. A nivel práctico, los objetos son grupos de datos (el *estado*) y funciones (la *funcionalidad*). Estas funciones son capaces de alterar los datos del propio objeto y no de otro (se intuye el concepto de *identidad*). Analizándolo desde el conocimiento que ya tienes, es lógico pensar que un objeto es, por tanto, una combinación de valores y funciones accesible a modo de elemento único. Exactamente de eso se trata. Existe una terminología técnica, eso sí, para referirse a esos valores y a esas funciones. Normalmente los valores se conocen como *propiedades* del objeto y las funciones se conocen como *métodos*. Así que siempre que hagamos referencia a cualquiera de estas dos palabras clave debes recordar que hacen referencia a la programación orientada a objetos. ### Fundamento teórico La programación basada en clases se basa en tres conceptos fundamentales que repasaremos aquí de forma rápida para razonar el interés de la programación orientada a objetos sobre otros paradigmas. La **encapsulación**[^encapsulation] trata de crear datos con sus métodos propios para alterarlos de modo que restrinjan el acceso directo al contenido de estos datos con el fin de asegurar una coherencia o robustez interna. Puedes entender esto como una forma de esconder información o como mi profesor de programación II en la universidad solía decir: «Las patatas se pelan en la cocina del restaurante, no en el comedor». La utilidad de la encapsulación es la de aislar secciones del programa para tener total control sobre su contenido gracias a tener total control de la vía de acceso a estos datos. A nivel práctico este concepto puede usarse para, por ejemplo, obligar a que un objeto sólo pueda ser alterado en incrementos controlados en lugar de poder pisarse con un valor arbitrario. La **herencia**[^inheritance] es un truco para reutilizar código de forma agresiva que, casualmente, sirve como una buena forma de razonar. Aporta la posibilidad de crear nuevas *clases* a partir de clases ya existentes. Volviendo a la simplificación anterior, si una clase es una definición enciclopédica de un concepto, como un perro, puede estar basada en otra descripción para evitar contar todo lo relacionado con ella. En el caso del perro, el perro es un animal. Animal podría ser otra clase definida previamente de la que el perro heredara y recibiera gran parte de su descripción genérica para sólo cubrir puntos que necesite especificar como el tamaño, la forma, el tipo de animal, el comportamiento concreto, etc. Existe la posibilidad de hacer herencias múltiples también ya que algunos conceptos pueden describirse en dos superclases distintas: un perro es un animal (vive, muere, se alimenta, se reproduce) y también es terrestre (camina sobre una superficie, etc). Ambos conceptos son independientes: los coches también son terrestres pero no son animales y los peces también son animales pero no terrestres. Y, finalmente, el **polimorfismo**[^polymorphism]. La propia etimología de la palabra define con bastante precisión el concepto, pero aplicarlo a la programación orientada a objetos no es tan evidente. Existen varios tipos de polimorfismo pero el más sencillo es entender el *subtyping*[^subtyping]. Una vez lo comprendas el resto será evidente. Si volvemos al ejemplo del perro, para ciertos comportamientos, nos da igual que tratemos de perros, de peces o de pájaros, todos son animales y todos los animales se comportan de la misma forma. Es decir, todas las subclases señaladas comparten el comportamiento de la superclase animal. Si esto es cierto, puede suponerse que en cualquier caso en el que se espere un objeto de la clase animal es seguro usar una subclase de ésta. Visto desde otra perspectiva, las subclases comparten comportamiento porque reutilizan las funciones de la clase principal o las redefinen (*herencia*), pero podemos asegurar que todas las subclases tienen un conjunto de funciones con la misma estructura, independientemente de lo que hagan, que aseguran que siempre van a ser compatibles. El nombre de esta cualidad viene a que un perro puede tomar la forma de un animal. Los otros tipos de polimorfismo explotan el mismo comportamiento de diferentes maneras, mientras que recuerdes que es posible programar de modo que el tipo de los datos que trates sea indiferente o pueda variar es suficiente. Otro ejemplo de esto son los operadores matemáticos, que son capaces de funcionar en cualquier tipo de número (integer, float, complex, etc.) de la misma manera, ya que todos son números, al fin y al cabo. Entender estos conceptos a nivel intuitivo, sin necesidad de entrar en los detalles específicos de cada uno, es interesante para cualquier programador y facilita de forma radical la comprensión de muchas de las decisiones de diseño tomadas en python y en proyectos relacionados aunque también, por supuesto, de otros lenguajes y herramientas. [^encapsulation]: https://en.wikipedia.org/wiki/Encapsulation_(computer_programming) [^inheritance]: https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming) [^polymorphism]: https://en.wikipedia.org/wiki/Polymorphism_(computer_science) [^subtyping]: https://en.wikipedia.org/wiki/Subtyping ## Sintaxis En el siguiente ejemplo se muestra la sintaxis básica a la hora de crear una clase y después instanciar dos nuevos objetos `bobby` y `beltza`. Los puntos (`.`) se utilizan para indicar a quién pertenece el método o propiedad al que se hace referencia (*identidad*). De este modo, no ocurrirá lo mismo cuando el perro (`Dog`) `bobby` ladre (`bark`) que cuando lo haga el perro `beltza`. Los métodos describen la *funcionalidad* asociada a los perros en general, pero además, la función `bark` los describe en particular, haciendo que cada perro tome su nombre (`name`), una propiedad o dicho de otro modo, su *estado*. ``` python class Dog: type = "canine" def __init__(self, name): self.name = name def bark(self): print("Woof! My name is " + self.name) bobby = Dog("Bobby") # New Dog called Bobby beltza = Dog("Beltza") # New Dog called Beltza bobby.name # Bobby beltza.name # Beltza bobby.type # canine beltza.type # canine bobby.bark() # Prints "Woof! My name is Bobby" beltza.bark() # Prints "Woof! My name is Beltza" ``` ### Creación de objetos El ejemplo muestra cómo crear nuevos *objetos* de la clase `Dog`. Las llamadas a `Dog("Bobby")` y `Dog("Beltza")` crean las diferentes instancias de la clase. Llamar a los nombres de clase como si de funciones se tratara crea una instancia de éstas. Los argumentos de entrada de la llamada se envían como argumentos de la función `__init__` declarada también en el propio ejemplo. Entiende de momento que los argumentos posicionales se introducen a partir de la segunda posición, dejando el argumento llamado `self` en el ejemplo para un concepto que más adelante entenderás. En el ejemplo, por tanto, se introduce el nombre (`name`) de cada `Dog` en su creación y la función `__init__` se encarga de asignárselo a la instancia recién creada mediante una metodología que se explica más adelante en este mismo capítulo. De momento no es necesario comentar en más profundidad estos detalles, con lo que sabes es suficiente para entender el funcionamiento general. Queda por aclarar, sin embargo, qué es la función `__init__` y por qué tiene un nombre tan extraño y qué es `type = canine`, que lo trataremos en próximos apartados de este capítulo. ### Herencia Antes de entrar en los detalles propuestos en el apartado anterior, que tratan conceptos algo más avanzados, es interesante ver cómo definir clases mediante la herencia. Basta con introducir una lista de clases de las que heredar en la definición de la clase, entre paréntesis, como si de argumentos de entrada de una función se tratara, tal y como se muestra en la clase `Dog` del siguiente ejemplo ejecutado en la REPL: ``` python >>> class Animal: ... def live(self): ... print("I'm living") ... >>> class Terrestrial: ... def move(self): ... print("I'm moving on the surface") ... >>> class Dog(Animal, Terrestrial): ... def bark(self): ... print("woof!") ... def move(self): ... print("I'm walking on the surface") ... >>> bobby = Dog() >>> bobby.bark() woof! >>> bobby.live() "I'm living" >>> bobby.move() "I'm walking on the surface" ``` El ejemplo muestra un claro uso de la herencia. La clase `Dog` hereda automáticamente las funciones asociadas a las superclases, pero es capaz de definir las propias e incluso redefinir algunas. Independientemente de la redefinición del método `move`, cualquier perro (`Dog`) va a ser capaz de moverse por la superficie, porque la superclase `Terrestrial` ya le da los métodos necesarios para hacerlo. Lo que ocurre es que cualquier subclase de `Terrestrial` tiene la ocasión moverse (`move`) a su manera: en el caso del perro, caminando. > NOTA: La herencia es interesante, pero tampoco debe caerse en la psicosis de > añadir demasiadas superclases. En ocasiones las superclases son necesarias, > sobre todo cuando aprovechar el polimorfismo facilita el trabajo, pero > usarlas de forma agresiva genera código extremadamente complejo sin razón. ### Métodos de objeto o funciones de clase: `self` Los métodos reciben un parámetro de entrada llamado `self` que no se utiliza a la hora de llamarlos: al hacer `bobby.bark()` no se introduce ningún argumento de entrada a la función `bark`. Sin embargo, si no se añade el argumento de entrada a la definición del método `bark` y se llama a `bobby.bark()` pasa lo siguiente: ``` python >>> class Dog: ... def bark(): ... pass ... >>> bobby = Dog() >>> bobby.bark() Traceback (most recent call last): File "", line 1, in TypeError: bark() takes 0 positional arguments but 1 was given ``` Python dice que `bark` espera `0` argumentos posicionales pero se le ha entregado `1`, que nosotros no hemos metido en la llamada, claro está. Así que ha debido de ser él. Efectivamente, python introduce un argumento de entrada en los métodos, el argumento de entrada que por convención se suele llamar `self`. Este parámetro es el propio `bobby` en este caso. > NOTA: Por convención se le denomina `self`. Tú le puedes llamar como te > apetezca pero, si pretendes que otros programadores te entiendan, mejor > `self`. Para explicar por qué ocurre esto es necesario diferenciar bien entre clase y objeto. Tal y como hemos hecho antes con las definiciones enciclopédicas (*clase*) y los conceptos del mundo real que encajan en la definición (*objeto*). Los objetos también se conocen como instancias, son piezas de información independiente que han sido creadas a partir de la definición que la clase aportaba. En python las clases tienen la posibilidad de tener funciones, que definen el comportamiento de la clase y no el de los objetos que se crean desde ellas. Ten en cuenta que las clases también deben procesarse y ocupan un espacio en la memoria, igual que te ocurre a ti, puedes conocer un concepto y su comportamiento y luego muchos casos que cumplan ese concepto y ambas cosas son independientes. Esta posibilidad aporta mucha flexibilidad y permite definir clases complejas. Ahora bien, para python las funciones de clase y los métodos (de los objetos, si no no se llamarían métodos), se implementan de la misma manera. Para la clase ambas cosas son lo mismo. Sin embargo, el comportamiento del operador punto (`.`), que dice a quién pertenece la función o método, es diferente si el valor de la izquierda es una clase o un objeto. Introduciendo en el segundo caso el propio objeto como primer parámetro de entrada, el `self` del que hablamos, para que la clase sepa qué objeto tiene que alterar. Este es el mecanismo de la *identidad* del que antes hablamos y no llegamos a definir en detalle. Cada objeto es único, y a través del `self` se accede a él. Es un truco interesante para no almacenar las funciones en cada uno de los objetos como método. En lugar de eso, se mantienen en la definición de la clase y cuando se llama al método, se busca de qué clase es el objeto y se llama a la función de la clase con el objeto como argumento de entrada. Dicho de otra forma, `bobby.bark()` es equivalente a `Dog.bark( bobby )`. Ilustrado en un ejemplo más agresivo, puedes comprobar que en función de a través de qué elemento se acceda a la función `bark` python la interpreta de forma distinta. A veces como función (*function*) y otras veces como método (*method*), en función de si se accede desde la clase o desde el objeto: ``` python >>> class Dog: ... def bark(self): ... pass ... >>> type ( Dog.bark) >>> type ( bobby.bark ) ``` > NOTA: También te habrás fijado, y si no lo has hecho es momento de hacerlo, > que los nombres de las clases empiezan por mayúscula en los ejemplos (`Dog`) > mientras que los objetos comienzan en minúscula (`bobby`). Se trata de otra > convención ampliamente utilizada para saber diferenciar entre uno y otro de > forma sencilla. Es evidente cuál es la clase y el objeto con los nombres que > hemos tratado en los ejemplos, pero en otros casos puede no serlo y con este > sencillo truco facilitas la lectura de tu código. Hay muchas ocasiones en las > que esta convención se ignora, así que cuidado. > Prueba a hacer `type(int)` en la terminal. ### Variables de clase En el primer ejemplo del capítulo hemos postergado la explicación de `type = canine` y ahora que ya manejas la mayor parte de la terminología y dominas la diferencia entre una clase y una instancia de ésta (un *objeto*) es momento de recogerla. A continuación se recupera la sección del ejemplo para facilitar la consulta, fíjate en la línea 2. ``` {.python .numberLines} class Dog: type = "canine" def __init__(self, name): self.name = name def bark(self): print("Woof! My name is " + self.name) ``` `type` es lo que se conoce como una *variable de clase* (*class variable*). > NOTA: En este documento se ha evitado de forma premeditada usar la palabra > *variable* para referirse a los valores y sus referencias con la intención de > marcar la diferencia entre ambos conceptos. En este apartado, sin embargo, a > pesar de que se siga tratando de una referencia, se usa el nombre *class > variable* porque es como se le llama en la documentación[^class_var] y así > será más fácil que lo encuentres si en algún momento necesitas buscar > información al respecto. De esto ya hemos discutido en el capítulo sobre > datos, donde decimos que *todo es una referencia*. [^class_var]: Previamente hemos hablado de que los objetos pueden tener propiedades asociadas, y cada objeto tendrá las suyas. Es decir, que cada instancia de la clase puede tener sus propias propiedades independientes. El caso que tratamos en este momento es el contrario, el `type` es un valor que comparten **todas** las instancias de `Dog`. Cualquier cambio en esos valores los verán todos los objetos de la clase, así que hay que ser cuidadoso. El acceso es idéntico al que ocurriría en un valor asociado al objeto, como en el caso `name` del ejemplo, pero en este caso observas que en su declaración en la clase no es necesario indicar `self`, ya no es necesario decir cuál es la instancia concreta a la que se le asigna el valor: se le asigna a todas. A parte de poder acceder a través de los objetos de la clase, es posible acceder directamente desde la clase a través de su nombre, como a la hora de acceder a las funciones de clase: `Dog.type` resultaría en `"canine"`. > NOTA: Si en algún caso python viera que un objeto tiene propiedades y > variables de clase definidas con el mismo nombre, cosa que no debería ocurrir > a menudo, tendrán preferencia las propiedades. ### Encapsulación explícita Es posible que te encuentres en alguna ocasión con métodos o propiedades, *campos* en general, cuyo nombre comience por `_` o por `__`. Se trata de casos en los que esas propiedades o métodos quieren ocultarse del exterior. El uso de `_` al inicio del nombre de un campo es una convención que avisa de que este campo no debe accederse desde el exterior de la clase y su objetivo es usarlo desde el interior de ésta. Esta convención se llevó al extremo en algún momento y se decidió crear un caso en el que esta convención inicial tuviera cierta funcionalidad añadida para las dobles barras bajas (`__`) que impidiera un acceso accidental a esos campos conocido como *name mangling*. #### Campos privados: *name mangling* El *name mangling* es un truco que hace python para asegurarse de que no se entra por accidente a las secciones que empiezan por `__`. Añade `_nombredeclase` al inicio de los campos, transformando su nombre final y dificultando el acceso por accidente. Ese acceso accidental no sólo es para que el programador no acceda, ya que, si se esfuerza la suficiente, va a poder hacerlo de igual modo, si no para que el propio python no acceda al campo que no corresponde. El hecho de añadir el nombre de la clase al campo crea una brecha en la herencia, haciendo que los campos no se hereden de la forma esperada. En una subclase en la que los campos de la clase madre han sido marcados con `__`, la herencia hace que estos campos se hereden con el nombre cambiado que contiene el nombre de la superclase. De este modo, es difícil para la subclase pisar estos campos ya que tendría que definirlos manualmente con el nombre cambiado. Crear nuevos campos con `__` no funcionaría, ya que, al haber cambiado de clase, el nombre generado será distinto. Este mecanismo es un truco para crear *campos privados*, concepto bastante común en otros lenguajes como Java o C++, que en python es inexistente. El concepto de los *campos privados* es interesante en la programación orientada a objetos. Pensando en la *encapsulación*, es lógico que a veces las clases definan métodos o propiedades que sólo los objetos creados a partir de ellas conozcan y que los objetos creados de clases heredadas no. Este es el método que python tiene para aportar esta funcionalidad. Es interesante añadir, por otro lado, que python es un lenguaje de programación muy dinámico por lo que la propia definición de las clases, y muchas cosas más, puede alterarse una vez creadas. Esto significa que el hecho de ocultar campos no es más que un acuerdo tácito entre programadores porque, si quisieran, podrían definir todo de nuevo. Trucos como este sirven para que el programador sea consciente de que está haciendo cosas que se supone que no debería hacer. Cuando programes en python, tómate esto como pistas que te indican cómo se supone que deberías estar usando las clases. ### Acceso a la superclase A pesar de la herencia, no siempre se desea eliminar por completo la funcionalidad de un método o pisar una propiedad. A veces es interesante simplemente añadir funcionalidad sobre un método o recordar algún valor definido en la superclase. Python soporta la posibilidad de llamar a la superclase mediante la función `super`, que permite el acceso a cualquier campo definido en la superclase. ``` python class Clase( SuperClase ): def metodo(self, arg): super().metodo(arg) # Llama a la definición de # `metodo` de `SuperClase` ``` > NOTA: `super` busca la clase previa por preferencia, si usas herencias > múltiples y pisas los campos puede complicarse. ## Interfaces estándar: protocolos Una de las razones principales para usar programación orientada a objetos es que, si se eligen los métodos con precisión, pueden crearse estructuras de datos que se comporten de similar forma pero que tengan cualidades diferentes. Independientemente de cómo estén definidas sus clases, si dos objetos disponen de los mismos métodos podrán ser sustituidos el uno por el otro en el programa y seguirá funcionando aunque su funcionalidad cambie. Dicho de otra forma, dos objetos (o dos cosas, en general) podrán ser intercambiados si disponen de la misma *interfaz*. *Interfaz*, de *inter*: entre; y *faz*: cara, viene a significar algo así como «superficie de contacto» y es la palabra que se usa principalmente para definir la frontera compartida entre dos componentes o, centrándonos en el caso que nos ocupa, su conexión funcional. Si recuerdas la *herencia* y la combinas con estos conceptos, puedes interpretar que además de una metodología para reutilizar código es una forma de crear nuevas definiciones que soporten la misma interfaz. En otros lenguajes de programación, Java, por ejemplo, existe el concepto *interfaz* que serían una especie pequeñas clases que definen qué funciones debe cumplir una clase para que cumpla la interfaz. A la hora de crear las clases se les puede indicar qué interfaces implementan y el lenguaje se encarga de asegurarse de que el programador ha hecho todo como debe. El dinamismo de python hace que esto sea mucho más flexible. Debido a que 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). 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 documentación de python es una buena fuente donde empezar. Todos las 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]: ### *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. 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 string, que será el que después se visualice. En el ejemplo a continuación se comienza con la clase `Dog` vacía y se visualiza una de sus instancias. Posteriormente, se reasigna la función `__repr__` de `Dog` con una función que devuelve un string. Al volver a mostrar a `bobby` el resultado cambia. Como se ve en el ejemplo, es interesante tener una buena función de representación si lo que se pretende es entender el contenido de los objetos. > NOTA: Python ya aporta una forma estándar de representar los objetos, si la > función `__repr__` no se define simplemente se usará la forma estándar. ``` python >>> class Dog: ... pass ... >>> bobby = Dog() >>> bobby <__main__.Dog object at 0x7fb7fba1b908> >>> Dog.__repr__ = lambda self: "Dog called: " + self.name >>> bobby.name = "Bobby" >>> bobby Dog called: Bobby >>> ``` ### *Contable*: `__len__` En python se utiliza la función `len` para comprobar la longitud de cualquier elemento contable. Por ejemplo: ``` python >>> len( (1,2,3) ) 3 ``` 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: ``` python >>> Dog.__len__ = lambda self: 12 # Siempre devuelve 12 >>> len(bobby) 12 ``` Este protocolo 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. ### *Buscable*: `__contains__` El método `__contains__` debe devolver `True` o `False` y recibir un argumento de entrada. Con esto el objeto será capaz de comprobarse con sentencias que hagan uso del operador `in` (y `not in`). Las dos llamadas del ejemplo son equivalentes. La segunda es lo que python realiza internamente al encontrarse el operador `in` o el operador `not in`. ``` python >>> 1 in [1,2,3] True >>> [1,2,3].__contains__(1) True ``` ### *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 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`. 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 pasar al siguiente elemento de un iterable. Ejemplificado: ``` python >>> l = [1,2,3] >>> next(l) Traceback (most recent call last): File "", line 1, in TypeError: 'list' object is not an iterator >>> it = iter(l) >>> it >>> >>> next(it) 1 >>> next(it) 2 >>> next(it) 3 >>> next(it) Traceback (most recent call last): File "", line 1, in StopIteration ``` La función `__next__` tiene un comportamiento muy sencillo. Si hay un próximo elemento, lo devuelve. Si no lo hay lanza la excepción `StopIteration`, para que la capa superior la capture. Fíjate que la lista por defecto no es un iterable y que se debe construir un elemento iterable desde ella con `iter` para poder hacer `next`. Esto se debe a que la función `iter` está pensada para restaurar la posición del cursor en el primer elemento y poder volver a iniciar la iteración. Sorprendentemente, este es el procedimiento de cualquier `for` en python. El `for` es una estructura creada sobre un `while` que construye iterables e itera sobre ellos automáticamente. Este bucle `for`: ``` python for el in secuencia: # hace algo con `el` ``` Realmente se implementa de la siguiente manera: ``` python # Construye un iterable desde la secuencia iter_obj = iter(secuencia) # Bucle infinito que se rompe cuando `next` lanza una # excepción de tipo `StopIteration` while True: try: el = next(iter_obj) # hace algo con `el` except StopIteration: break ``` Así que, si necesitas una clase con capacidad para iterarse sobre ella, puedes crear un pequeño iterable que soporte el método `__next__` y devolver una instancia nueva de éste en el método `__iter__`. ### *Creable*: `__init__` El método `__init__` es uno de los más usados e interesantes de esta lista, esa es la razón por la que ha aparecido en más de una ocasión durante este capítulo. El método `__init__` es a quién se llama al crear nuevas instancias de una clase y sirve para *ini*cializar las propiedades del recién creado objeto. Cuando se crean nuevos objetos, python construye su estructura en memoria, pidiéndole al sistema operativo el espacio necesario. Una vez la tiene, envía esa estructura vacía a la función `__init__` como primer argumento para que sea ésta la encargada de rellenarla. Como se ha visto en algún ejemplo previo, el método `__init__` (es un método, porque el objeto, aunque vacío, ya está creado) puede recibir argumentos de entrada adicionales, que serán los que la llamada al nombre de la clase reciba, a la hora de crear los nuevos objetos. Es muy habitual que el inicializador reciba argumentos de entrada, sobre todo argumentos con nombre, para que el programador que crea las instancias tenga la opción de inicializar los campos que le interesen. Volviendo a un ejemplo previo: ``` python class Dog: type = "canine" def __init__(self, name): self.name = name def bark(self): print("Woof! My name is " + self.name) bobby = Dog("Bobby") # Aquí se llama a __init__ ``` El nombre del perro, `"Bobby"` será recibido por `__init__` en el argumento `name` e insertado al `self` mediante `self.name = name`. De este modo, esa instancia de `Dog`, `bobby`, tomará el nombre `Bobby`. > NOTA: En muchas ocasiones, el método `__init__` inicializa a valores vacíos > todas las posibles propiedades del objeto con el fin de que quien lea el > código de la clase sea capaz de ver cuáles son los campos que se utilizan en > un primer vistazo. Es una buena práctica listar todos los campos posibles en > `__init__`, a pesar de que no se necesite inicializarlos aún, con el fin de > facilitar la lectura. > NOTA: Quien tenga experiencia con C++ puede equivocarse pensando que > `__init__` es un constructor. Tal y como se ha explicado anteriormente, al > método `__init__` ya llega un objeto construido. El objetivo de `__init__` es > inicializar. En python el constructor, que se encarga de crear las instancias > de la clase, es la función `__new__`. > NOTA: Si creas una clase a partir de la herencia y sobreescribes su método > `__init__` es posible que tengas que llamar al método `__init__` de la > superclase para inicializar los campos asociados a la superclase. Recuerda > que puedes acceder a la superclase usando `super`. ### *Abrible* y *cerrable*: `__enter__` y `__exit__` Este protocolo permite 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. 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 cerrarlos de forma automática. > NOTA: Los PEP (*Python Enhancement Proposals*) son propuestas de mejora para > el lenguaje. Puedes consultar todos en la web de python. Son una fuente > interesante de información y conocimiento del lenguaje y de programación en > general. > Pensando en, por ejemplo, la lectura de un archivo, se requieren varias etapas para tratar con él, por ejemplo: ``` python f = open("file.txt") # apertura del fichero f.read() # lectura f.close() # cierre ``` Este método es un poco arcaico y peligroso. Si durante la lectura del fichero ocurriera alguna excepción el fichero no se cerraría, ya que la excepción bloquearía la ejecución del programa. Para evitar estos problemas, lo lógico sería hacer una estructura `try-except` y añadir el cierre del fichero en un `finally`. La sentencia `with` se encarga básicamente de hacer eso y facilita la escritura de todo el proceso quedándose así: ``` python with f as open("file.txt"): # apertura f.read() # en este cuerpo `f` está abierto # Al terminar el cuerpo, de forma normal o forzada, # `f` se cierra. ``` Ahora bien, para que el fichero pueda ser abierto y cerrado automáticamente, deberá tener implementados los métodos `__enter__` y `__exit__`. En el PEP 343 se muestra la equivalencia entre la sentencia `with` y el uso de `__enter__`, `__close__` y el `try-except`. [^pep343]: Puedes leer el contenido completo del PEP en: ### *Llamable*: `__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 fácil: un *llamable* es un objeto que soporta el protocolo correspondiente, definido por 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 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*. ``` python >>> class Dog: ... def __call__(self): ... print("Dog called") ... >>> dog = Dog() >>> dog() Dog called ``` Ten en cuenta que el método `__call__` puede recibir cualquier cantidad de argumentos como ya hemos visto en apartados anteriores, pero el primero será el 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__` 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. 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 se intenta asociar un campo a un valor llama al método `__setitem__` del objeto. 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. ``` python >>> class Dog: ... def __getitem__(self, k): ... print(k) ... def __setitem__(self, k, v): ... print(k, v) ... >>> bobby = Dog() >>> bobby["field"] field >>> bobby["field"] = 10 field 10 ``` 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. ### Ejemplo de uso Para ejemplificar varios de estos protocolos, tomamos como ejemplo una pieza de código fuente que quien escribe este documento ha usado en alguna ocasión en su trabajo como desarrollador. Se trata de un iterable que es capaz de iterar en un sistema de ficheros estructurado en carpetas *año-mes-día* con la estructura `AAAA/MM/DD`. Este código se creó para analizar datos que se almacenaban de forma diaria en carpetas con esta estructura. Diariamente se insertaban fichero a fichero por un proceso previo y después se realizaban análisis semanales y mensuales de los datos. Esta clase permitía buscar por las carpetas de forma sencilla y obtener rápidamente un conjunto de carpetas que procesar. El ejemplo hace uso del módulo `datetime`, un módulo de la librería estándar que sirve para procesar fechas y horas. Por ahora, puedes ver la forma de importarlo como una receta y en el siguiente capítulo la entenderás a fondo. El funcionamiento del módulo es sencillo y puedes usar la ayuda para comprobar las funciones que no conozcas. Te animo a que analices el comportamiento del ejemplo, viendo en detalle cómo se comporta. Como referencia, fuera de la estructura de la clase, en las últimas líneas, tienes disponible un bucle que puedes probar a ejecutar para ver cómo se comporta. ``` {.python .numberLines} from datetime import timedelta from datetime import date class dateFileSystemIterator: """ Iterate over YYYY/MM/DD filesystems or similar. """ def __init__( self, start = date.today(), end = date.today(), days_step = 1, separator = '/'): self.start = start self.current = start self.end = end self.separator = separator self.step = timedelta( days = days_step ) def __iter__( self ): self.current = self.start return self def __next__( self ): if self.current >= self.end: raise StopIteration else: self.current += self.step datestring = self.current - self.step datestring = datestring.strftime( "%Y" \ + self.separator \ + "%m"+self.separator \ +"%d") return datestring def __repr__( self ): out = self.current - self.step tostring = lambda x: x.strftime("%Y" \ + self.separator \ + "%m" \ + self.separator + "%d") return "" \ + "," \ + "," \ + "," it = dateFileSystemIterator(start = date.today() - timedelta(days=30)) print(it) for i in it: print(i) ``` #### Ejercicio libre: `yield` y los generadores La parte de la iteración del ejemplo previo puede realizarse forma más breve mediante el uso de la sentencia `yield`. Aunque no la trataremos, `yield` habilita muchos conceptos interesantes, entre ellos los *generadores*. A continuación tienes un ejemplo de cómo resolver el problema anterior mediante el uso de esta sentencia. Te propongo como ejercicio que investigues cómo funciona buscando información sobre los *generadores* (*generator*) y la propia sentencia `yield`. ``` python from datetime import datetime, timedelta def iterate_dates( date_start, date_end=datetime.today(), separator='/', step=timedelta(days=1)): date = date_start while date < date_end: yield date.strftime('%Y'+separator+'%m'+separator+'%d') date += step ``` `yield` tiene mucha relación con las *corrutinas* (*coroutine*) que, aunque no se tratarán en este documento, son un concepto muy interesante que te animo a investigar. Si lo haces, verás que los generadores son un caso simple de una corrutina. ## Lo que has aprendido Este capítulo también ha sido intenso como el anterior, pero te prometo que no volverá a pasar. El interés principal de este capítulo es el de hacerte conocer la programación orientada a objetos y enseñarte que en python lo inunda todo. Todo son objetos. Para entenderlo has comenzado aprendiendo lo que es la programación orientada a objetos, concretamente la orientada a clases, donde has visto por primera vez los conceptos de identidad propia, comportamiento y estado. Desde ahí has saltado al fundamento teórico de la programación orientada a objetos y has visitado la encapsulación, la herencia y el polimorfismo para luego, una vez comprendidos, comenzar a definir clases en python. Esto te ha llevado a necesitar conocer qué es el argumento que suele llamarse `self`, una excusa perfecta para definir qué son las variables y funciones de clase y en qué se diferencian de las propiedades y métodos. Como la encapsulación no se había tratado en detalle aún, lo próximo que has hecho ha sido zambullirte en los campos privados viendo cómo python los crea mediante un truco llamado *name mangling* y su impacto en la herencia. Aunque en este punto conocías el comportamiento general de la herencia hacia abajo, necesitabas conocerlo hacia arriba. Por eso, ha tocado visitar la función `super` en este punto, función que te permite acceder a la superclase de la clase en la que te encuentras. En lugar de contártela en detalle, se te ha dado una pincelada sobre ella para que tú investigues cuando lo veas necesario, pero que sepas por dónde empezar. Para describir más en detalle lo calado que está python de programación orientada a objetos necesitabas un ejemplo mucho más agresivo: los protocolos. A través de ellos has visto cómo python recoge las funcionalidades estándar y te permite crear objetos que las cumplan. Además, te ha servido para ver que **todo** en python es un objeto (hasta las clases lo son[^objects]) y para ver formas elegantes de resolver problemas comunes, como los iteradores, `with` y otros. [^objects]: Puedes preguntárselo a python: ``` python >>> class C: pass ... >>> isinstance(C, object) True ```