Python – El poderoso inicializador


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)

Los IDEs (aquí IDLE) nos facilitan la vida
Los IDEs (en la imagen, IDLE) nos facilitan la vida

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

Otro ejemplo de la inteligencia de IDLE
Otro ejemplo de la inteligencia de IDLE

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ó


Python – El poderoso inicializador


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.

10 opiniones en “Python – El poderoso inicializador”

  1. Tengo una pequeña duda con la POO. No sé cuando es el momento de poder aprenderla. Es decir, si estamos aprendiendo todo el tema de ciclos, funciones, colecciones, condicionales, etc…¿Cuando sabemos que es el momento para aprender este paradigma de POO? O dicho de otra manera ¿Se pueden aprender ambos paradigmas a la vez? Estar estudiando la programación modular y a la vez la POO. La cosa es, que nunca veo que me llegue el momento de aprender la POO. Ya que con los otros paradigmas, todavía creo, me queda un mundo por aprender.

    Gracias!

    1. Depende del lenguaje que aprendas, la POO podrá ser una opción o no. En el caso de Python lo es, pero mi consejo es que, tan pronto controles los rudimentos básicos, aprendas los conceptos de la POO. Por supuesto que se pueden aprender a la vez.
      Piensa, sobre todo, desde el punto de vista práctico. No son más que herramientas para resolver problemas y cuantas más manejes más recursos dispondrás para afrontarlos.
      Los conceptos de POO en Python son relativamente sencillos y extienden todo lo que ya sabes. Una vez aprendidos los bucles, las funciones, las estructuras de datos, el paso natural es seguir ya con las clases y los objetos.
      Sobre todo, programa mucho. La única manera de aprender a programar es programando. Te darás cuenta de los beneficios de la POO por ti mismo, sin que nadie te los cuente.
      Dependiendo del problema al que te enfrentes, descubrirás que no siempre la POO es la mejor opción. Python te ofrece libertad para que elijas por ti mismo.
      Ánimo y adelante con la POO ya, sin más dilación. No tienes por qué dominar todos los conceptos de golpe, vete asimilándolos y prácticándolos poco a poco. En apenas un par de horas de estudio, lo que se tarda en aprender qué es una clase y qué es un objeto, dispondrás de poderosas herramientas creativas de aplicación inmediata.
      Saludos

      1. ¡Muchas gracias! Tomo nota de tus consejos. Estoy aprendiendo Python de forma autodidacta y los ratos que puedo. Y claro, de esta forma es difícil tener una metodología de estudio. Me refiero que nunca sabes cuando estas preparado para pasar de un nivel o de un concepto de programación a otro. Bueno, lo positivo es que uno no tiene que hacer exámenes, jajaja…

        Un saludo y que te vaya bien el año que entra.

  2. Hola, tengo una duda con este tutorial. Comentas que lo lógico es hacer esto:

    class Coche:
    def __init__(self, mar, mod, gaso):
    self.marca = mar
    self.modelo = mod
    self.gasolina = gaso

    self.color = ”
    self.puertas = ”
    self.kilometros = 0
    self.velocidad = 0
    self.arrancado = False

    Pero si hacemos esto, es lo mismo, no…?:

    class Coche:
    def __init__(self, mar, mod, gaso):
    self.marca = mar
    self.modelo = mod
    self.gasolina = gaso

    color = ”
    puertas = ”
    kilometros = 0
    velocidad = 0
    arrancado = False

    El objeto instanciado se comporta igual.

    1. Qué tal, Juan:
      Por supuesto, es equivalente. De hecho así es como estaba antes de la versión final de la clase, si te fijas, salvo que yo coloco los atributos de datos antes que los métodos.
      El motivo de dejar todo en el inicializador no es otro sino la elegancia, dejando que sea este quien se ocupe de verdaderamente inicializar el objeto, que para eso le pagan. 😉
      Saludos

      1. Gracias por la aclaración. No sé si tienes pensado extender este tema con la herencia de clases ¿Pero que pasa si una subclase tiene su __init__ propio? Es decir, heredaría el __init__ de la clase padre y tendría , aparte, la suya propia. Con la complejidad de que el parámetro de __init__ fuera distintos de clase padre y clase hija. Es decir, que apuntaran a distintos métodos.

        Perdona por la insistencia. Pero el tema de clases y objetos ya es un mundo aparte…

        1. Por supuesto, todo llegará. Esto no es más que una introducción a la POO en Python.
          Las subclases tienen su propio inicializador, naturalmente, pero es responsabilidad del programador invocar, en primer lugar, dentro del inicializador de la subclase, al inicializador de la clase padre para, a continuación, proseguir con la inicialización específica de la subclase.
          Veremos cómo hacerlo pronto.
          Saludos

  3. Perdona por la indentacion. self.marca, self.modelo y self.gasolina están dentro de def__init__
    Los otros atributos fuera y sin el self.

  4. Javier, estuve buscando por la web si en Python existe algo
    parecido a estructuras “struct” como en C o la tipica UDT “Type”
    como en Visual Basic 6.0 y he leido en algun sitio que no hay una
    declaracion para este tipo de estructura de datos, si no, que hay que
    armarlas con una clase. Te comento que segui el tutorial de Clases
    y arme esto como ejemplo ejecutandolo en el IDLE, brilla por su ausencia
    el _init_, el self, el constructor etc.

    class Persona:
    nombre = ”
    apellido = ”
    edad = 0

    Persona.nombre = ‘Rick’
    Persona.apellido = ‘Hunter’
    Persona.edad = 19

    print (Persona.nombre)
    print (Persona.apellido)
    print (Persona.edad)

    Funciona, pero…, esta clase ‘minima’ es valida?
    Saludos y Gracias por tu tiempo e informacion que hay
    disponible en el Blog.

    1. Digamos que la todas esas estructuras de datos no tienen sentido en Python. No necesitas diseñar una clase para ello, aunque siempre es una opción. Usando con creatividad simplemente las listas y los diccionarios puedes, virtualmente, lograr cualquier estructura que te plantees. Observa la naturaleza libre y dinámica de las listas, en la que cada elemento puede ser de un tipo diferente. Una lista puede usarse como estructura en C. Una lista de listas puede servir sobradamente a un array de estructuras. Si, además, combinas las listas con la capacidad de indexación de los diccionarios la potencia es tremenda.
      Y te lo dice alguien que es un fanático apasionado del lenguaje C.
      Respecto a las clases, el tratamiento que ofrece Python es muy flexible y, para un purista, quizás demasiado simple. Ya has descubierto que puedes “jugar” con una clase sin necesidad de instanciarla, aunque es algo que no te servirá de gran cosa. Algunos manuales se acercan a la POO en Python así, para hacer más llano el salto. Personalmente prefiero ser más conceptual y dejar muy claro desde un principio la distinción entre clase y objeto.
      ¡Saludos!

Deja un comentario