Beruflich Dokumente
Kultur Dokumente
Introducción
Con todo lo que conocemos hasta ahora ya podemos crear bastantes buenos
programas, sin embargo, no dejan de ser instrucciones estructuradas, eso significa
que cuando queremos solucionar un problema tenemos que pensar de arriba
abajo y lo único que nos da un poco de juego son las funciones, las listas y los
diccionarios. Así era la programación en el pasado bastante aburrida, con
muchísimo código y mucha gestión de recursos y difícil de mantener, hasta que
poco a poco fue tomando fama un paradigma de programación conocido como
programación orientada a objetos, este paradigma o modelos de solución de
problemas resulto muy útil para satisfacer las necesidades de un mundo cada vez
más tecnificado, en el que más y más sectores se estaban informatizando y era
necesaria una forma más fácil y sencilla de trasladar los problemas del mundo real
a la programación, la mejora fue muy importante, ya que las tediosas estructuras
no solo se podían replicar fácilmente en clase y objetos mucho mejor ordenados,
si no que estos además permiten manejar sus propios atributos y funciones
internas llamadas métodos. En esta tercera fase del curso vamos a meternos de
lleno en el mundo de los objetos, definiendo y manejando nuestras propias clases,
descubriendo la herencia y repasando algunos de los métodos nativos de las
colecciones, haciéndolas cada vez aún más potentes. Bienvenidos a la
programación orientada a objetos.
Primer contacto con la POO
Lo único que tenéis que hacer es probar ambos códigos (sin necesidad de
analizarlos), luego sacad vuestras propias conclusiones sobre cuál os parece más
útil e intuitivo de aplicar y extender en el mundo real.
Ejemplo estructurado
# Definimos unos cuantos clientes
clientes= [
{'Nombre': 'Hector', 'Apellidos’: ‘Costa Guzman',
'dni':'11111111A'},
{'Nombre': 'Juan', 'Apellidos’: ‘González Márquez',
'dni':'22222222B'}
]
print('Cliente no encontrado')
print("==LISTADO DE CLIENTES==")
print(clientes)
print("\n==LISTADO DE CLIENTES==")
print(clientes)
Ejemplo orientado a objetos
### No intentes entender este código, sólo fíjate en cómo se
utiliza abajo
def __str__(self):
return '{} {}'.format(self.nombre,self.apellidos)
¿No os parece que el código orientado a objetos es más auto explicativo a la hora
de utilizarlo? Además, con programación estructurada tenemos que enviar la lista
que queremos consultar todo el rato, mientras que con la POO tenemos esas
"estructuras" como la empresa que contienen los clientes, todo queda como más
ordenado.
Clases y objetos
Podéis imaginaros los objetos como un nuevo tipo de dato cuya definición viene
dada en una estructura llamada clase.
Suelo hacer una metáfora y comparar las clases con moldes de galletas y los
objetos con las galletas en sí mismas. Si bien todas las galletas que se hacen con
el mismo molde tienen la misma forma, cada una adquiere atributos individuales
después del horneado. Cosas como el color, la textura, el sabor... pueden llegar a
ser muy distintas.
Extrapolando el ejemplo, una clase es sólo un guión sobre como deben ser los
objetos que se crearán con ella.
La función type()
Ya he comentado varias veces que en Python todo son clases y objetos, eso se
puede comprobar fácilmente pasando a la función type() cualquier variable o
literal:
numero = 10
type(numero)
def hola():
pass
type(hola)
Con eso en mente veamos cómo crear nuestras propias clases.
Definición de clase
Esta es una definición muy simple de lo que es una galleta, ya que con el pass la
dejo vacía. Luego añadiremos más información, por ahora veamos cómo crear
galletas con este molde.
Instancias de clase
Para entender bien los objetos debemos tener claras dos cuestiones
fundamentales:
Puede parecer trivial, pero es importante tener claro que los objetos "existen" sólo
durante la ejecución del programa y se almacenan en la memoria del sistema
operativo.
una_galleta = Galleta()
otra_galleta = Galleta()
Demostrar que las galletas existen como "entes independientes" dentro de la
memoria, es tan sencillo como imprimirlas por pantalla:
print(una_galleta)
print(otra_galleta)
print(Galleta)
print(Galleta)
print(type(una_galleta))
print(una_galleta.__class__)
print(Galleta.__name__)
print(type(una_galleta).__name__)
print(una_galleta.__class__.__name__)
Atributos
A efectos prácticos los atributos no son muy distintos de las variables, la diferencia
fundamental es que sólo existen dentro del objeto.
Atributos dinámicos
Dado que Python es muy flexible los atributos pueden manejarse de distintas
formas, por ejemplo, se pueden crear dinámicamente (al vuelo) en los objetos.
class Galleta:
pass
galleta = Galleta()
galleta.sabor = "salado"
galleta.color = "marrón"
Atributos de clase
Aunque la flexibilidad de los atributos dinámicos puede llegar a ser muy útil, tener
que definir los atributos de esa forma es tedioso. Es más práctico definir unos
atributos básicos en la clase. De esa manera todas las galletas podrían tener unos
atributos por defecto:
class Galleta:
chocolate = False
galleta = Galleta()
if galleta.chocolate:
print("La galleta tiene chocolate")
else:
print("La galleta no tiene chocolate")
galleta.chocolate = True
if galleta.chocolate:
print("La galleta tiene chocolate")
else:
print("La galleta no tiene chocolate")
Por lo menos de esta forma nos aseguraremos de que el atributo chocolate existe
en todas las galletas desde el principio. Además, es posible consultar el valor por
defecto que deben tener las galletas haciendo referencia al atributo en la definición
de la clase:
print(Galleta.chocolate)
Galleta.chocolate = True
galleta = Galleta()
if galleta.chocolate:
print("La galleta tiene chocolate")
else:
print("La galleta no tiene chocolate")
Métodos
Si por un lado tenemos las "variables" de las clases, por otro tenemos sus
"funciones", que evidentemente nos permiten definir funcionalidades para
llamarlas desde las instancias.
class Galleta:
chocolate = False
def saludar():
print("Hola, soy una galleta muy sabrosa")
galleta = Galleta()
galleta.saludar()
Sin embargo, al intentar ejecutar el código anterior desde una galleta veréis que
no funciona. Nos indica que el método saludar() requiere 0 argumentos pero se
está pasando uno.
class Galleta:
chocolate = False
def saludar():
print("Hola, soy una galleta muy sabrosa")
Galleta.saludar()
Los objetos tienen una característica muy importante: son conscientes de que
existen. Y no, no es broma.
Cuando se ejecuta un método desde un objeto (que no desde una clase), se envía
un primer argumento implícito que hace referencia al propio objeto. Si lo definimos
en nuestro método podremos capturarlo y ver qué es:
class Galleta:
chocolate = False
def saludar(soy_el_propio_objeto):
print("Hola, soy una galleta muy sabrosa")
print(soy_el_propio_objeto)
galleta = Galleta()
galleta.saludar()
¿Curioso que haya funcionado verdad? Además ¿no os suena de algo ese
resultado que muestra el parámetro que hemos definido? Se trata de la propia
representación del objeto.
class Galleta:
chocolate = False
def saludar(soy_el_propio_objeto):
print("Hola, soy una galleta muy sabrosa")
print(soy_el_propio_objeto)
galleta = Galleta()
galleta.saludar()
print(galleta)
Pues sí, podemos acceder al propio objeto desde el interior de sus métodos. Lo
único que como este argumento hace referencia al objeto en sí mismo por
convención se le llama self.
Poder acceder al propio objeto desde un método es muy útil, ya que nos permite
acceder a sus atributos. Fijaros, el siguiente código no funcionaría como
esperamos:
class Galleta:
chocolate = False
def chocolatear(self):
chocolate = True
galleta = Galleta()
galleta.chocolatear()
print(galleta.chocolate)
class Galleta:
chocolate = False
def chocolatear(self):
self.chocolate = True
galleta = Galleta()
galleta.chocolatear()
print(galleta.chocolate)
Métodos especiales
Ahora que sabemos crear métodos y hemos aprendido para qué sirve el
argumento self, es momento de introducir algunos métodos especiales de las
clases.
Constructor
class Galleta:
def __init__(self):
print("Soy una galleta acabada de hornear!")
galleta = Galleta()
La finalidad del constructor es, como su nombre indica, construir los objetos. Por
esa razón permite sobreescribir el método que crea los objetos, permitiéndonos
enviar datos desde el principio para construirlo:
class Galleta:
chocolate = False
Destructor
class Galleta:
def __del__(self):
print("La galleta se está borrando de la memoria")
galleta = Galleta()
del(galleta)
def __del__(self):
print("La galleta se está borrando de la memoria")
galleta = Galleta()
galleta.__del__()
String
class Galleta:
def __str__(self):
return f"Soy una galleta {self.color} y {self.sabor}."
galleta = Galleta("dulce", "blanca")
print(galleta)
print(str(galleta))
print(galleta.__str__())
Hay que tener en cuenta que este método debe devolver la cadena
en lugar de mostrar algo por pantalla, ese es el funcionamiento
que se espera de él.
Length
class Cancion:
def __len__(self):
return self.duracion
Hasta ahora no lo hemos comentado, pero al ser las clases un nuevo tipo de dato
resulta más que obvio que se pueden poner en colecciones e incluso utilizarlos
dentro de otras clases.
class Pelicula:
# Constructor de clase
def __init__(self, titulo, duracion, lanzamiento):
self.titulo = titulo
self.duracion = duracion
self.lanzamiento = lanzamiento
print('Se ha creado la película:', self.titulo)
def __str__(self):
return '{} ({})'.format(self.titulo,
self.lanzamiento)
class Catalogo:
def mostrar(self):
for p in self.peliculas:
print(p) # Print toma por defecto str(p)
class Ejemplo:
__atributo_privado = "Soy un atributo inalcanzable desde
fuera."
e = Ejemplo()
print(e.__atributo_privado)
Y en los métodos...
class Ejemplo:
def __metodo_privado(self):
print("Soy un método inalcanzable desde fuera.")
e = Ejemplo()
e.__metodo_privado()
¿Qué sentido tiene esto en Python? Ninguno, porque se pierde toda la gracia de lo
que en esencia es el lenguaje: flexibilidad y polimorfismo sin control (hablaremos
de esto más adelante).
Sea como sea para acceder a esos datos se deberían crear métodos públicos que
hagan de interfaz. En otros lenguajes les llamaríamos getters y setters y es lo que
da lugar a las propiedades, que no son más que atributos protegidos con
interfaces de acceso:
class Ejemplo:
__atributo_privado = "Soy un atributo inalcanzable desde
fuera."
def __metodo_privado(self):
print("Soy un método inalcanzable desde fuera.")
def atributo_publico(self):
return self.__atributo_privado
def metodo_publico(self):
return self.__metodo_privado()
e = Ejemplo()
print(e.atributo_publico())
e.metodo_publico()
Teoría previa
Nota
El plano cartesiano
Vectores en el plano
A(x1, y1) => A(2, 3)
B(x2, y2) => B(5, 5)
Ejercicio
Nota:
La función raíz cuadrada en Python sqrt() se debe importar del módulo math y utilizarla de la
siguiente forma:
import math
math.sqrt(9)
Puedes identificar fácilmente estos valores si intentas dibujar el cuadrado a partir de su diagonal. Si
andas perdido, prueba de dibujarlo en un papel, ¡seguro que lo verás mucho más claro! Además
recuerda que puedes utilizar la función abs() para saber el valor absolute de un número.
Experimentación
Crea los puntos A(2, 3), B(5,5), C(-3, -1) y D(0,0) e imprimelos por pantalla.
(Optativo) Consulta la distancia entre los puntos 'A y B' y 'B y A'.
import math
class Punto:
def __str__(self):
return "({}, {})".format(self.x, self.y)
def cuadrante(self):
if self.x > 0 and self.y > 0:
print("{} pertenece al primer
cuadrante".format(self))
elif self.x < 0 and self.y > 0:
print("{} pertenece al segundo
cuadrante".format(self))
elif self.x < 0 and self.y < 0:
print("{} pertenece al tercer
cuadrante".format(self))
elif self.x > 0 and self.y < 0:
print("{} pertenece al cuarto
cuadrante".format(self))
elif self.x != 0 and self.y == 0:
print("{} se sitúa sobre el eje X".format(self))
elif self.x == 0 and self.y != 0:
print("{} se sitúa sobre el eje Y".format(self))
else:
print("{} se encuentra sobre el
origen".format(self))
class Rectangulo:
def base(self):
print("La base del rectángulo es {}".format(
self.vBase ) )
def altura(self):
print("La altura del rectángulo es {}".format(
self.vAltura ) )
def area(self):
print("El área del rectángulo es {}".format(
self.vArea ) )
A = Punto(2,3)
B = Punto(5,5)
C = Punto(-3, -1)
D = Punto(0,0)
A.cuadrante()
C.cuadrante()
D.cuadrante()
A.vector(B)
B.vector(A)
A.distancia(B)
B.distancia(A)
A.distancia(D)
B.distancia(D)
C.distancia(D)
R = Rectangulo(A, B)
R.base()
R.altura()
R.area()