Objetivo: presentar el inicializador de objetos en Python, invocado automáticamente por el constructor para facilitar las características iniciales del objeto.
Hemos comenzado nuestra andadura en el mundo de la POO en Python presentando los conceptos esenciales de clase y objeto. La clase representa el modelo, la plantilla con la que después crearemos los objetos concretos.
Regresemos a la sencilla clase Coche con la que hemos estado trabajando en las últimas entregas:
class Coche:
marca = ''
modelo = ''
color = ''
numero_de_puertas = 0
cuenta_kilometros = 0
velocidad = 0
arrancado = False
def arrancar(self):
if not self.arrancado:
print('Roarrrr')
self.arrancado = True
else:
# Dale al encendido estando el coche arrancado y escucha...
print('Kriiiiiiiiiiicccc')
def parar(self):
self.arrancado = False
def acelerar(self):
if self.arrancado:
self.velocidad = self.velocidad + 1
def frenar(self):
if self.velocidad > 0:
self.velocidad = self.velocidad - 1
def pitar(self):
print('Bip Bip Bip')
def consultar_velocimetro(self):
return self.velocidad
def consultar_cuenta_kilometros(self):
return self.cuenta_kilometros
Para fabricar coches a partir de este esquema, invocamos al constructor:
>>> coche1 = Coche()
Como puedes comprobar, el valor de los atributos de datos del objeto recién creado son los que aparecen en la definición de la clase. Por ejemplo:
>>> coche1.marca
''
De modo que, para poder montarte en el coche, lo primero que habría que hacer es darle algo de forma y color:
>>> coche1.marca = "Seat"
>>> coche1.modelo = "Ibiza"
>>> coche1.color = "Blanco"
>>> coche1.numero_de_puertas = 3
Los demás campos podemos dejarlos con sus valores iniciales, no parece muy apropiado que nada más fabricar el coche se nos estampe a toda pastilla contra una pared.
Esta manera de proceder, que habría que repetir con cada coche que fabricáramos, no es, obviamente, ni práctica, ni elegante ni inteligente.
¿Por qué no facilitamos estos valores iniciales en la propia invocación al constructor?
Es decir, algo así como:
coche1 = Coche("Seat", "Ibiza", "Blanco", 3)
Mucho más elegante, menos propenso a errores y, como veremos, permitiéndonos además un mayor control sobre los valores que pueden tomar los atributos.
Para poder comunicarnos de este modo con la clase Coche necesitamos ampliar su definición incorporando un nuevo método: el inicializador.
El inicializador es un método particular cuya función es obvia: ocuparse de los rudimentos iniciales necesarios a la hora de construir el objeto. Toma la forma siguiente, obsérvala con atención:
def __init__(self, argumento1, argumento2, ..., argumentoN):
código
La palabra init va precedida y seguida por dos símbolos de subrayado (dos delante y dos detrás). Esto, que puede parecer algo esotérico, es una buena convención práctica de Python para evitar posibles conflictos de nombres.
Observa que el primer argumento es self, como en cualquier otro método, que ya sabes que referencia al objeto actual (enseguida lo verás en acción). Los demás argumentos deberán cuadrar con la invocación en el constructor:
coche1 = coche(argumento1, argumento2, ..., argumentoN)
Recuerda que self no se indica dentro del constructor.
Estamos en condiciones de introducir código en el inicializador que, en este caso, es bien simple:
def __init__(self, marca, modelo, color, puertas):
self.marca = marca
self.modelo = modelo
self.color = color
self.numero_de_puertas = puertas
En la declaración de parámetros he empleado puertas en lugar de numero_de_puertas simplemente para ilustrar la flexibilidad que tenemos a la hora de elegir nombres de parámetros en las funciones. Naturalmente, en la última línea del código hay que utilizar el nombre real del atributo para que éste se nutra del valor facilitado.
La clase Coche quedaría entonces así:
class Coche:
marca = ''
modelo = ''
color = ''
numero_de_puertas = 0
cuenta_kilometros = 0
velocidad = 0
arrancado = False
def __init__(self, marca, modelo, color, puertas):
self.marca = marca
self.modelo = modelo
self.color = color
self.numero_de_puertas = puertas
def arrancar(self):
if not self.arrancado:
print('Roarrrr')
self.arrancado = True
else:
# Dale al encendido estando el coche arrancado y escucha...
print('Kriiiiiiiiiiicccc')
etcétera, ...
En breve la mejoraremos aún más, pero vayamos asegurando los conceptos paso a paso.
Con esta definición, la siguiente llamada al constructor ya es lícita:
>>> coche1 = Coche("Seat", "Ibiza", "Blanco", 3)
Lo que sucede a continuación no debe sorprenderte ahora: los argumentos «Seat», «Ibiza», «Blanco» y 3 son facilitados, respectivamente, a marca, modelo, color y puertas de __init__.
Una vez dentro de __init__, self referencia al objeto actual que se crea al invocar al constructor, es decir, coche1. De modo que interiormente __init__ realiza esto:
coche1.marca = "Seat"
coche1.modelo = "Ibiza"
coche1.color = "Blanco"
coche1.numero_de_puertas = 3
Puedes comprobar que coche1 ha sido inicializado correctamente. Por ejemplo:
>>> coche1.color
'Blanco'
>>> coche1.numero_de_puertas
3
Ahora sí, nuestra clase Coche empieza a tener clase de verdad. Pero aún podemos hacerla más fina…
Sabemos que las variables en Python no necesitan declararse y que se construyen en el momento en que reciben un valor. Este hecho nos permite simplificar nuestra clase. Si los atributos de datos van a ser inicializados dentro de __init__, ¿por qué molestarnos siquiera en dar valores iniciales al definir la clase? Simplifiquemos, entonces:
class Coche:
cuenta_kilometros = 0
velocidad = 0
arrancado = False
def __init__(self, marca, modelo, color, puertas):
self.marca = marca
self.modelo = modelo
self.color = color
self.numero_de_puertas = puertas
def arrancar(self):
if not self.arrancado:
print('Roarrrr')
self.arrancado = True
else:
# Dale al encendido estando el coche arrancado y escucha...
print('Kriiiiiiiiiiicccc')
etcétera, ...
Hemos dejado sólo los atributos que expresamente no establecemos en __init__.
Aún podemos ir más allá. ¿Por qué no incluir toda la inicialización en __init__, incluso la de aquellos miembros que no se faciliten explícitamente en la invocación al constructor?
class Coche:
def __init__(self, marca, modelo, color, puertas):
self.marca = marca
self.modelo = modelo
self.color = color
self.numero_de_puertas = puertas
self.cuenta_kilometros = 0
self.velocidad = 0
self.arrancado = False
def arrancar(self):
if not self.arrancado:
print('Roarrrr')
self.arrancado = True
else:
# Dale al encendido estando el coche arrancado y escucha...
print('Kriiiiiiiiiiicccc')
etcétera, etcétera...
Los atributos de datos, objetos también en sí (en Python, todo lo es), serán construidos en el momento de la inicialización.
Por lo general, bien sea a la hora de inicializar un objeto, o bien llegado el momento de modificar cualquiera de sus atributos de datos, es buena práctica dejar que sean los métodos quienes se encarguen de ello, en lugar de atacar a las variables miembro directamente. Dentro de un método podemos analizar el valor facilitado, comprobar que sea legítimo, corregirlo o incluso rechazarlo si no procede.
No sería muy sensato escribir código como el siguiente:
coche1.velocidad = 1000
No hay coche que pueda alcanzar 1000 Km/h. Tampoco podría estar parado y, en el instante siguiente, circular a 100 Km/h. Usando los métodos acelerar y frenar que hemos definido en la clase se garantiza un incremento o decremento de la velocidad gradual. Estos métodos, además, podrían asegurarse de que no se excediera en ningún caso un valor máximo por mucho que pisáramos el acelerador (ni mínimo cuando frenásemos, algo que ya hace el método frenar).
Tienes ya las bases suficientes para empezar a utilizar la Programación Orientada a Objetos en tus programas. Te advierto, es una vía casi sin retorno: una vez escribes tu primer programa POO, apetece poco volver a la programación estructurada tradicional.
Pero esto apenas ha empezado, aún queda mucho camino por descubrir…
Javier Montero Gabarró
El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)
El Club del Autodidacta
Consulta el índice completo de artículos relacionados con Python.