La programación orientada a objetos (POO) es un paradigma de programación que se basa en el concepto de «objetos», que son entidades que contienen datos y comportamientos. En Python, la POO se implementa mediante la creación de clases, que son plantillas para crear objetos, pero todo esto lo trataremos en detalle a continuación.
En esta entrada de nuestro curso gratuito de Python desde Cero en español comenzaremos a estudiar la programación orientada a objetos donde desarrollaremos vários ejemplos, colocando su código y diferentes videos explicando las generalidades de este paradigma, por lo tanto te recomiendo que le des un vistazo al el Curso Completo de Python dando clic aquí si crees que todavía debes cubrir algunos aspectos de este lenguaje de programación.
También te puedes suscribir al canal de YouTube si te gusta la programación, la teoría del control y la automatización:
¿Qué aprenderás en esta página?
Si estás interesado en aprender sobre programación orientada a objetos (POO) en Python este curso, totalmente gratuito, que he preparado para ti en este post será todo lo que necesites ya que aprenderás desde cero cómo utilizar POO en Python y cómo aprovechar las ventajas que ofrece este paradigma de programación.
Durante este recorrido en POO podrás conocer las bases teóricas de esta filosofía de programación y principalmente cómo aplicarla en nuestros scripts en Python. Aprenderás a crear clases y objetos empleando descomposiciones, abstracciones, polimorfismos y herencias.
Además, en este mismo post podrás encontrar numerosos ejemplos de POO en python, para que puedas entender mejor cómo aplicar cada concepto y puedas darle modularidad y reutilización a tú código para crear programas más eficientes y fáciles de mantener.
Así que vamos a comenzar el curso de Python empleando Programación Orientada a Objetos!!
Programación Orientada a Objetos
Una de las ventajas más importantes de la POO es que permite la reutilización de código mediante la creación de clases que pueden ser utilizadas en distintos programas. Además, la POO también permite una mejor organización del código y una mayor facilidad de mantenimiento y expansión del mismo.
Para comenzar a trabajar con POO en Python, es necesario conocer algunos conceptos clave, como las clases, los objetos, los atributos y los métodos. Las clases son la base de la POO en Python y se utilizan para crear objetos. Los objetos son instancias de una clase y pueden tener atributos (datos) y métodos (funciones). Los atributos son variables que se almacenan dentro de un objeto y pueden ser accedidos mediante el uso de «dot notation», mientras que los métodos son funciones que se almacenan dentro de un objeto y pueden ser invocadas para realizar una acción.
El paradigma de Programación Orientada a Objetos se compone de 4 elementos:
- Clases
- Propiedades
- Métodos
- Objetos
Definiciones de POO Python
Clases: es un molde que permite crear un objeto. Una clase es capaz de definir un objeto junto con sus propiedades (atributos) y sus modificaciones (métodos)
Objeto: Es la instancia de una clase y consta de:
Estado. Representado por los atributos del objeto, que reflejan sus propiedades
Comportamiento. Representado por los métodos del objeto, que reflejan su respuesta a otros objetos
Identidad. Cada objeto tiene un nombre único que le permite interactuar con otros objetos
En Python todo es un objeto y tiene un tipo.
- Si es un objeto, significa que este va a tener alguna representación dentro de la memoria
- Y que tenga un tipo significa que se pueden encapsular los datos y el comportamiento dentro de un solo objeto.
Formas de interactuar con un objeto:
- Creación
- Manipulación
- Destrucción
Clases
Para crear una clase en Python, se utiliza la palabra clave «class» seguida del nombre de la clase y dos puntos. A continuación, se define el código de la clase encerrado entre dos bloques de indentación. Por ejemplo:
class Clase: # Código de la clase aquí pass
Note que según la convención del PEP8, se recomienda nombrar la clase siempre con la primera letra en mayúscula y las restantes en minúscula
Vamos a crear nuestra primera clase para entender un poco su sintaxis: la clase Laptop
class Laptop(): """ Clase empleada para trabajar con Laptops """ has_ssd = True
Tenemos nuestra plantilla del objeto Laptop, que únicamente contiene si el laptop tiene almacenamiento SSD o NO como variable estática (variable que pertenece a la clase)
Instanciación de Objetos
Una vez que se ha creado una clase, se puede crear un objeto de esa clase mediante la llamada a la clase seguida de paréntesis. Por ejemplo:
mi_objeto = MiClase()
Para agregar atributos a un objeto, se puede hacer directamente utilizando la «dot notation». Por ejemplo:
mi_objeto.atributo = "valor"
Una vez conocemos la sintaxis, vamos a instanciar el objeto de nuestra clase Laptop. En otras palabras estamos usando el molde Laptop para crear un objeto nuevo para eso tendremos que instanciarlo del siguiente modo:
laptop1 = Laptop()
Hemos construido un objeto de la clase Laptop con nuestra plantilla, por lo tanto, laptop1 se trata de un computador portátil con disco de almacenamiento SSD. De hecho lo podemos comprobar si usamos la función Built-in de Python dir().
laptop1 = Laptop() print(dir(laptop1)) print(Laptop.has_ssd) print(Laptop.__doc__)
Del código anterior a través de dir() podemos ver cuales son todos los métodos que posee la clase Laptop. Y también la variable de la clase has_ssd. Adicionalmente, podemos hacer uso de unas funciones internas que crea Python al momento de generar una clase conocidas como Métodos Mágicos de Python, en este caso estamos llamando el método mágico __doc__ que recupera el comentário que hemos colocado a la clase. El terminal entoces me entrega la siguiente información:
Método Constructor
También es posible agregar atributos a un objeto mediante el uso de un método especial llamado «init«. Este método se invoca automáticamente cuando se crea un objeto y se utiliza para inicializar los atributos del objeto. Por ejemplo:
class MiClase: def __init__(self, atributo1, atributo2): self.atributo1 = atributo1 self.atributo2 = atributo2
Por parámetros de entrada puede recibir valores para los atributos de cada objeto.
Atributo: Son las variables que definen a los objetos de una clase. Son sus características.
Parámetro SELF
En Python siempre los métodos ubicados dentro de una clase comienzan con un parámetro llamado self y luego los parámetros de inicialización.
Se puede llamar de otro nombre si se desea, pero por convención siempre se emplea la palabra self.
Con el tiempo, comenzaremos a notar que este parámetro que a su vez es una variable, la podemos utilizar dentro de la clase, para poder recuperar cada uno de los atributos dentro de los distintos métodos o funciones que iremos programando dentro de la clase.
self (a si mismo), es una variable útil para acceder a las variables del propio objeto.
El constructor liga todas las variables con el atributo self
Ejemplo Constructor Python
Continuando con nuestra clase Laptop, es hora de crear su método constructor el cual va a definir el objeto de la clase con sus diferentes atributos. En este caso, un computador portatil o un Laptop puede caracterizarse por su marca, tipo de procesador, tipo de memoria y tipo de almacenamiento (disco rígido o disco de estado sólido ssd)
class Laptop(): """ Clase empleada para trabajar con Laptops """ def __init__(self, marca, procesador, memoria, ssd): self.marca = marca self.procesador = procesador self.memoria = memoria self.has_ssd = ssd
Si instanciamos nuevamente el objeto, deberemos asignar en los parámetros de entrada todos estos atributos que posee el método constructor de Python.
laptop1 = Laptop('Dell', 'Core i7', 16, True)
Instancias
Mientras que la clase es un molde, a los objetos creados se les conoce como instancias.
Cuando se crea una instancia, se ejecuta el método __init__
Todos los métodos de una clase reciben implícitamente como primer parámetro self
Instanciación es cuando el objeto se crea en la memoria mientras se ejecuta el código. Una vez dicho objeto está instanciado podremos acceder a los atributos del objeto en cualquier momento usando el Dot Notation.
laptop1.marca laptop1.procesador
Adicionalmente, podemos cargar alguno de los atributos del método por algún valor por defecto. En este caso, vamos a suponer que las Laptops por defecto en mi tienda siempre tienen una memoria de 16Gb y que siempre tienen disco de almacenamiento sólido por SSD. De esa manera, el método constructor de Python con parámetros por defecto se define de la siguiente forma:
class Laptop(): """ Clase empleada para trabajar con Laptops """ def __init__(self, marca, procesador, memoria = 16, ssd = True): self.marca = marca self.procesador = procesador self.memoria = memoria self.has_ssd = ssd laptop1 = Laptop('Dell', 'Core i7')
Método Destructor
El método __del__
en Python es un método especial que se llama justo antes de que un objeto sea destruido. Este método se conoce como el «método destructor». Es una de las características más únicas y poderosas de la programación orientada a objetos en Python.
El método __del__
se utiliza para realizar cualquier tarea necesaria antes de que un objeto sea eliminado, como cerrar archivos o liberar recursos. Por ejemplo, si un objeto representa un archivo abierto, el método __del__
puede utilizarse para asegurarse de que el archivo se cierre correctamente antes de ser eliminado.
Es importante tener en cuenta que el método __del__
no garantiza un orden específico en el que se eliminarán los objetos. Por lo tanto, no se deben confiar en el método __del__
para realizar tareas críticas, como guardar datos importantes. En su lugar, es mejor utilizar otros métodos, como un método de «guardar» explícito, para realizar estas tareas.
Además, el método __del__
solo se llama una vez cuando un objeto ya no está en uso. Si un objeto tiene referencias a otro objeto, ambos objetos seguirán existiendo hasta que no haya ninguna referencia a ellos.
Como ejemplo de nuestra clase Laptop, vamos a generar un método destructor que imprima en pantalla un mensaje una vez el método ha sido eliminado:
class Laptop(): """ Clase empleada para trabajar con Laptops """ def __del__(self): print("Se a eliminado el Laptop") del laptop1
Descomposición y Abstracción
La Descomposición y Abstracción son dos conceptos clave en la Programación Orientada a Objetos (POO) en Python.
La descomposición implica dividir un sistema complejo en componentes más pequeños y manejables, mientras que la abstracción se refiere a ocultar detalles innecesarios para enfocarse en la funcionalidad esencial.
En POO, la descomposición se logra mediante la creación de objetos que representan componentes individuales del sistema. Cada objeto tiene sus propias características y comportamientos específicos.
La abstracción se logra a través de la creación de clases que definen la estructura y el comportamiento de un objeto. Al crear objetos a partir de clases, los detalles internos del objeto pueden ser ocultos y solo la funcionalidad esencial es expuesta.
Métodos de una Clase en Python
Existen 3 tipos de métodos que podemos programar cuando usamos la programación orientada a objetos con python:
- Métodos de instancia: Métodos de cada objeto (acciones o funciones de cada uno)
- Métodos estáticos
- Métodos de clase
Métodos de instancia
Toman el parámetro self como primer parámetro representando la instancia del método. Seguidamente pueden tener más inputs.
Vamos a crear una clase Cilindro con sus respectivos métodos de instancia para ver ejemplos y ejercicios resueltos sobre programación orientada a objetos.
class Cilindro: #Constructor def __init__(self, radio = 1, height = 1, color = 'green'): self.radio = radio self.height = height self.color = color #Métodos de Instancia (Parametro de entrada es propia instancia) def perimeter(self): return 2*self.radio + self.height #Método de Instancia def area_base(self): return 3.1416 * self.radio ** 2 #Método de Instancia def volumen(self): return 3.1416 * self.radio ** 2 * self.height cilindro1 = Cilindro(2, 3, 'red') print(f'El perimetro del cilindro es {cilindro1.perimeter()}') print(f'El área del cilindro es {cilindro1.area()}') print(f'El volumen del cilindro es {cilindro1.volumen()}') #Cambiando algún atributo de la clase cilindro1.height = 1.5 print(f'El perimetro del cilindro es {cilindro1.perimeter()}') print(f'El área del cilindro es {cilindro1.area()}') print(f'El volumen del cilindro es {cilindro1.volumen()}')
Podemos crear un método de instancia empleando el método __str__ para devolver algún tipo de string al momento de colocar la instancia o objeto dentro de un print.
def __str__(self): return (f'Radio: {self.radio}\nHeight: {self.height}')
Este método se invoca con la función PRINT
print(cilindro1)
Método __str__()
En Python, el método __str__()
es un método especial que se utiliza para definir cómo se debe imprimir una representación en cadena (string) de un objeto de una clase personalizada. Este método se llama automáticamente cuando se intenta imprimir el objeto utilizando la función print()
.
El método __str__()
debe devolver una cadena (string) que represente el objeto de la clase personalizada de una manera legible para los humanos. Puedes personalizar la cadena que se devuelve para incluir información específica sobre el objeto.
Veamos un ejemplo de cómo se utiliza el método __str__()
en una clase en Python:
class Coche: def __init__(self, marca, modelo, año): self.marca = marca self.modelo = modelo self.año = año def __str__(self): return f"Coche {self.marca} {self.modelo} del año {self.año}" mi_coche = Coche("Toyota", "Corolla", 2023) print(mi_coche)
En este ejemplo, la clase Coche
tiene un método __str__()
que devuelve una cadena que incluye la marca, modelo y año del coche. Cuando se llama a la función print()
en el objeto mi_coche
, se imprimirá la representación en cadena devuelta por el método __str__()
, que en este caso es «Coche Toyota Corolla del año 2023».
Al personalizar el método __str__()
, puedes hacer que la impresión de objetos de una clase personalizada sea más legible y significativa para los usuarios y programadores. También es una forma útil de depurar y entender el comportamiento de los objetos.
Métodos Estáticos
Los métodos estáticos en Python son extremadamente similares a los métodos de nivel de clase de Python (métodos de instancia) , con la diferencia de que un método estático está vinculado a una clase en lugar de a los objetos de esa clase.
Esto significa que se puede llamar a un método estático sin un objeto para esa clase. Esto también significa que los métodos estáticos no pueden modificar el estado de un objeto ya que no están vinculados a él. Veamos cómo podemos crear métodos estáticos en Python.
Los métodos estáticos se definen usando el decorador @staticmethod, que se añade antes de definir el método estático respectivo.
Los decoradores nos permiten alterar el comportamiento de las funciones o clases.
Los métodos estáticos se invocan sobre la propia clase. Para eso continuamos con el ejemplo de la Clase Cilindro para ver como funcionan los métodos estáticos con Python. El siguiente código debe ser anexado al código anterior mostrado en los métodos de clase.
#Método Estático @staticmethod def are_equal_height(cilindro1, cilindro2): if cilindro1.height == cilindro2.height: return True return False
Para invocar los métodos estáticos
cilindro1 = Cilindro(0.5, 3, 'red') cilindro2 = Cilindro(1.2, 3, 'blue') #Metodo Estático se invoca sobre la propia clase print(Cilindro.are_equal_height(cilindro1, cilindro2))
Los métodos estáticos tienen un caso de uso muy claro. Cuando necesitamos alguna funcionalidad que no sea un Objeto sino la clase completa, hacemos un método estático. Esto es bastante ventajoso cuando necesitamos crear métodos de utilidad, ya que generalmente no están vinculados al ciclo de vida de un objeto.
Finalmente, tenga en cuenta que, en un método estático, no necesitamos self que se pase como primer argumento.
Métodos de clase
Para usar este método debemos emplear el decorador @classmethod para marcarlo como un método de clase .
En lugar de aceptar un self parámetro de entrada, los métodos de clase toman como primer argumento un cls que apunta a la clase, y no a la instancia del objeto, cuando se llama al método.
Debido a que el método de clase solo tiene acceso a este argumento cls, no puede modificar el estado de la instancia del objeto. Eso requeriría acceso a self. Sin embargo, los métodos de clase aún pueden modificar el estado de la clase que se aplica en todas las instancias de la clase.
Continuamos con el ejemplo de la Clase Cilindro para entender como funcionan los métodos de clase en Python. El siguiente código debe ser anexado al código anterior mostrado en los métodos de clase.
#Método de Clase @classmethod def tank_cilinder(cls): radio = 2 height = 3.5 return cls(radio, height)
Para invocar un método de clase con python lo hacemos de la siguiente forma:
cilindro3 = Cilindro.tank_cilinder() print(cilindro3)
Cómo utilizar las property en Python de manera eficiente
Las property en Python te permiten acceder y modificar atributos de clase de una manera más fácil y segura. Utilizando la sintaxis de decorator, puedes definir una función como una propiedad y luego acceder a ella como si fuera un atributo. Esto te permite controlar el acceso y modificación de los atributos de una manera más clara y segura.
Para manejar los atributos de un objeto, podemos utilizar el decorador @property que permite a un método ser accedido como un atributo, omitiendo así el uso de paréntesis vacíos.
Observación. los paréntesis son vacíos cuando en un método no hay parámetros que indicar.
Por ejemplo, si quieres asegurarte de que un atributo sólo puede ser leído pero no modificado, puedes utilizar la property como un decorador. Así, cualquier intento de modificar el atributo lanzará una excepción.
Volviendo a nuestro ejemplo anterior de la Clase Cilindro, recordemos que habíamos creado el siguiente método de instancia que calcula el área:
def area(self): return 3.1416 * self.radio ** 2
Para invocar el método anterior podíamos hacerlo de la siguiente forma:
print(f'El área del cilindro es {cilindro1.area()}')
Observemos que el método de instancia area(self) solo tiene la variable de autoreferencia self como argumento de entrada, por lo tanto, este método es un fiel candidato a convertirlo en una propiedad agregando el decorador @property.
@property def area(self): return 3.1416 * self.radio ** 2
De esa forma, al momento de llamar este método, podremos omitir los parentesis y simplementa acceder al área como si fuese un atributo de la clase.
print(f'El área del cilindro es {cilindro1.area}')
Recuerde nuevamente que el método área se ha transformado en una propiedad (Solo sirve como lectura), por lo tanto no intente modificar su valor porque de lo contrario aparecerá un AttributeError, es decir que NO podemos hacer lo siguiente:
cilindro1.area = 2.5 #Esto genera un error, porque es solo de lectura
Para poder modificar una propiedad, necesitamos utilizar el método .setter()
@Nombre_Propiedad.setter
@property def area(self): return pi * self.radio ** 2 #Modificando la propiedad @area.setter def area(self, area): self.radio = (area / pi) ** 0.5
Note que para realmente hacer la modificación del área usando el método SETTER, lo que debemos modificar dentro de dicho método es el atributo original de la clase, self.radio, dado que modificando el radio consigo indirectamente poder modificar el área del cilindro.
Herencia (Inheritance)
En la programación orientada a objetos con Python, la herencia es un mecanismo fundamental que facilita la creación de una jerarquía de clases. Se establece una relación de parentesco entre las clases, donde la clase superior se denomina superclase y la clase derivada, subclase. La herencia posibilita que las subclases compartan y extiendan el comportamiento de la superclase sin necesidad de reescribir código, adherente al principio «Don’t Repeat Yourself» (DRY). Este principio es esencial para mantener la simplicidad y eficiencia en el código, reduciendo la duplicación y facilitando el mantenimiento y la escalabilidad.
Por ejemplo, consideremos una clase base llamada Polygon
, que define propiedades y comportamientos generales de un polígono:
class Polygon: def __init__(self, number_sides): self.num = number_sides self.sides = [0] * number_sides def input_sides(self): self.sides = [float(input(f'Side {i+1}: ')) for i in range(self.num)] def disp_sides(self): for i in range(self.num): print(f'Side {i+1} is {self.sides[i]}')
La clase Polygon
puede ser extendida por una subclase Triangle
, que hereda las propiedades y métodos de Polygon
:
class Triangle(Polygon): def __init__(self): super().__init__(3) # Invoca el constructor de la superclase para un triángulo de 3 lados def findArea(self): a, b, c = self.sides s = (a + b + c) / 2 # Calcula el semiperímetro area = (s*(s-a)*(s-b)*(s-c)) ** 0.5 print(f'The area of the triangle is {area:.2f}')
La función super()
en la subclase Triangle
invoca al constructor de la superclase Polygon
, permitiendo que Triangle
utilice sus métodos y atributos. Así, se evita la redundancia en la definición de métodos comunes como input_sides()
y disp_sides()
.
La instancia de Triangle
puede usar estos métodos heredados y también sus propios métodos, como se muestra a continuación:
trian = Triangle() trian.input_sides() trian.disp_sides() trian.findArea()
Python también admite la herencia múltiple, permitiendo que una subclase herede de más de una superclase. Cuando las superclases tienen métodos comunes, Python prioriza la superclase que está más a la izquierda en la declaración de la subclase.
Por ejemplo:
class A: def __init__(self): print('Soy la clase A') def a(self): print('Método heredado de clase A') class B: def __init__(self): print('Soy la clase B') def b(self): print('Método heredado de clase B') class C(A, B): # Hereda de A y B, con A como prioridad def c(self): print('Método de clase C')
En este caso, si A
y B
tienen métodos con el mismo nombre, el método de la clase A
tendría prioridad en la instancia de C
.
La herencia y la reutilización de código mediante estos principios refuerzan la modularidad y la capacidad de mantenimiento del software, aspectos esenciales para el desarrollo eficaz y sostenible.
Polimorfismo
El polimorfismo es un pilar fundamental en la programación orientada a objetos y se refiere a la capacidad que tienen los objetos de comportarse de múltiples formas. En Python, esta característica permite que una subclase modifique el comportamiento de un método heredado de la superclase. Para lograr esto, se redefine el método en la subclase con el mismo nombre que en la superclase pero con una implementación distinta, adecuada a la subclase.
Por ejemplo, si se tiene una clase Polygon
con un método area
y una subclase Triangle
, se puede redefinir el método area
específicamente para Triangle
:
class Polygon: # ... definición previa de la clase ... def area(self): pass # Implementación genérica o abstracta class Triangle(Polygon): def area(self): a, b, c = self.sides s = (a + b + c) / 2 return (s*(s-a)*(s-b)*(s-c)) ** 0.5
En este caso, el método area
se comporta de manera polimórfica. Aunque se invoca con el mismo nombre, su comportamiento varía dependiendo del tipo de objeto que lo ejecute, lo cual es un ejemplo del polimorfismo en acción.
Variables Privadas
Python adopta una filosofía de «somos todos adultos aquí», en la que se confía en que los desarrolladores no accederán ni modificarán datos que no deberían. A pesar de eso, Python proporciona una forma de simular el acceso privado a través de una técnica conocida como name mangling.
El mangling se logra prefijando el nombre del atributo con dos guiones bajos (__
). Esto le indica a Python que cambie el nombre del atributo para incluir el nombre de la clase, lo que hace que sea más difícil pero no imposible acceder al atributo desde fuera de la clase.
Por ejemplo:
class MyClass: def __init__(self): self.__private_var = 42 def get_private(self): return self.__private_var obj = MyClass() print(obj.get_private()) # Funciona correctamente print(obj.__private_var) # Levantará un AttributeError
En el ejemplo anterior, __private_var
es un atributo al que se le ha aplicado mangling, y aunque técnicamente es accesible, se entiende que no debe usarse fuera de la definición de la clase MyClass
.
Es importante señalar que aunque los atributos con mangling no son accesibles directamente, aún pueden ser modificados o accedidos indirectamente, lo que significa que la privacidad en Python es una convención, más que una restricción de lenguaje estricta.
Eso es todo por la entrada del dia de hoy, espero les haya gustado y hayan aprendido algo nuevo. Si te ha servido el contenido de esta entrada, de los videos y los códigos de implementación y deseas apoyar mi trabajo invitandome a un café super barato, puedes hacerlo en el siguiente link:
👉 Invitar a Sergio a un Café ☕️
Que esten muy bien, nos vemos en la siguiente entrada.
Mi nombre es Sergio Andres Castaño Giraldo, y en este sitio web voy a compartir una de las cosas que mas me gusta en la vida y es sobre la Ingeniería de Control y Automatización. El sitio web estará en constante crecimiento, voy a ir publicando material sobre el asunto desde temas básicos hasta temas un poco más complejos. Suscríbete al sitio web, dale me gusta a la página en Facebook y únete al canal de youtube. Espero de corazón que la información que comparto en este sitio, te pueda ser de utilidad. Y nuevamente te doy las gracias y la bienvenida a control automático educación.