From af9b550693df50aed916240c8ace93a5b3ed7e44 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Thu, 28 Nov 2019 17:15:49 +0100 Subject: [WIP]: OOP --- src/05_oop.md | 318 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) diff --git a/src/05_oop.md b/src/05_oop.md index fbe51e3..7d0eacd 100644 --- a/src/05_oop.md +++ b/src/05_oop.md @@ -48,3 +48,321 @@ 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") +beltza = Dog("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" +``` + +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. + +Antes de entrar en esos dos puntos, 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` + +Te habrás fijado que 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 + + + +### 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 + -- cgit v1.2.3