Patrones para Programación orientada a Objetos en Python
Contents
Patrones para Programación orientada a Objetos en Python #
Introducción#
En esta lección hacemos una revisión técnica de las cosas que debemos tener en cuenta para diseñar clase más robustas. Esta revisión está basada en el la lectura de Medium por Michal Oleszak.
Ocho consejos para crear mejores clases#
Existe una gran diferencia entre diseñar un objeto que simplemente funcione y escribir un código de calidad que aproveche al máximo los beneficios del enfoque de programación orientada a objetos (POO). En esta sección revisamos ocho pasos que conducen a las clases de Python de buena calidad. Estos pasos son:
Establecer atributos en el constructor.
Distinguir datos y métodos a nivel de clase y a nivel de instancia.
Determina lo que es igual.
Proporcione representaciones de cadenas.
Conoce lo que es estático.
Decidir lo que es interno y privado.
Establecer el acceso a los atributos.
Utilice cadenas de documentación.
En Python, los objetos se definen por sus planos llamados clases . Los atributos de clase se representan como variables de Python y sus métodos son funciones de Python. Consideraremos la siguiente clase de ejemplo de juguete que representa una cuenta bancaria.
class CuentaBancaria:
def set_cliente(self, cliente):
self.cliente = cliente
def set_saldo(self, saldo):
self.saldo = saldo
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
La clsse dispone de cuatro métodos, permitiéndonos configurar el titular (cliente) de la cuenta o depositar dinero, entre otros. Una vez que establecemos el cliente crea el atributo. Esta clase podría considerarse completamente funcional, en el sentido de que podemos trabajar con ella. Puedo crear una cuenta para mí sin dinero en efectivo y hacer algunas transacciones:
# primeras transacciones
# crea instancia de CuentaBancaria
mi_cuenta = CuentaBancaria()
# Configura el cliente y el saldo inicial
mi_cuenta.set_cliente("María")
mi_cuenta.set_saldo(0)
# dos transacciones
mi_cuenta.deposito(1000)
mi_cuenta.retiro(300)
print(mi_cuenta.cliente, mi_cuenta.saldo)
María 700
Este código aunque funcional está bastante mal escrito. Veamos como escribir el código correctamente. Por ejemplo, se podría intentar hacer las transacciones sin crear el saldo inicial, lo cual llevaría a un error.
Establecer atributos en el constructor.#
El primer consejo es establezca atributos en el constructor para asegurarse de que existan y se puedan encontrar fácilmente en un solo lugar
. Recuerde que el constructor se define con la función __init__()
. Realmente existe una forma de definir constructores alternativos que sobrecargan a la función __init__()
, como veremos en la siguiente sección. Para definir constructores alatrnativo usaremos métodos de clase, no estudiados hasta ahora en esta lección.
from datetime import datetime
class CuentaBancaria:
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < -10_000:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
El valor de saldo es definido por defecto. Además se ha agregado un control que genera un error si se intenta crea una cuenta con un saldo muy negativo. A continuación instanciamos un objeto e imprimimos sus atributos.
# Crea un objeto con saldo inicial = 0
mi_cuenta = CuentaBancaria('María', 151348)
print(mi_cuenta.cliente, mi_cuenta.numero_cuenta, mi_cuenta.saldo, mi_cuenta.creada_en)
María 151348 0 2022-08-30
Distinguir datos y métodos a nivel de clase y a nivel de instancia#
En la POO algunos atributos y métodos son por naturaleza inherentes a la clase en general, mientras que otros son específicos de las instancias de esta clase. Hacer una distinción entre los dos es vital para asegurarse de que la cuenta bancaria de un individuo no cambie la forma en que funcionan las cuentas bancarias en general.
En esta lección solamente hemos definidos atributos y métodos inherentes a las instancias de clase. Los métodos de nivel de clase son útiles, entre otros, para crear objetos de clase a partir de fuentes externas, como archivos csv. Métodos de clase permiten por ejemplo crear constructores alternativos para los objetos (instancias de clase).
Definiendo un método de clase para crear un objeto con información en un archivo#
Es posible crear métodos de clase en Python, que usan un apuntador usualmente llamado cls
en lugar de self
, que apunta a donde esta definida la clase, es decir, no necesitan una instancia para existir.
Su caso de uso popular es crear una instancia a partir de fuentes externas. Es posible que deseemos tener un método que cree una instancia de CuentaBancaria
a partir de un archivo CSV que contenga por ejemplo el cliente y el número de cuenta en el formato separado por comas, como por ejemplo
María, 123
Para hacerlo, podemos agregar un método que llamaremos por ejemplo from_csv(). Lo decoramos con el decorador @classmethod
y usamos la referencia de clase, cls, como primer argumento. Podemos usarlo en la declaración de devolución para hacer que el método devuelva la instancia de la clase en función del contenido del archivo CSV.
En cuanto a los atributos, el umbral de saldo -10000, con el que se compara el saldo al crear la cuenta puede pensarse como
La clase CuentaBancaria modificada quedaría como que hay una regla que establece que no se puede configurar ninguna cuenta con un saldo más bajo. En lugar de codificarlo, deberíamos asignarlo a un atributo en la definición de la clase y acceder más tarde a través del puntero de instancia self
. Estos atributos de clase se definen al comienzo de la clase. Tenga en cuenta que el valor contenido en los atributos de clase son comunes a todas la instancias de clase.
Los demás métodos y atributos ya incluidos arriba pertenece a cada instancia particular y son en consecuencia atributos de instancia de clase, que ya aprendimos a crear antes en esta lección.
La clase CuentaBancaria actualizada con los métodos y atributos de clase propuestos en esta sección es como sigue.
from datetime import datetime
import csv
class CuentaBancaria:
MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
Probamos la clase.
# usando el método de clase
path = 'https://raw.githubusercontent.com/AprendizajeProfundo/Libro_Fundamentos_Programacion/main/Python/Datos/cuenta.csv'
mi_cuenta = CuentaBancaria.from_csv(path)
print(mi_cuenta.cliente, mi_cuenta.numero_cuenta, mi_cuenta.saldo, mi_cuenta.creada_en)
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
/tmp/ipykernel_2685/1779118915.py in <module>
1 # usando el método de clase
2 path = 'https://raw.githubusercontent.com/AprendizajeProfundo/Libro_Fundamentos_Programacion/main/Python/Datos/cuenta.csv'
----> 3 mi_cuenta = CuentaBancaria.from_csv(path)
4 print(mi_cuenta.cliente, mi_cuenta.numero_cuenta, mi_cuenta.saldo, mi_cuenta.creada_en)
/tmp/ipykernel_2685/1014366421.py in from_csv(cls, filepath)
17 @classmethod
18 def from_csv(cls, filepath):
---> 19 with open(filepath, "r") as f:
20 fila = csv.reader(f).__next__()
21 cliente, numero_cuenta = fila
FileNotFoundError: [Errno 2] No such file or directory: 'https://raw.githubusercontent.com/AprendizajeProfundo/Libro_Fundamentos_Programacion/main/Python/Datos/cuenta.csv'
# usando el constructor __init__
print(mi_cuenta.cliente, mi_cuenta.numero_cuenta, mi_cuenta.saldo, mi_cuenta.creada_en)
María 151148 0 2022-08-16
Implementar igualdad de objetos#
La comparación de instancias de clase es una tarea habitual, cuya implementación no es inmediata. Observe el siguiente ejemplo.
cuenta_A = CuentaBancaria("María", 151148)
cuenta_B = CuentaBancaria("María", 151148)
cuenta_A == cuenta_B
False
Las dos cuentas tiene el mismo contenido, pero la comparación es False. Esto sucede porque cuando Python compara en realidad los fragmentos de memoria que ocupan las instancias.
Para implementar la comparación de instancias de clase usamos la función especial __eq__
. Veamos un ejemplo de implementación en nuestra clase CuentaBancaria.
from datetime import datetime
import csv
class CuentaBancaria:
MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
# Probamos de nuevo
cuenta_A = CuentaBancaria("María", 151148)
cuenta_B = CuentaBancaria("María", 151148)
cuenta_A == cuenta_B
True
De igual manera es posible implementar las comparaciones mayor que __gt__
, menor o igual a __le__
, etc. Veamos un ejemplo. Vamos a comparar las cuentas por el saldo.
from datetime import datetime
import csv
class CuentaBancaria:
MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __gt__(self, other):
return True if self.saldo > other.saldo else False
# probamos
cuenta_A = CuentaBancaria("María", 151148, 1000)
cuenta_B = CuentaBancaria("Luis", 151050, 500)
print(cuenta_A == cuenta_B)
print(cuenta_A > cuenta_B)
False
True
Implementar representación en forma de cadenas#
Para tener una representación de las instancia en forma de texto, fácil de revisar por parte del usuario, implemente el método especial __repr__
. Esto permite depurar más fácilmente nuestros objetos, porque es una representación más legible para los humanos. Veamos el ejemplo. Vamos a implementar además el método especial __str__
que ya estudiamos antes para tener una versión de print comprensible para los humanos. Agregamos además una función auxiliar a nuestra clase para manejar las fechas de análisis. Hablaremos más sobre esto más adelante.
from datetime import datetime
import csv
class CuentaBancaria:
MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self.cliente}
Número de cuenta: {self.numero_cuenta}
Fecha de creación: {self.to_dash_date(str(self.creada_en))}
Saldo actual: {self.saldo}
"""
def to_dash_date(self, date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self.cliente}', " \
f"numero_cuenta={self.numero_cuenta}, " \
f"saldo={self.saldo})"
Este es el resultado.
mi_cuenta = CuentaBancaria("María", 151148)
print(mi_cuenta)
repr(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 0
"CuentaBancaria(cliente='María', numero_cuenta=151148, saldo=0)"
Marcar métodos estáticos#
Un método se considera estático cuando no requiere transformar atributos dentro de la instancia o no utiliza métodos de la instancia. En ejemplo anteiror la función utilitaria to_dash_date recibe la fecha de creación solamente para mostrala en un formato mas adecuado para los humanos. Esta método es estático y vamos a marcarlo como tal. Esto ahorra memoria y lo hace más eficiente. Observe que ahora no pasamos el apuntador cls a la método.
from datetime import datetime
import csv
class CuentaBancaria:
MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self.cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self.saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self.cliente}
Número de cuenta: {self.numero_cuenta}
Fecha de creación: {self.to_dash_date(str(self.creada_en))}
Saldo actual: {self.saldo}
"""
@staticmethod
def to_dash_date(date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self.cliente}', " \
f"numero_cuenta={self.numero_cuenta}, " \
f"saldo={self.saldo})"
Este es el resultado. El mismo de antes.
mi_cuenta = CuentaBancaria("María", 151148)
print(mi_cuenta)
repr(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 0
"CuentaBancaria(cliente='María', numero_cuenta=151148, saldo=0)"
Decidir que es interno y que es privado#
Algunos métodos y atributos en cualquier clase están destinados a ser utilizados explícitamente por el usuario del código, como los métodos retiro() y *deposito()*en nuestra clase CuentaBancaria. Algunos, sin embargo, no lo son. El método _dash_date() es una utilidad de ayuda llamada por la clase bajo el capó, pero no está pensada para ser llamada manualmente.
Dichos métodos y atributos se conocen como métodos y atributos internos
y es la mejor práctica comenzar sus nombres con un guión bajo para que tengamos _to_dash_date(), y por ejemplo self._cliente Y self._saldo, si así se decide.
Esta convención de nomenclatura por sí misma no hace nada, pero permite que las personas que miran su código reconozcan de inmediato qué métodos no forman parte de la API pública y, por lo tanto, pueden cambiar inesperadamente en futuras versiones del código.
Un tema relacionado son losmétodos y atributos privados
. Es posible que desee ocultar algunos métodos o atributos del mundo exterior, por ejemplo, para asegurarse de que no se sobrescriban. En Python, no existe un mecanismo para ocultarlos por completo, pero podemos marcarlos como privados nombrándolos con un doble guión bajo inicial. Por ejemplo, podríamos querer que el umbral de saldo mínimo sea privado, por lo que lo llamamos __MIN_SALDO
.
Ahora, aunque es accesible dentro de la clase como de costumbre, la llamada mi_cuencta.__MIN_SALDO
generará una excepción. Esto significa para el usuario que este atributo es privado y no debe ser manipulado.
Con estas consideraciones, nuestra clase toma la forma
from datetime import datetime
import csv
class CuentaBancaria:
__MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self._cliente = cliente
self.numero_cuenta = numero_cuenta
self.creada_en = datetime.now().date()
if saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self.saldo += monto
def retiro(self, monto):
self.saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self._cliente}
Número de cuenta: {self.numero_cuenta}
Fecha de creación: {self._to_dash_date(str(self.creada_en))}
Saldo actual: {self._saldo}
"""
@staticmethod
def _to_dash_date(date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self._cliente}', " \
f"numero_cuenta={self.numero_cuenta}, " \
f"saldo={self._saldo})"
El resultado es el siguiente
mi_cuenta = CuentaBancaria("María", 151148)
print(mi_cuenta)
repr(mi_cuenta)
print(mi_cuenta.__MIN_SALDO)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 0
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-64-a7763f2390a2> in <module>
2 print(mi_cuenta)
3 repr(mi_cuenta)
----> 4 print(mi_cuenta.__MIN_SALDO)
AttributeError: 'CuentaBancaria' object has no attribute '__MIN_SALDO'
Establecer el acceso a los atributos: @property
#
Consideremos el siguiente fragmento de código:
mi_cuenta = CuentaBancaria("María", 151148)
mi_cuenta._saldo = -999_999
mi_cuenta.creada_en = datetime(1900,1,1)
print(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 1900-01-01 00:00:00
Saldo actual: -999999
Como puede ver, es bastante fácil cambiar la fecha de creación en una cuenta existente, así como establecer el saldo en un número negativo grande.
Recuerde que la fecha de creación se establece automáticamente en la creación del objeto, y no podríamos configurar una cuenta con un saldo demasiado negativo gracias a la validación de entrada en el método __init__()
¿Qué podemos hacer al respecto? Idealmente, la fecha de creación debe ser un atributo de solo lectura y el saldo debe tener alguna validación cada vez que se actualiza. Esto se puede lograr en Python usando propiedades y setters.
Usaremos propiedades y configuradores para hacer que los atributos sean de solo lectura y validar sus actualizaciones.
Para hacer, digamos, que el atributo saldo sea de solo lectura, todo lo que se necesitamos hacer es agregar una función a la clase llamada exactamente como este atributo decorado con un decorador @property
y devolviendo el valor del atributo.
Vamos al nuevo código de nuestra clase. Hemos declarado todos los atributos de instancia como internos y creado la propiedad saldo
from datetime import datetime
import csv
class CuentaBancaria:
__MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self._cliente = cliente
self._numero_cuenta = numero_cuenta
self._creada_en = datetime.now().date()
if saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self._saldo += monto
def retiro(self, monto):
self._saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self._cliente}
Número de cuenta: {self._numero_cuenta}
Fecha de creación: {self._to_dash_date(str(self._creada_en))}
Saldo actual: {self._saldo}
"""
@staticmethod
def _to_dash_date(date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self._cliente}', " \
f"numero_cuenta={self._numero_cuenta}, " \
f"saldo={self._saldo})"
@property
def numero_cuenta(self):
return self._numero_cuenta
@property
def creada_en(self):
return self._creada_en
@property
def saldo(self):
return self._saldo
Primero revisemos que nada del comportamiento externo anterior no cambió.
mi_cuenta = CuentaBancaria("María", 151148)
print(mi_cuenta)
repr(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 0
"CuentaBancaria(cliente='María', numero_cuenta=151148, saldo=0)"
Pero ahora no funciona
mi_cuenta.saldo = 100
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-84-006ff73a9363> in <module>
----> 1 mi_cuenta.saldo = 100
AttributeError: can't set attribute
Este funcionamiento estaría bien por ejemplo para número de cuenta y fecha de creación
mi_cuenta.numero_cuenta
151148
from datetime import datetime
import csv
class CuentaBancaria:
__MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
self._cliente = cliente
self._numero_cuenta = numero_cuenta
self._creada_en = datetime.now().date()
if saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self._saldo += monto
def retiro(self, monto):
self._saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self._cliente}
Número de cuenta: {self._numero_cuenta}
Fecha de creación: {self._to_dash_date(str(self._creada_en))}
Saldo actual: {self._saldo}
"""
@staticmethod
def _to_dash_date(date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self._cliente}', " \
f"numero_cuenta={self._numero_cuenta}, " \
f"saldo={self._saldo})"
@property
def numero_cuenta(self):
return self._numero_cuenta
@property
def creada_en(self):
return self._creada_en
@property
def saldo(self):
return self._saldo
@saldo.setter
def saldo(self, nuevo_saldo):
if nuevo_saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = nuevo_saldo
mi_cuenta = CuentaBancaria("María", 151148)
print(mi_cuenta)
repr(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 0
"CuentaBancaria(cliente='María', numero_cuenta=151148, saldo=0)"
mi_cuenta.saldo = 500
print(mi_cuenta)
Cuenta Bancaria:
Propietario de la cuenta: María
Número de cuenta: 151148
Fecha de creación: 2022-08-16
Saldo actual: 500
mi_cuenta.saldo = -15_000
print(mi_cuenta)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-91-f82c62de04c0> in <module>
----> 1 mi_cuenta.saldo = -15_000
2 print(mi_cuenta)
<ipython-input-88-cce4e77522d3> in saldo(self, nuevo_saldo)
64 def saldo(self, nuevo_saldo):
65 if nuevo_saldo < self.__MIN_SALDO:
---> 66 raise ValueError("¡Saldo muy pequeño!")
67 else:
68 self._saldo = nuevo_saldo
ValueError: ¡Saldo muy pequeño!
Documentar con cadenas de texto clases, métodos, atributos públicos#
Escriba cadenas de documentación para todos los módulos, funciones, clases y métodos públicos. Las cadenas de documentos no son necesarias para los métodos no públicos, pero debe tener un comentario que describa lo que hace el método. Este comentario debe aparecer después de la línea “def”. Esta documentación aparece al pedir ayuda (help) de los objetos.
Nuestra versión final de la clase CuentaBancaria es
from datetime import datetime
import csv
class CuentaBancaria:
"""
Clase CuentaBancaria:
Esta clase implementa la creación y moviemientos de una cuencta bancaria.
Atributos públicos:
"""
__MIN_SALDO = -10_000
def __init__(self, cliente, numero_cuenta, saldo=0):
"""
Este es el constructor por defecto de la clase CuentaBancaria
"""
self._cliente = cliente
self._numero_cuenta = numero_cuenta
self._creada_en = datetime.now().date()
if saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = saldo
@classmethod
def from_csv(cls, filepath):
with open(filepath, "r") as f:
fila = csv.reader(f).__next__()
cliente, numero_cuenta = fila
return cls(cliente, numero_cuenta)
def deposito(self, monto):
self._saldo += monto
def retiro(self, monto):
self._saldo -= monto
def __eq__(self, other):
return True if self.numero_cuenta == other.numero_cuenta else False
def __str__(self):
return f"""
Cuenta Bancaria:
Propietario de la cuenta: {self._cliente}
Número de cuenta: {self._numero_cuenta}
Fecha de creación: {self._to_dash_date(str(self._creada_en))}
Saldo actual: {self._saldo}
"""
@staticmethod
def _to_dash_date(date):
return date.replace("/", "-")
def __repr__(self):
return f"CuentaBancaria(cliente='{self._cliente}', " \
f"numero_cuenta={self._numero_cuenta}, " \
f"saldo={self._saldo})"
@property
def numero_cuenta(self):
return self._numero_cuenta
@property
def creada_en(self):
return self._creada_en
@property
def saldo(self):
return self._saldo
@saldo.setter
def saldo(self, nuevo_saldo):
if nuevo_saldo < self.__MIN_SALDO:
raise ValueError("¡Saldo muy pequeño!")
else:
self._saldo = nuevo_saldo
help(CuentaBancaria)
Help on class CuentaBancaria in module __main__:
class CuentaBancaria(builtins.object)
| CuentaBancaria(cliente, numero_cuenta, saldo=0)
|
| Clase CuentaBancaria:
| Esta clase implementa la creación y moviemientos de una cuencta bancaria.
| Atributos públicos:
|
| Methods defined here:
|
| __eq__(self, other)
| Return self==value.
|
| __init__(self, cliente, numero_cuenta, saldo=0)
| Este es el constructor por defecto de la clase CuentaBancaria
|
| __repr__(self)
| Return repr(self).
|
| __str__(self)
| Return str(self).
|
| deposito(self, monto)
|
| retiro(self, monto)
|
| ----------------------------------------------------------------------
| Class methods defined here:
|
| from_csv(filepath) from builtins.type
|
| ----------------------------------------------------------------------
| Readonly properties defined here:
|
| creada_en
|
| numero_cuenta
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| saldo
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __hash__ = None
Ejercicios#
Implemente una clase Persona que contenga la información general de una persona
Diseñe una clase Estudiante que implemente el manejo de las notas de clase de un estudiante. Derive la clase de Persona. Suponga que el estudiante debe atender a tres cursos: Matemáticas, Español y Biología.
Implemente el ingreso de notas: hasta 3 por curso.
Implemente el cálculo de la nota definitiva, como un promedio de las notas.
Use todos los patrones definidos en esta lección.