Autor: Luis Fernando Apáez Álvarez
-Curso PyM-
Proyecto 1 (parte IV)
Fecha: 01 de diciembre del 2022
Dentro de Python, para la POO podemos diferenciar tres tipos de métodos:
Método de instancia: Lo utilizamos para acceder o modificar el estado de un objeto. Dichos métodos deben tener como primer parámetro la palabra self
.
Método de clase: Lo utilizamos para acceder o modificar el estado de una clase. Por ejemplo, si utilizamos un método dentro de una clase que sólo ocupa variables propias de la clase, entonces dicho método es un método de clase. El primer parámetro de los métodos de clase es, por convención, cls
el cual se refiere a la clase.
Método estático: Es un método de utilidad general que realiza una tarea de forma aislada. Dentro de este método, no usamos instancia o variable de clase porque este método estático no toma ningún parámetro como self
y cls
.
Los métodos de clase solo están vinculados con la propia clase y no con los objetos, de modo que éste solo puede acceder a las variables de clase. Este tipo de métodos puede modificar un estado de la clase cambiando así el valor de una variable de clase, lo cual se vería reflejado en todos los objetos de la clase.
Veamos un ejemplo.
# Definimos la clase estudiante
class estudiante:
# nombre de la escuela
escuela = 'UNAM'
# constructor
def __init__(self, nombre, edad):
# atributos
self.nombre = nombre
self.edad = edad
# crearemos un metodo de clase para cambiar el
# nombre de la escuela
@classmethod
def cambio_escuela(cls, nueva_escuela):
cls.escuela = nueva_escuela
# o alternativamente
# cls.escuela = nueva_escuela
Notemos la gran diferencia existente entre el método de clase en comparativa con los métodos de instancia. En los métodos de instancia estamos acostumbrados a colocar el self.
con el cual nos referimos a los objetos en cuestión, luego, para el método de clase tendremos en su lugar cls.
la cual hace referencia a la clase con la cual estemos trabajando, la cual es en este caso la clase estudiante
. Además, tenemos que cls.escuela
es totalmente equivalente a colocar de manera directa estudiante.escuela
.
Pongamos en acción el método de clase cambio_escuela
:
# instanciamos
estud1 = estudiante('Luis', 25)
# accedamos al nombre de la escuela
estud1.escuela
'UNAM'
# creamos otro objeto
estud2 = estudiante('Luisa', 25)
# accedamos al nombre de la escuela
estud2.escuela
'UNAM'
# accedemos al nombre de la escuela de nuestra clase
# estudiante
estudiante.escuela
'UNAM'
# empleamos el metodo de clase
estudiante.cambio_escuela('IPN')
estudiante.escuela
'IPN'
Vemos que los el cambio hecho previamente se ve reflejado en los dos objetos que creamos antes:
print(estud1.escuela)
print(estud2.escuela)
IPN IPN
Veamos otro ejemplo de método de clase
# Definimos la clase estudiante
class estudiante:
# constructor
def __init__(self, nombre, promedio):
# atributos
self.nombre = nombre
self.promedio = promedio
# metodo str
def __str__(self):
return f'Nombre: {self.nombre}. Promedio: {self.promedio}'
# crearemos un metodo de clase para ingresar el promedio
# pero que este en formato de nota
@classmethod
def promedio_nota(cls, nombre, promedio_n):
return cls(nombre, promedio_n)
# o alternativamente
# return estudiante(nombre, promedio_n)
donde lo que está ocurriendo en la última línea es que el método de clase servirá para crear un objeto. Notemos que el método de clase está utilizando a la clase misma para la creación de un objeto. Además, podemos ver que al escribir cls(nombre, promedio_n)
tenemos que nombre
será pasado al atributo self.nombre
del objeto y análogamente promedio_n
.
# instanciamos un objeto
estudiante1 = estudiante('Luis', 8)
print(estudiante1)
Nombre: Luis. Promedio: 8
# creamos el objeto auxiliandonos para ello
# del metodo de clase promedio_nota
estudiante2 = estudiante.promedio_nota('Luis', 'C')
print(estudiante2)
Nombre: Luis. Promedio: C
Así, en este caso estamos utilizando el método de clase de manera indirecta para modificar los atributos de la clase a la hora de crear un objeto.
Finalmente, si definimos una clase padre y dentro de ella tenemos métodos de clase, para todas las clases hijas éstas heredaran dichos métodos de clase.
Por otro lado, consideremos el código de la clase menu
y efectuemos algunos cambios necesarios
# Recordemos la clase menu
class menu:
# en realidad las cadenas cadena(1-6) pueden ser atributos de clase
cadena1 = "De que tipo es tu función:"
cadena2 = "_" * 30 + '\n'
cadena3 = "1: Algebraicas"
cadena4 = "2: Trigonométricas"
cadena5 = "3: Exponencial"
cadena6 = "4: Logarítmica"
# constructor
def __init__(self):
self.fun = ""
self.rango = ""
# Atributo para control de flujo
self.control_f = False
def mensaje_inicial(self):
# Cambiamos valor lógico de control_f
self.control_f = True
print(menu.cadena1)
print(menu.cadena2)
print(menu.cadena3)
print(menu.cadena4)
print(menu.cadena5)
print(menu.cadena6)
print(menu.cadena2)
def info_usuario(self):
# Implementamos el if para el control del flujo
if self.control_f:
n_aux = int(input("Coloca un número del 1 al 4: "))
print("_" * 30)
if n_aux not in (1,2,3,4):
print("Error, debes ingresar un número del 1 al 4")
print("Fin del proceso")
else:
self.fun = input("Ingresa tu función: ")
self.rango = input("Ingresa el rango de graficación: ")
self.rango_inf = self.rango[self.rango.find("(") + 1: self.rango.find(",")]
self.rango_sup = self.rango[self.rango.find(",") + 1: self.rango.find(")")]
if n_aux != 1:
self.fun = "np." + self.fun
else:
self.fun = self.fun
else:
print("Debe invocarse primero el método mensaje_inicial")
Lo que deberás hacer es convertir el método mensaje_inicial()
a un método de clase realizando las modificaciones pertienentes. Nota que deberás hacer que el constructor reciba un parámetro el cual debe ser del tipo booleano, lo cual te permitirá realizar correctamente la definición del método de clase.
De la clase menu
, haz que el atributo self.control_f
sea privado. De tal manera, lo que deberás hacer también será implementar los correspondientes métodos get y set. Nota: para la implementación de los get y set deberás utilizar decoradores.
Consideraremos ahora la clase graficar
, pero la haremos independiente de la clase menu
y realizaremos algunas modificaciones:
# Importamos los módulos necesarios
from IPython.display import display, Math, Markdown
import sympy as sym
from sympy.parsing.sympy_parser import parse_expr
import matplotlib.pyplot as plt
import numpy as np
class graficar:
# constructor
def __init__(self, booleano):
self.__control_f = booleano
# metodo str
def __str__(self):
# requeriremos que primero se ejecute el metodo info_usuario(),
# lo cual controlamos con el siguiente try-except
try:
return f'Rango:({self.rango_inf},{self.rango_sup})\nf(x) = {self.fun}'
except AttributeError:
return 'Primero debe ejecutarse el método info_usuario()!!'
# utilizaremos este metodo de la clase menu:
def info_usuario(self):
# Implementamos el if para el control del flujo
if self.__control_f:
n_aux = int(input("Coloca un número del 1 al 4: "))
print("_" * 30)
if n_aux not in (1,2,3,4):
print("Error, debes ingresar un número del 1 al 4")
print("Fin del proceso")
else:
self.fun = input("Ingresa tu función: ")
self.rango = input("Ingresa el rango de graficación: ")
self.rango_inf = self.rango[self.rango.find("(") + 1: self.rango.find(",")]
self.rango_sup = self.rango[self.rango.find(",") + 1: self.rango.find(")")]
if n_aux != 1:
self.fun = "np." + self.fun
else:
self.fun = self.fun
else:
print("Debe configurarse un valor inicial booleano de True")
def Graficador(self):
if self.__control_f:
x = np.arange(eval(self.rango_inf), eval(self.rango_sup), 0.01)
y = self.fun
plt.plot(x, eval(y))
plt.grid()
plt.show()
else:
print("Debe configurarse un valor inicial booleano de True")
# Método estático para imprimir en Markdown
@staticmethod
def printmd(cadena):
return display(Markdown(cadena))
# Definimos calc_lim como un método estático
@staticmethod
def calc_lim(func, lim):
x = sym.symbols('x')
cad_lim = '\lim\limits_{x\\rightarrow 0}'
cad_lim2 = cad_lim.replace('0', lim)
y = parse_expr(func)
limite = sym.limit(y,x,lim)
return graficar.printmd(f'Cálculo: ${cad_lim2}{sym.latex(y)}={limite}$')
donde notamos que en el return del método calc_lim()
para poder acceder al método estático printmd()
fue necesario colocar primero el nombre de la clase. Cabe mencionar que en la parte pasada del proyecto no fue necesario colocar graficar.printmd
pues en alguna celda de código anterio se había definido y ejecutado la función
def printmd(cadena):
return display(Markdown(cadena))
Implementa un método estático a la clase graficar
para poder calcular derivadas. Dicho método debe ser similar al método calc_lim()
.