Python – Fabricando objetos


Objetivo: presentar el concepto de Objeto o Instancia de una Clase en Python.

En el anterior artículo introdujimos las clases, auténticos pilares de la Programación Orientada a Objetos. Podemos pensar en ellas como diseños de moldes o plantillas para representar en Python (abstraer) una determinada realidad.

Aprendimos, por ejemplo, a modelar un coche:

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

En la definición de clase nos encontramos sus características o atributos, que toman forma de variables (marca, modelo, color, etc…) y la funcionalidad propia de la clase o métodos, que nos indican qué cosas podemos hacer con ella, representados mediante funciones (arrancar(), acelerar(), frenar(), etc.).

Pero esto no es más que un esquema, un molde. Lo que queremos ahora es fabricar coches concretos, como un Seat León de cinco puertas o un Ford Fiesta de tres.

Al hecho de “fabricar” objetos a partir de un molde de clase se le denomina instanciar, y al producto obtenido se le conoce como instancias o, simplemente, objetos de esa clase.

De modo que vamos a asegurarnos que tenemos los conceptos claros: las clases son los moldes a los que recurrimos después para crear objetos concretos.

Para poner en marcha nuestra fábrica de coches virtual no necesitaremos una gran inversión en infraestructura y personal; nos bastará con ejecutar el intérprete de Python.

Para mayor comodidad, supongamos que hemos preparado ya todo el código con la definición de la clase Coche y lo hemos incluido en un fichero tal como coche.py.

La primera labor consistirá, por lo tanto, en ejecutar dicho código. En mi caso, lo abro en Idle y lo ejecuto con Run, pero tú puedes elegir cualquier otra forma de hacerlo. La cuestión es tener el intéprete interactivo con toda la definición de la clase cargada ya en memoria.

Comprobemos su existencia:

>>> Coche
<class '__main__.Coche'>

Para fabricar coches concretos necesitamos invocar a lo que se conoce como el constructor de clase, que se ocupará de crear el objeto en memoria. El rito para que haga acto de presencia es muy simple: basta con escribir el nombre de la clase seguido de un par de paréntesis.

Construyamos un primer coche:

>>> coche1 = Coche()

Típicamente los paréntesis los utilizaremos para definir valores iniciales del objeto. Más adelante, cuando presentemos el inicializador, aprenderás a usar esa característica.

Parémonos un instante a contemplar nuestra primera fabricación:

>>> coche1
<__main__.Coche object at 0x0234B7D0>

Lo que nos está diciendo el intérprete es que coche1 es un objeto de tipo Coche y está ubicado en la dirección de memoria 0x0234B7D0.

Cada objeto posee en Python un identicador único que podemos consultar con la función id():

>>> id(coche1)
37009360

En CPython, la implementación más común de Python, este identificador coincide con la dirección de memoria anterior, como podrás comprobar si te tomas la molestia de pasarlo a hexadecimal.

Ya que nos ha ido tan bien, probemos a fabricar un segundo automóvil:

>>> coche2 = Coche()

Aunque de la misma clase, se trata en efecto de un objeto diferente:

>>> id(coche2)
37634800

Para acceder a los atributos y métodos del objeto recurrimos a la notación punto, separando el nombre del objeto del atributo o método mediante un punto.

Establezcamos algunos atributos de coche1:

<code>>>> coche1.marca = "Seat"
>>> coche1.modelo = "León"
>>> coche1.color = "negro"

Y otros tantos de coche2:

coche2.marca = "Ford"
coche2.modelo = "Fiesta"
coche2.numero_de_puertas = 3

Esta forma de proceder, que puede parecer natural, no es la común. Tal como ya hemos apuntado, para dar valores iniciales a los objetos suele emplezarse la figura del inicializador, como veremos más adelante. Lo importante ahora es comprender bien el concepto.

Comprobemos los atributos de coche1:

