Las clases
Sintaxis
Declarar una clase es tan sencillo como declarar una función: una palabra clave, seguida del nombre de la clase, seguida de un bloque que contiene el código de la clase:
class MiClase:
"""Documentación"""
Conviene recordar lo importante que es adquirir el buen hábito de documentar el código y, por tanto, las clases y las funciones.
El resto es bastante sencillo. Si se declara una variable dentro de una clase, esta variable es un atributo de la clase. Si se declara una función en la clase, esta función es, entonces, un método de la clase.
Y, siempre a nivel sintáctico, Python es muy permisivo. Se puede declarar una clase en una función, una clase en una clase (y también una función en una función, por otro lado). Luego, habrá que ver si tiene alguna utilidad (como es el caso), pero no entraremos en estos detalles hasta llegar a la parte Los fundamentos del lenguaje.
He aquí cómo crear una instancia:
mi_instancia = MiClase()
Destacamos la presencia de los paréntesis para gestionar la construcción del objeto. No es necesario en absoluto utilizar una palabra clave. ¿Por qué? Porque los lenguajes estáticos van a realizar automáticamente operaciones para construir el objeto en memoria y todo el proceso es predecible respecto a los atributos declarados en la clase....
Noción de instancia en curso
En la mayoría de lenguajes de programación, existe una palabra clave (generalmente this) que representa la instancia en curso. El propio lenguaje hace un poco de magia arreglándoselas para encontrar la instancia correcta.
En Python, no hay magia alguna. La instancia en curso no es una palabra clave, sino el primer parámetro de cada método. Este es también el funcionamiento de C cuando crea librerías de funciones en base a la misma estructura (como por ejemplo con las API de Gimp).
Este es uno de los aspectos más desconcertantes para aquellos que hayan programado con objetos en algún otro lenguaje. He aquí un ejemplo de método:
class MiClase:
"""Documentación"""
def mi_metodo(self, nombre):
print("{}.mi_metodo({}".format(self, nombre)
He aquí ahora un ejemplo concreto de una clase con un método de inicialización y un método que permite mostrar el objeto:
class Punto:
"""Representa un punto en el espacio"""
def __init__(self, x, y, z):
"""Método de inicialización de un punto en el espacio"""
self.x = x
...
Operadores
Recordaremos que en Python, todo es un objeto. Cuando se utiliza un operador, Python va a invocar, en realidad, a un método especial del operador sobre el operando de la izquierda y a pasarle el operando de la derecha como parámetro (si existe, lo que depende del operador en cuestión).
Basta con crear un método con un nombre especial para que el operador asociado exista para la clase.
Ejercicio: Agregue el operador de suma a la clase Punto, sabiendo que se utiliza el método especial __add__ (y que un punto puede sumarse con otro punto).
He aquí la solución:
class Punto:
"""Representa un punto en el espacio"""
[ ... código omitido ... ]
def __add__(self, other):
return Punto(self.x + other.x,
self.y + other.y,
self.z + other.z)
Ejercicio: Agregue el operador de sustracción (método __sub__) así como el operador de multiplicación (método especial __mul__), sabiendo que un punto se multiplica por un escalar (número).
Ejercicio: El método especial __str__ es el que se utiliza por print para mostrar un objeto, sea cual sea. Sobrecárguelo para utilizarlo en lugar del método...
Herencia
La manera en la que un lenguaje gestiona la herencia es una de sus marcas más reconocibles y fundamentales. Hay tantas prácticas contradictorias y doctrinas sobre el asunto que resulta complicado aclararse.
Se trata de un concepto de los años 1970 sobre el que se ha teorizado mucho y se ha adaptado a muchos lenguajes que eran, originalmente, lenguajes imperativos. Algunas adaptaciones son referencias (C++ para C), otras son algo frágiles (PHP, en el que el objeto es simplemente una semántica y se gestiona en realidad mediante un diccionario asociativo más una lista de funciones).
También existe el lenguaje Java, que está orientado a objetos, pero que ha retorcido algunos conceptos. Podemos citar, por ejemplo, la transformación de la noción de interfaz en una manera de trabajar con la herencia múltiple sin decirlo, porque esto parece dar algo de miedo.
En Python, el lenguaje se ha diseñado en primer lugar para ser multiparadigma con, entre otros, el soporte al paradigma orientado a objetos, y acepta la herencia múltiple, lo que significa que una clase puede heredar de varias clases.
Antes de asustarse al leer estas líneas, pongamos las cosas en perspectiva y veamos para qué puede servir la herencia.
Para explicarlo de una forma muy sencilla, la herencia es un método que permite evitar duplicar código. Y se distinguen dos problemáticas principales.
1. Especialización
Problemática 1: «Tengo dos objetos que se comportan más o menos de la misma forma, pero con algunas diferencias.»...