summaryrefslogtreecommitdiff
path: root/es/05_oop.md
diff options
context:
space:
mode:
authorEkaitz Zarraga <ekaitz@elenq.tech>2020-03-04 13:41:02 +0100
committerEkaitz Zarraga <ekaitz@elenq.tech>2020-03-04 13:41:02 +0100
commite81137474f102d71fe0b25307bcf88810c1b2d43 (patch)
tree840a184cf0b9c08a84c25caf390e4b7696589c9a /es/05_oop.md
parent403a94590f02479f7be41bbb62aae972e0ab596a (diff)
New arrangement for multilanguage and metadata support
Diffstat (limited to 'es/05_oop.md')
-rw-r--r--es/05_oop.md1093
1 files changed, 1093 insertions, 0 deletions
diff --git a/es/05_oop.md b/es/05_oop.md
new file mode 100644
index 0000000..6fdb9ce
--- /dev/null
+++ b/es/05_oop.md
@@ -0,0 +1,1093 @@
+# 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]: <https://en.wikipedia.org/wiki/Class-based_programming>
+[^proto]: <https://en.wikipedia.org/wiki/Prototype-based_programming>
+
+## 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 "<stdin>", line 1, in <module>
+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)
+<class 'function'>
+>>> type ( bobby.bark )
+<class 'method'>
+```
+
+> 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]: <https://docs.python.org/3/tutorial/classes.html#class-and-instance-variables>
+
+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: Duck Typing
+
+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).
+
+> 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)
+
+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]: <https://dle.rae.es/protocolo>
+
+### *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 "<stdin>", line 1, in <module>
+TypeError: 'list' object is not an iterator
+>>> it = iter(l)
+>>> it
+<list_iterator object at 0x7ff745723908>
+>>>
+>>> next(it)
+1
+>>> next(it)
+2
+>>> next(it)
+3
+>>> next(it)
+Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+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.
+> <https://www.python.org/dev/peps/>
+
+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:
+<https://www.python.org/dev/peps/pep-0343/>
+
+### *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
+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.
+
+### *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.
+
+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. 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.
+
+``` 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.
+
+#### *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 99, 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__`, `__setitem__` y
+`__delitem__`) 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
+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 "<dateFileSystemIterator: <Current: " \
+ + tostring(self.current) + ">" \
+ + ",<Start: " + tostring(self.start) + ">" \
+ + ",<End: " + tostring(self.end) + ">" \
+ + ",<Step: " + str(self.step) + ">"
+
+
+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.
+
+También, te recuerdo que, aunque sea de forma colateral y sin prestarle
+demasiada atención, se te ha sugerido que cuando programamos no lo hacemos
+únicamente para nosotros mismos y que la facilidad de lectura del código y la
+preparación de éste para que otros lo usen es primordial. Los próximos
+capítulos tratan en parte de ésto: de hacer uso del patrimonio tecnológico de
+la humanidad, y de ser parte de él.
+
+[^objects]: Puedes preguntárselo a python:
+ ``` python
+ >>> class C: pass
+ ...
+
+ >>> isinstance(C, object)
+ True
+ ```