>>> coche1.marca
'Seat'
>>> coche1.modelo
'León'
>>> coche1.color
'negro'
>>> coche1.numero_de_puertas
0
>>> coche1.cuenta_kilometros
0
>>> coche1.velocidad
0
>>> coche1.arrancado
False

Aprecia como los atributos que no hemos inicializado expresamente toman los valores que tenían en la definición de la clase.

Juguemos ahora un poco con los métodos, funciones que definen lo que podemos hacer con los objetos.

Traigamos a la palestra el primero de ellos. Lo reescribo aquí por comodidad:

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')

El parámetro self, tal como explicamos en el artículo anterior, es obligatorio y debe figurar el primero en la declaración del método. Hace referencia al objeto en cuestión sobre el que se aplicará el método.

Para invocar el método, recurrirmos nuevamente a la notación punto, pasando entre paréntesis los argumentos que requiere el método en particular sin contar a self. Como en arrancar sólo existe self, a efectos prácticos es como si no tuviera ninguno:

>>> coche1.arrancar()
Roarrrr

Aunque no esté presente en la invocación, se está pasando implícitamente como self el nombre del método que llama a arrancar(), en este caso, coche1. Observa el código del método. Lo primero que hace es comprobar que el coche no está arrancado, en cuyo caso imprime “Roarrr”, cambiando a continuación el atributo arrancado a True. Verifiquemos que en efecto se ha realizado esto último:

>>> coche1.arrancado
True

Naturalmente, coche2 permanece completamente ajeno a estas operaciones, pues arrancar no ha actuado sobre él, sino sobre coche1. Comprobemos que sigue detenido:

>>> coche2.arrancado
False

Estando coche1 arrancado, si intentamos arrancarlo de nuevo obtendremos un chirriante y desagradable ruido propio de forzar el motor de arranque sin necesidad:

>>> coche1.arrancar()
Kriiiiiiiiiiicccc

Revisemos algunos métodos más de la definición de la clase Coche:

    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

Experimentemos con ellos. Cada vez que aceleramos, la velocidad del vehículo se incrementa en una unidad:

>>> coche1.acelerar()
>>> coche1.velocidad
1
>>> coche1.acelerar()
>>> coche1.velocidad
2

Para detener el vehículo, primero lo frenamos completamente:

>>> coche1.frenar()
>>> coche1.frenar() # Al tener velocidad 2 aplicamos dos veces el método
>>> coche1.velocidad
0

Y, a continuación, sacamos la llave de contacto:

>>> coche1.parar()
>>> coche1.arrancado
False

Es muy importante que estudies con cuidado en estos ejemplos cómo hemos accedido desde los métodos a los atributos del objeto a través de self. En la siguiente lección, cuando presentemos el inicializador, le daremos a esta operatividad un carácter esencial.

Javier Montero Gabarró


Python – Fabricando objetos


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.

