13. Programación orientada a objetos#

13.1. Introducción a la programación orientada a objetos#

La Programación Orientada a Objetos (POO) es un paradigma que organiza el código en torno a objetos, los cuales representan entidades del mundo real o conceptos abstractos. Un objeto posee ciertos datos (atributos) y comportamientos (métodos).

Este enfoque permite reutilización de código, facilidad de mantenimiento y escalabilidad, sobre todo pensando en proyectos que implican más allá de unos cientos de líneas.

13.2. Clases, atributos y métodos#

En la programación orientada a objetos, las clases, atributos y métodos, son conceptos básicos que debemos comprender para asimilar el paradigma.

Una clase nos permite definir las propiedades que tiene un objeto (atributos) y cómo se comportará (métodos). Una analogía que puede servir es el proceso de fabricación de una máquina. Para poder fabricarla debemos generar un conjunto de planos de fabricación, ensamblaje e integración que especifiquen las dimensiones, geometría, materiales, proceso de fabricación, instrucciones de ensamble, etc. Todas estas especificaciones garantizarán que tendremos una máquina de acuerdo con lo que se planeó en el proceso de diseño. En la POO, una clase es como el plano de fabricación: un conjunto de instrucciones que servirán para construir el objeto con ciertos atributos y métodos. Piensa en los atributos como las propiedades de la máquina (peso, costo, velocidad, temperatura, potencia, etc.) y en los métodos como las acciones o comportamiento que puede tener (encender, apagar, acelerar, detener, alertar, etc.).

Los atributos de una clase pueden entenderse como variables que pertenecen a cada instancia (u objeto) creada a partir de esa clase. Los métodos son funciones definidas dentro de la clase que pueden acceder y modificar los atributos del objeto.

En Python para definir una clase se utiliza la palabra reservada class, con una sintaxis básica como se muestra enseguida:

class NombreDeLaClase:
    def __init__(self,atributo1,...)
        self.atributo1 = atributo1
        # más atributos e instrucciones de inicialización

    def metodo(self):
        # instrucciones para hacer algo

El método __init__ es un método especial, usualmente conocido como el constructor, que se ejecuta cuando se crea un nuevo objeto de la clase; self es una referencia al propio objeto dentro de una clase y nos permite acceder a los atributos y métodos del objeto dentro de la clase.

Vamos a crear una clase Vector para ir ejemplificando lo anterior. Por ahora, la clase Vector tendrá tres atributos: las componentes cartesianas \(x,y,z\) del vector.

class Vector:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y 
        self.z = z

Cuándo hacemos self.x = x implica que el valor pasado como argumento x lo almacenamos como el atributo con ese mismo nombre.

Para crear un objeto de la clase Vector debemos llamar a la clase como lo hacemos con una función, pasando como argumentos los parámetros definidos en el constructor (método __init__), exceptuando a self que no se pasa explícitamente al crear el objeto:

u = Vector(1,0,2)

Hemos creado aquí un objeto de la clase Vector, puedes verificarlo con type:

print(type(u))
<class '__main__.Vector'>

Podemos acceder de la forma habitual a los atributos del objeto:

print(f"u = ({u.x}, {u.y}, {u.z})") # accedemos a los atributos
u = (1, 0, 2)

Vamos a agregar un método magnitud a nuestra clase, el cual simplemente calculará la magnitud del vector:

class Vector:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y 
        self.z = z
    
    def magnitud(self):
        return (self.x**2 + self.y**2 + self.z**2)**(1/2)

Creamos un par de objetos de esta clase:

u = Vector(1,-1,2)
v = Vector(0,0,1)

Y usamos el método recién agregado para calcular la magnitud de ambos vectores:

print(f"Magnitud de u = {u.magnitud()}")
print(f"Magnitud de v = {v.magnitud()}")
Magnitud de u = 2.449489742783178
Magnitud de v = 1.0

Observa que los métodos de la clase deben definirse siempre con self como primer parámetro, pero este no se pasa como argumento al momento de invocar el método, tal y como puedes notar en magnitud que no le pasamos argumento alguno.

