Fundamentos de
Programación en Python:
Módulo 6
Los conceptos básicos del enfoque orientado a objetos
El enfoque orientado a objetos es
particularmente útil cuando se aplica a
proyectos grandes y complejos llevados a
cabo por grandes equipos formados por
muchos desarrolladores.
Este tipo de programación en un proyecto
facilita muchas tareas importantes, por
ejemplo, dividir el proyecto en partes
pequeñas e independientes y el desarrollo
independiente de diferentes elementos del
proyecto.
Python es una herramienta universal para
la programación procedimental y orientada a objetos.
Enfoque procedimental versus el enfoque orientado a
objetos
En el enfoque procedimental tenemos dos mundos separados: el de los
datos y el del código.
En el mundo de los datos se establecen las variables de diferentes tipos, y
en el mundo codificado se establecen las funciones.
¿Hay algún vinculo entre estos dos mundos? Por supuesto que sí. Las
funciones procesan los datos (información) pero no a la inversa.
¿Pero es esto completamente cierto?
¿Hay algunos tipos especiales de datos que pueden usar funciones? Sí.
Los métodos son funciones que se invocan desde dentro de los datos.
Aquí da inicio el enfoque de programación orientada a objetos.
Los datos y el código viven en un solo mundo (Encapsulados). Cada
mundo es una clase. Haciendo analogía a la cocina. Cada clase es una
receta que es capaz de producir platillos, es decir, objetos.
Cada objeto tiene propiedades o atributos.
Cada receta puede modificarse y
crear nuevas recetas (nuevas
clases) las nuevas recetas
pueden heredar propiedades y
métodos de la receta original.
Los objetos son
encarnaciones de las ideas
expresadas en clases, como un
pastel de queso en tu plato, es
una encarnación de la idea
expresada en una receta impresa
en un viejo libro de cocina.
Los datos mantienen
comunicación entre sí, intercambiando datos o activando sus métodos.
No existe un límite claro entre los datos y el código: viven como uno solo
dentro de los objetos.
Jerarquías de clase
La clase que nos concierne es como una categoría, como resultado de
similitudes definidas con precisión.
Intentaremos señalar algunas clases que son buenos ejemplos de este
concepto.
Todos los vehículos existentes (y los que aún no existen) están
relacionados por una sola característica importante: la capacidad de
moverse. Tenemos que mejorar la definición.
Consideremos las siguientes circunstancias: los vehículos son entidades
creadas artificialmente que se utilizan para el transporte, movidos por
fuerzas de la naturaleza y dirigidos (conducidos) por humanos.
La clase vehículos es muy amplia. Tenemos que definir clases
especializadas. Las clases especializadas son las subclases. La clase
vehículos será una superclase para todas ellas.
A estas alturas, probablemente puedas señalar algunas subclases
potenciales para la superclase Vehículos. Hay muchas clasificaciones
posibles. Elegimos subclases basadas en el medio ambiente y decimos
que hay (al menos) cuatro subclases:
Vehículos Terrestres.
Vehículos Acuáticos.
Vehículos Aéreos.
Vehículos Espaciales.
Los vehículos terrestres pueden dividirse aún más, según el método con el
que impactan el suelo. Entonces, podemos enumerar:
1. Vehículos de ruedas.
2. Vehículos oruga.
3. Aerodeslizadores.
Jerarquías de clase:
continuación
Otro ejemplo es la jerarquía del reino taxonómico de los animales.
Podemos decir que todos los animales (nuestra clase de nivel superior) se
puede dividir en cinco subclases:
1. Mamíferos.
2. Reptiles.
3. Pájaros.
4. Peces.
5. Anfibios.
Hemos identificado las siguientes subclases para la subclase mamíferos.
1. Mamíferos salvajes.
2. Mamíferos domesticados.
¿Qué es un objeto?
Una clase (entre otras definiciones) es un conjunto de objetos. Un objeto
es un ser perteneciente a una clase.
Un objeto es una encarnación de los requisitos, rasgos y cualidades
asignados a una clase específica.
Las clases forman una jerarquía. Esto puede significar que un objeto que
pertenece a una clase específica pertenece a todas las superclases al
mismo tiempo.
Cada subclase es más especializada (o más específica) que su
superclase. Por el contrario, cada superclase es más general (más
abstracta) que cualquiera de sus subclases.
¿Qué contiene un objeto?
La programación orientada a objetos supone que cada objeto existente
puede estar equipado con tres grupos de atributos:
1. Un objeto tiene un nombre que lo identifica. (Sustantivo)
2. Un objeto tiene un conjunto de propiedades individuales que lo hacen
original. (Adjetivo)
3. Un objeto tiene un conjunto de habilidades para realizar actividades
específicas, capaz de cambiar el objeto en sí, o algunos de los otros
objetos. (Verbo)
Ejemplos:
Max es un gato grande que duerme todo el día.
Nombre del objeto = Max
Clase de inicio = Gato
Propiedad = Tamaño (grande)
Actividad = Dormir (todo el día)
Un Cadillac rosa pasó rápidamente.
Nombre del objeto = Cadillac
Clase de inicio = Vehículo terrestre
Propiedad = Color (rosa)
Actividad = Pasar (rápidamente)
Una ventana de una aplicación de agenda telefónica contiene un
menú de opciones.
Nombre del objeto = menú
Clase de inicio = ventana
Actividad = marcar número de teléfono
Herencia
Cualquier objeto vinculado a un nivel específico de una jerarquía de clases
hereda todos los rasgos (así como los requisitos y cualidades)
definidos dentro de cualquiera de las superclases.
Tu primera clase
La programación orientada a objetos es el arte de definir y expandir
clases.
La clase inicial será la superclase, de tal clase las subclases heredarán
sus propiedades y acciones así también podrán incluir propiedades y
acciones nuevas, propias de cada superclase.
El hecho de haber creado una super clase, no significa que un objeto se
creara automáticamente. Para ello, el programador tiene que crearlo con el
lenguaje en que se este laborando, en este caso Python.
Creación de una clase simple
class ClaseSimple:
pass
Creación de un objeto simple
Imagina que deseas crear un objeto (exactamente uno) de la clase
ClaseSimple.
Para hacer esto, debes asignar una variable para almacenar el objeto
recién creado de esa clase y crear un objeto al mismo tiempo.
Se hace de la siguiente manera:
miPrimerObjeto =
ClaseSimple()
Nota:
El nombre de la clase intenta fingir que es una función, ¿puedes ver
esto? Lo discutiremos pronto.
El objeto recién creado está equipado con todo lo que trae la clase;
Como esta clase está completamente vacía, el objeto también está
vacío.
El acto de crear un objeto de la clase seleccionada también se llama
instanciación (ya que el objeto se convierte en una instancia de la
clase).
¿Qué es una pila?
Una pila es una estructura desarrollada
para almacenar datos de una manera
muy específica.
El nombre alternativo para una pila (pero
solo en la terminología de TI) es UEPS
(LIFO son sus siglas en íngles). LIFO =
Last In – Firts Out.
Operaciones elementales: push (cuando
un nuevo elemento se coloca en la parte
superior) y pop (cuando un elemento
existente se retira de la parte superior).
La pila: el enfoque
pila =
procedimental
Utilización de una lista para almacenar
los valores de una pila.
def push(val): Función que inserta los valores de
pila.append(va manera que el ultimo que entra es el
primero que sale.
l)
def pop():
val = pila[- Función que elimina el ultimo valor
agregado a la pila y a su vez lo retorna.
1]
del pila[-1]
return val
La pila: el enfoque
procedimental frente al
enfoque orientado a objetos
La pila creada anteriormente es funciona, pero no tan optima como
pensamos, a medida que la utilices la pila tendrá varias desventajas, por
ejemplo:
1. La variable “pila []” es altamente vulnerable, es decir, se puede
modificar fácilmente ya que se puede llegar a confundir el nombre de
la variable, y fácilmente alguien podría hacer lo siguiente:
pila[0] = 9;
2. ¿Qué pasaría si requieres implementar varias pilas? Tendríamos que
crear otra pila, y probablemente otras funciones “push” y “pop”.
En conclusión habría mucho código redundante.
El enfoque orientado a objetos ofrece varias soluciones para los
problemas mencionados anteriormente.
1. Protección de los valores seleccionados contra el acceso no
autorizado, a esto se le conoce como “encapsulamiento”.
2. Obtención de una receta que replica los ingredientes esenciales para
cocinar una pila, es decir, tener una clase que implementa todos los
atributos y comportamientos necesarios de una pila. (Así nos
evitamos líneas de código iguales).
3. Creación de una subclase que herede los comportamientos y
atributos de la superclase y agregue unos nuevos.
La pila - el enfoque orientado
a objetos
1. Así es como comienza la pila en el enfoque orientado a objetos.
class Pila:
2. Ahora, esperemos dos cosas de la clase:
a. La clase tenga una propiedad que sea el almacenamiento de la
pila, es decir, cada objeto tendrá su propia lista.
b. La lista debe estar oculta de la vista de los usuarios de la clase.
¿Cómo se hace esto?
Debes agregar una instrucción específica. Las propiedades deben
agregarse a la clase manualmente.
¿Cómo garantizar que dicha actividad tiene lugar cada vez que se crea
una nueva pila?
Hay una manera simple de hacerlo - tienes que equipar a la clase con una
función específica:
1. Tiene que ser nombrada de forma estricta.
2. Se invoca implícitamente cuando se crea el nuevo objeto.
Tal función es llamada el constructor, ya que su propósito general es
construir un nuevo objeto.
El constructor sabe todo acerca del objeto y debe tener las inicializaciones
necesarias.
Ejemplo:
class Pila:
El nombre del constructor es siempre __init__
def __init__(self):
print("¡Hola!")
El parámetro se usa para representar el objeto recién
creado: puedes usar el parámetro para manipular el
objetoPila = Pila() objeto y enriquecerlo con las propiedades necesarias;
Parámetro obligatorio: self.
La salida del programa es: “¡Hola!”, el constructor ha sido invocado de
manera implícita y automáticamente.
class Pila: Modificación del estado del parámetro self (esta
def __init__(self): modificación se vera reflejada en el objeto recién creado).
self.listaPila = []
Se agrego una propiedad a “self” y esta permanecerá allí
hasta que termine la vida del objeto o la propiedad se elimine.
objetoPila = Pila()
print(len(objetoPila.listaPil
a))
Nota:
Notación punto -> forma general de acceder a las propiedades de un
objeto. (self.listaPila = [])
Establecimiento de una propiedad por primera vez a un objeto -> lo estas
creado; a partir de ese momento, el objeto tiene la propiedad.
Se accedió a la propiedad “listaPila” desde fuera de la clase, ¿se logró? Sí.
Esto no es lo que queremos de la pila. Nosotros queremos que listaPila
este escondida del mundo exterior. ¿Es eso posible?
Sí, y es simple, pero no muy intuitivo.
class Pila:
Se agregaron dos guiones bajos
def __init__(self): antes del nombre listaPila
self.__listaPila = []
objetoPila = Pila()
print(len(objetoPila.__listaPila
))
El cambio invalida el programa..
¿Por qué?
Cuando cualquier componente de la clase tiene un nombre que comienza
con dos guiones bajos (__), se vuelve privado - esto significa que solo se
puede acceder desde la clase.
Si se intenta acceder fuera de la clase, una excepción AttributeError
debe ser lanzada.
Pila en el enfoque orientado a objetos.
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
Las funciones parecen familiares, pero tienen más parámetros que sus
contrapartes procedimentales.
Ambas funciones tienen un parámetro llamado self en la primera posición
de la lista de parámetros. ¿Es necesario? Si, lo es.
Permite que el método acceda a entidades (propiedades y actividades
/ métodos) del objeto.
Python envía implícitamente el objeto como el primer argumento, es decir,
a self.
Esto significa que el método está obligado a tener al menos un
parámetro, que Python mismo utiliza - no tienes ninguna influencia
sobre él.
Si el método no ocupa ninguno parámetro aún así debes especificar “self”.
Ahora puedes hacer que más de una pila se comporte de la misma
manera. Cada pila tendrá su propia copia de datos privados, pero utilizará
el mismo conjunto de métodos.
objetoPila1 = Pila()
objetoPila2 = Pila()
objetoPila1.push(3)
objetoPila2.push(objetoPila1.pop())
print(objetoPila2.pop())
Existen dos pilas creadas a partir de la misma clase base. Trabajan
independientemente.
Ejemplo de la creación de 3 pilas independientes.
pequeñaPila = Pila()
otraPila = Pila()
graciosaPila = Pila()
pequeñaPila.push(1)
otraPila.push(pequeñaPila.pop() + 1)
graciosaPila.push(otraPila.pop() - 2)
print(graciosaPila.pop())
Vamos a agregar una nueva clase para manejar pilas.
La nueva clase debería poder evaluar la suma de todos los elementos
almacenados actualmente en la pila.
El primer paso es fácil: solo define una nueva subclase que apunte a la
clase que se usará como superclase.
class SumarPila(Pila):
Esto es lo que queremos de la nueva pila:
1. Queremos que el método push no solo inserte el valor en la pila, sino
que también sume el valor a la variable sum.
2. Queremos que la función pop no solo extraiga el valor de la pila, sino
que también reste el valor de la variable sum.
En primer lugar, agreguemos una nueva variable a la clase. Sera una
variable privada, al igual que la lista de pila. No queremos que nadie
manipule el valor de la variable sum.
self.__sum = 0
Python te obliga a invocar explícitamente el constructor de una
superclase. Omitir este punto tendrá efectos nocivos: el objeto se verá
privado de la lista __listaPila. Tal pila no funcionará correctamente.
Pila.__init__(self)
Ten en cuenta la sintaxis:
Se especifica el nombre de la superclase (esta es la clase cuyo
constructor se desea ejecutar).
Se pone un punto (.) después del nombre.
Se especifica el nombre del constructor.
Se debe señalar al objeto (la instancia de la clase) que debe ser
inicializado por el constructor; es por eso que se debe especificar el
argumento y utilizar la variable self aquí; recuerda: invocar
cualquier método (incluidos los constructores) desde fuera de la
clase nunca requiere colocar el argumento self en la lista de
argumentos
Nota: generalmente es una práctica recomendada invocar al constructor
de la superclase antes de cualquier otra inicialización que desees
realizar dentro de la subclase. Esta es la regla que hemos seguido en el
código.
Vamos a cambiar la funcionalidad de los métodos, no sus nombres.
Podemos decir con mayor precisión que la interfaz (la forma en que se
manejan los objetos) de la clase permanece igual al cambiar la
implementación al mismo tiempo.
Comencemos con la implementación de la función push. Esto es lo que
esperamos de la función:
Agregar el valor a la variable __sum.
Agregar el valor a la pila.
def push(self, val):
self.__sum += val
Pila.push(self, val)
Toma en cuenta la forma en que hemos invocado la implementación
anterior del método push (el disponible en la superclase):
Tenemos que especificar el nombre de la superclase; esto es
necesario para indicar claramente la clase que contiene el método,
para evitar confundirlo con cualquier otra función del mismo nombre.
Tenemos que especificar el objeto de destino y pasarlo como primer
argumento (no se agrega implícitamente a la invocación en este
contexto).
Se dice que el método push ha sido anulado - el mismo nombre que en la
superclase ahora representa una funcionalidad diferente.
Esta es la nueva función pop:
def pop(self):
val = Pila.pop(self)
self.__sum -= val
¿Cómoreturn
podemosvalmostrar el valor de __sum y que al mismo tiempo se
proteja de modificaciones?
Tenemos que definir un nuevo método. Lo nombraremos getSuma. Su
única tarea será devolver el valor de __sum.
def getSuma(self):
return self.__sum
Variables de instancia
En general, una clase puede equiparse con dos tipos diferentes de datos
para formar las propiedades de una clase.
Este tipo de propiedad existe solo cuando se crea explícitamente y se
agrega a un objeto. Como ya sabes, esto se puede hacer durante la
inicialización del objeto, realizada por el constructor.
Agregar una propiedad a un objeto se puede hacer en cualquier momento
de la vida del objeto. Y asi mismo eliminarla en cualquier punto.
Tal enfoque tiene algunas consecuencias importantes:
Diferentes objetos de la misma clase pueden poseer diferentes
conjuntos de propiedades.
Debe haber una manera de verificar con seguridad si un objeto
específico posee la propiedad que deseas utilizar (a menos que
quieras provocar una excepción, siempre vale la pena considerarlo).
Cada objeto lleva su propio conjunto de propiedades - no
interfieren entre sí de ninguna manera.