8 opiniones en “Python – Fabricando objetos”

    1. Tienes que importar el módulo que contiene la clase y luego referenciar tu clase con notación punto. O bien incluirla directamente en tu espacio de nombres con algo así como:

      from mi_fichero_de_clases import MiClase

      y en este caso no necesitarías usar la notación punto.

      Saludos

  1. Muy buenas, sé que estoy escribiendo en una entrada de hace dos años, y que el blog parece que lleva parado un año, pero realmente no sé dónde más preguntar…

    Estoy empezando a programar en Python, y para ello estoy haciendo un pequeño programa que genera subgrupos aleatorios dentro de un mismo grupo, es decir, si se tiene por ejemplo un grupo A con 20 miembros, se pueden generar 4 subgrupos B, C, D y E de 5 miembros cada uno, escogidos al azar de los 20 del grupo A.

    Para esto, he definido una clase grupo, y dentro de la misma, uno de sus métodos es “nSubgrupos”, que devuelve una lista con los n subgrupos que le pides. Para llevarlo a cabo, mi razonamiento ha sido el siguiente: un subgrupo no deja de ser un grupo, solo que sus miembros son de otro grupo.

    Así que, ni corto ni perezoso, he dicho: si para instanciar un objeto de la clase Grupo, el método __init__ recibe el nombre del grupo y una lista de miembros, pues voy a hacer lo mismo: cojo al azar tantos miembros como hagan falta del grupo, los meto en una lista, y le pongo un nombre.

    ¿Cuál es el problema? Que este grupo “subgrupo” tiene primero los mismos integrantes que el original, y después aparecen los tomados al azar. He debbugeado el programa, y la lista que le paso tiene efectivamente un número concreto de miembros al azar, pero cuando llega al método __init__, he comprobado que en self están los valores del grupo que llama al método subgrupo.

    Es decir, que no sé si lo estoy dejando claro:

    Existe una clase Grupo
    Esta clase tiene un método subgrupo
    Dentro del método subgrupo se instancia un objeto Grupo, que llamaré B
    Si un objeto Grupo A usa el método subgrupo, el objeto Grupo B que se crea dentro del método subgrupo tiene los mismos atributos que el Grupo A.

    Por mucho que dentro del método yo haga:

    B = Grupo(“Nombre”, [lista de miembros])

    Y no entiendo por qué pasa eso, ni cómo puedo evitarlo. Por qué el “self” del grupo A pasa a ser el “self” del grupo B.

    Espero haberme explicado con claridad, y gracias por el blog.

    1. No he podido evitar seguir bicheando y he encontrado la solución (en StackOverFlow, la página que más puedes amar y odiar a la vez cuando buscas dudas de programación jajaja)

      Concretamente, aquí:

      https://stackoverflow.com/questions/475871/python-why-use-self-in-a-class

      Podemos resumir mi problema en dos partes:

      1) Por un lado, no tenía ni idea de la diferencia entre “variable de clase” y “variable de instancia”

      2) Por otro lado, vengo de C y C++

      Por la razón 2, por deformación a la hora de definir una clase defino los atributos (o miembros) fuera de todos los métodos (porque en C++ hay que hacerlo así 100%) así que yo todas los atributos de la clase Grupo los definía e inicializaba nulos porque sí. Eso los convierte en “variables de clase”.

      Lo que he creído entender, es que las “variables de clase” son las que se definen en una clase fuera de los métodos de la misma, y su particularidad es que dichas variables son COMPARTIIDAS por todas las instancias de la clase. Si no supongo mal, creo que esto se debe a lo que explicas en la entrada “El sagrado misterio de la inmutabilidad”, ya que al construir Python esas variables luego no se crean nuevas, sino que simplemente se actualiza su valor. En el caso del nombre del grupo no había problema porque al ser un string, un nombre se cambiaba por otro, pero en el caso de los miembros, yo en el método __init__ no asignaba la lista tal cual, sino que (por tontería) utilizaba el método que te permite añadir un nuevo miembro a la lista, evidentemente por cada miembro pasado en esa lista: como usaba el método append, y la lista de miembros era “variable de clase”, no se pisaba el valor, sino que se añadía

      Las “variables de instancia”, por contra, son todas aquellas que se declaran con “self”, por ejemplo, “self.nombre”, y son únicas para cada instancia, de forma que se construyen para cada una. Personalmente creo que por claridad todos los atributos que deban declararse como “variable de instancia” deben ir en el método __init__, se inicialicen o no en el mismo, pero eso ya son manías propias.

      En fin, espero que mi explicación haya quedado clara, de todas formas, en el enlace que he puesto de StackOverFlow, hay una explicación con un ejemplo que a mí me ha servido para entender esto, y solucionarlo.

      Mil gracias igualmente.

      1. ¡Qué tal, José María!
        Lo de amar, de acuerdo, pero odiar StackOverflow?? 😀
        ¡No hay mejor recurso para programadores!!! 😉
        Saludos

Deja un comentario