Los métodos también permiten que el objeto interactúe con objetos de su misma clase. Para ejemplificar vamos a agregar un método producto_punto a nuestra clase. Recuerda que el producto punto es una operación que se define entre dos vectores. Este método recibe un argumento other que corresponde al otro objeto de tipo Vector con el cual se realiza la operación:

class Vector:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y 
        self.z = z
    
    def magnitud(self):
        return (self.x**2 + self.y**2 + self.z**2)**(1/2)

    def producto_punto(self,other):
        return self.x*other.x + self.y*other.y + self.z*other.z

Creamos ahora un par de objetos de tipo Vector y usamos producto_punto para calcular el producto punto:

u = Vector(-5,3,0)
v = Vector(2,4,1)
print(u.producto_punto(v))
2

Da exactamente lo mismo si usamos el método producto_punto del objeto v:

print(v.producto_punto(u))
2

Hasta ahora hemos implementado un par de métodos que devuelven un valor numérico (escalar). Pero también es posible, y común, que un método devuelva otro objeto de la misma clase. Por ejemplo, al sumar dos vectores, el resultado no es un número, sino otro vector. Si quisiéramos implementar un método que sume dos vectores, este debería devolver otro objeto de la clase Vector. Veamos a continuación la implementación:

class Vector:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y 
        self.z = z
    
    def magnitud(self):
        return (self.x**2 + self.y**2 + self.z**2)**(1/2)

    def producto_punto(self,other):
        return self.x*other.x + self.y*other.y + self.z*other.z

    def sumar(self,other):
        return Vector(self.x+other.x, self.y+other.y, self.z+other.z)

Definimos un par de vectores y usamos el método sumar:

u = Vector(4,0,-7)
v = Vector(5,3,2)
w = u.sumar(v)

Comprobamos que w es un objeto de la clase Vector:

print(type(w))
<class '__main__.Vector'>

Y mostramos las componentes del vector:

print(f"w = ({w.x}, {w.y}, {w.z})")
w = (0, 3, -5)

En general, los atributos de un objeto se pueden modificar por asignación, por ejemplo:

w.x = 0 # modificando el atributo "x" del vector "w"
print(f"w = ({w.x}, {w.y}, {w.z})")
w = (0, 3, -5)

Es más, podemos agregar nuevos atributos al objeto:

w.atributox = -1000

Si quieres ver todos los atributos actuales de un objeto puedes utilizar la función vars:

vars(w)
{'x': 0, 'y': 3, 'z': -5, 'atributox': -1000}

En este punto es bueno que sepas que existen maneras de evitar que se agreguen atributos a un objeto o que se modifique el valor, el cual es un comportamiento típicamente deseable en muchos diseños. Esto es algo que trataremos en la siguiente sección.

13.3. Encapsulamiento#

13.4. Herencia#

13.5. Polimorfismo#

13.6. Composición#

13.7. Métodos mágicos (dunder methods)#

13.8. Un poco más sobre los métodos#

13.9. Ejercicios#


Implementa una clase Triangulo que reciba tres lados y verifique que forman un triángulo válido. Debe incluir los siguientes atributos y métodos:

  • lados: un atributo para guardar en forma de lista los lados del triángulo.

  • area: un atributo que debe calcular y devolver el área del triángulo.

  • es_equilatero(): un método que determina si el triángulo es equilátero, debe devolver un booleano.

  • es_rectangulo(): un método que determina si es un triángulo rectángulo, debe devolver un booleano.

  • angulos(): un método que calcula y devuelve los ángulos internos del triángulo.


Crea una clase denominada Engrane, la cual debe tener los siguientes atributos:

  • Número de dientes

  • Módulo

  • Diámetro

El constructor debe recibir únicamente los argumentos asociados el número de dientes y módulo. El diámetro es un atributo que debe calcularse, y debes cuidar que no pueda modificarse directamente por asignación.


Programa una clase llamada MyString que debes heredar del tipo str de Python. Realiza las modificaciones necesarias para que el operador + agregue un espacio a la concatenación. Por ejemplo:

s = MyString('hola')
print(s+s)

En lo anterior la salida esperada es:

hola hola