Tema1 1 POO Clases Python
Tema1 1 POO Clases Python
Datos
Tema 1_1: Programación Orientada
a Objetos.
Por ejemplo, con los conocimientos print("De", p0, "a", p1, ":", calcula_di
stancia(p0,p1))
adquiridos hasta ahora, si quisiéramos
desarrollar un módulo de soporte para el
manejo de puntos de coordenadas Este código, que utiliza tuplas para
cartesianas en dos dimensiones, modelar puntos, supone en realidad
3
varios problemas.
Programación Orientada a Objetos
Otro Ejemplo: Tipos de datos «a medida»
5
Programación Orientada a Objetos
Una variable cuyo contenido fuera de tipo Básicamente, estas entidades se componen
«cliente» albergaría en un solo paquete de datos y funciones, agrupadas en un
toda la información propia de un cliente: su concepto encapsulador llamado clase.
nombre, su DNI y su edad.
POO en Python: Definición de clases
Pero también pediríamos el tipo coche, el
Python es un lenguaje de POO. Una de
tipo empleado, el tipo profesor, etc.
las características más poderosas en estos
Python nos permite definir nuevos tipos de
lenguajes es la capacidad de perm itir a un
datos combinando tipos existentes.
program ador crear nuevas clases que
Podemos definir un tipo de datos nuevo, m odelen los datos necesarios para
digamos Cliente, que agrupe en un solo resolver el problem a.
paquete los datos básicos que lo forman.
Podemos imaginar los objetos como un
Los nuevos tipos de datos recibirán el
nuevo tipo de dato cuya definición viene
nombre genérico de clases.
dada en una estructura llamada clase.
La programación orientada a objetos
Usamos TAD’s para proporcionar la
se basa en simular la actuación de las
descripción lógica de cómo se ve un objeto
distintas entidades que toman parte en la
de datos (su estado) y qué puede hacer
automatización de un problema.
(sus métodos). 6
Programación Orientada a Objetos
Mediante la construcción de una clase que para realizarlo es tan fácil como llamar a la
implementa un TAD, un programador puede clase como si fuera una función.
aprovechar el proceso de abstracción y al
Cada instancia tiene su propia referencia
mismo tiempo proporcionar los detalles
(ya que están en lugares distintos de la
necesarios para utilizar realmente la
memoria). En cambio, la clase no tiene una
abstracción en un programa.
referencia porque es sólo un guion de
Atributos y métodos instrucciones
Los objetos "existen" sólo durante la Si hay algo que ilustre el potencial de la
ejecución del programa y se almacenan en POO esa es la capacidad de definir variables
la memoria del sistema operativo. y funciones dentro de las clases, aunque
aquí se conocen como atributos y
Es decir, mientras las clases están ahí en el
métodos respectivamente.
código haciendo su papel de instrucciones,
los objetos no existen hasta que el Si por un lado tenemos las "variables" de las
programa se ejecuta y se crean en la clases (atributos), por otro tenemos sus
memoria. "funciones“ (métodos), que evidentemente
nos permiten definir funcionalidades para
Este proceso de "crear" los objetos en la
llamarlas desde las instancias.
memoria se denomina instanciación y
Programación Orientada a Objetos
Es posible definir unos atributos básicos atributos, debe anteponerse "self.", para
en la clase. De esa manera todos los objetos que se distingan de los parámetros
podrían tener unos atributos por defecto y
podemos consultar el valor por defecto que
deben tener los objetos haciendo referencia Métodos especiales
al atributo en la definición de la clase.
Constructor
Definir un método es bastante simple,
Ahora que sabemos crear métodos y hemos
sólo tenemos que añadirlo en la clase y
aprendido para qué sirve el argumento
llamarlo desde el objeto con los paréntesis,
self, es momento de introducir algunos
como si de una función se tratase
métodos especiales de las clases.
Cuando se ejecuta un método desde un
El constructor es un método que se llama
objeto, se envía un primer argumento
automáticamente al crear un objeto, se
implícito que hace referencia al propio
define con __init__. Su finalidad es la de
objeto.
construir los objetos, permitiéndonos enviar
Este primer parámetro que representa al datos para construirlo.
objeto sobre el que está actuando se
denomina self. Para acceder a los
Programación Orientada a Objetos
#Class_Clientes.py Por ejemplo, este fragmento de programa
class Cliente:
def __init__(self, nombre, dni, edad): muestra el dni de los integrantes de la
self.nombre=nombre lista:
self.dni=dni
self.edad=edad
for i in clientes:
print(clientes[i].dni)
Todos los métodos tendrán como primer
parámetro uno llamado self. Le siguen #o también
tantos parámetros como campos tiene una
variable del tipo Cliente for Cliente in clientes :
print(clientes.dni)
Cada cliente creado es una instancia u
objeto de la clase Cliente. Podemos dotar a los objetos de la clase
Cliente de cierta «inteligencia». Definiremos
Si en algún momento necesitamos acceder a
métodos adicionales :
nombre, dni o edad, podemos hacerlo
anteponiendo el nombre del objeto al
def iniciales(self):
nombre del atributo, separándolos mediante cadena=''
for caracter in self.nombre:
un punto ('.’), como se verá en el ejemplo
if 'A' <= caracter <='Z':
Tema1_02 cadena=cadena+caracter+'. '
return cadena
9
Programación Orientada a Objetos
String #Tema1_02
from Class_Clientes import Cliente
El método __str__ es el que devuelve la
juan = Cliente('Juan Perez','12345678Z',19)
representación de un objeto en forma de pepe = Cliente('Pepe Lopez','23456789D',20)
cadena. Cuando se llama es cuando toni = Cliente('Antonio Perez','87654321Q',
20)
imprimimos una variable por pantalla (esto clientes = [toni, juan, pepe]
es opcional, pero es realmente muy útil y print(juan)
print('Las iniciales de ', juan.nombre, 'so
recomendado). n ', juan.iniciales())
def __str__(self):
cadena='Nombre: {0}\n'.format(self.n
ombre) Destructor
cadena=cadena + 'DNI: {0}\n'.format(
self.dni) Si existe un constructor también debe existir
cadena=cadena + 'Edad: {0}\n'.format un destructor que se llame al eliminar el
(self.edad)
return cadena objeto para que encargue de las tareas de
limpieza como vaciar la memoria. Ese es el
Y este sería el código del programa principal papel del método especial del.
que usa la clase cliente. Lo primero que
Es muy raro sobreescribir este método
debemos de hacer es importar la clase
porque se maneja automáticamente, pero es
Cliente del fichero que la contiene
interesante saber que existe.
(Class_Clientes.py) 10
__del__(self)
Programación Orientada a Objetos
Referencias y objetos E invocarlo mediante
>>>copia=juan.copia()
Hemos de tratar ahora un problema que ya
nos es conocido
¿Cómo ha quedado la memoria en este
>>> juan=Cliente('Juan Perez’, '12345678Z’, caso? Observa detenidamente la siguiente
19)
>>> copia=juan
figura:
>>> copia.edad=20
>>> print (copia.edad)
20
>>> print(juan.edad)
20
def copia(self):
nuevo=Cliente(self.nombre,self.dni,se
lf.edad)
11
return nuevo
Programación Orientada a Objetos
def consultar(self, nombre):
Una aplicación: un listín telefónico
if nombre in self.listin:
Construyamos un programa que gestione un return self.listin[nombre]
listín telefónico que permita asociar a una else:
return []
persona más de un teléfono. A través de un
menú podremos seleccionar diferentes def eliminar(self, nombre):
acciones: añadir y/o eliminar teléfonos y if nombre in self.listin:
del self.listin[nombre]
consultar el listín.
#Class_Agenda.py #Tema1_03
class Listin: from Class_Agenda import Listin
def __init__(self):
def menu():
self.listin={}
opcion = 0
while opcion < 1 or opcion > 4:
def añadir(self, nombre, telefono):
print('\tEscoge una opción:')
if nombre in self.listin:
print('\t\t1) Añadir teléfonos.')
if not telefono in self.listin[n
print('\t\t2) Consultar el listín.')
ombre]:
print('\t\t3) Eliminar persona del listí
self.listin[nombre].append(t n.')
elefono)
print('\t\t4) Salir.')
else:
self.listin[nombre]=[telefono] opcion = int(input('\t Opción? '))
return opcion
12
Programación Orientada a Objetos
# programa principal elif opcion == 3:
listin = Listin() nombre = input('Nombre: ')
listin.eliminar(nombre)
opcion = 0
while opcion != 4:
opcion = menu() Length
if opcion == 1: Finalmente, otro método especial
nombre = input('Nombre: ')
interesante es el que devuelve la longitud
telefono = input('Teléfono: ')
listin.añadir(nombre, telefono) __len__. Normalmente está ligado a
mas = input(f'¿Deseas añadir otro secuencias, pero nada impide definirlo en
teléfono a {nombre} (s/n)? ')
una clase. Lo usaremos más adelante.
while mas == 's':
telefono = input('Teléfono: ')
class Cancion:
listin.añadir(nombre, telefono)
mas = input(f'¿Deseas añadir def __init__(self, autor, titulo,duracio
otro teléfono a {nombre} (s/n)? ') n):
elif opcion == 2: self.duracion = duracion
nombre = input('Nombre: ') def __len__ (self ):
telefonos = listin.consultar(nombre) return self.duracion
for telefono in telefonos:
cancion = Cancion("Queen", "Don't Stop Me No
print(telefono) w",210)
print(len(cancion))
print(cancion.__len__())
Programación Orientada a Objetos
Puntos y vectores en el plano
más simplificada
class Fraccion:
def __init__(self,arriba,abajo):
self.num = arriba
self.den = abajo
def __str__(self):
return str(self.num)+"/"+str(self
.den) >>> print (miFraccion)
3/5
31
Programación Orientada a Objetos
Podemos redefinir muchos otros métodos def __add__(self,otraFraccion):
class Fraction {
podrán ser accedidas por los métodos de
public: clase del objeto, no por el usuario. Solo los
Fraction(int top, int bottom) métodos public pueden ser accedidos y
{ utilizados por el usuario.
//Fraction contructor method
num = top; //setting num's value Polimorfismo significa la capacidad de
den = bottom;//setting den's value
aparecer en muchas formas. En la POO, el
}
private: polimorfismo se refiere a la capacidad de
int num; // num atribute procesar objetos o métodos de manera
int den; // den attribute diferente dependiendo de su tipo de datos,
};
clase, número de argumentos, etc.
C ++ nos permite controlar el acceso con Por ejemplo, podemos agregar constructores
las palabras clave de acceso public y adicionales para manejar números enteros e
private. instancias sin parámetros dados:
35
Programación Orientada a Objetos
Fraction (int top, int bottom){
con derecho a acceder a todos los miembros
num = top;
den = bottom; privados y protegidos de la clase.
}
Fraction (int top){ La sobrecarga de operadores nos
num = top; permite hacer que los operadores funcionen
den = 1;
para clases definidas por el usuario al definir
}
Fraction (){ un significado especial para ese operador
num = 0; cuando se aplica a objetos de la clase como
den = 1;
operandos.
}
Deberemos definir un método llamado algo En C ++, este nuevo operador debe
así como show que permitirá que el objeto implementarse como un friend de la clase
Fraction se imprima a sí mismo como una para definir el comportamiento del operador
cadena en los objetos de la clase a partir de un
void show(){ método que no sea de la clase.
cout << num << "/" << den << endl;
} Por ejemplo, en C ++, hacemos una
sobrecarga de operadores al declarar una
Una función friend de una clase es una función amiga con el nombre de << y darle
función definida fuera de su alcance, pero una nueva implementación externa
36
Programación Orientada a Objetos
#include <iostream>
Podemos sobrecargar muchos otros
using namespace std;
class Fraction { operadores para nuestra nueva clase
public: Fraction. Algunas de las más importantes
Fraction(int top = 0, int bottom = 1){
son las operaciones aritméticas básicas.
num = top;
den = bottom;}
//the following tells the compiler to look f Fraction operator +(const Fraction &otherFrac){
or this friend's definition outside the class //Note the return type is a Fraction
friend ostream &operator << (ostream &stream int newnum = num*otherFrac.den + den*otherFr
, const Fraction &frac); ac.num;
private: int newden = den*otherFrac.den;
int num, den; return Fraction(newnum, newden);
}; }
ostream &operator << (ostream &stream, const Fra
ction &frac) {
/** this is the definition. */ Podemos usar este método escribiendo una
stream << frac.num << "/" << frac.den; expresión aritmética estándar que incluya
return stream;
fracciones, asignando el resultado de la
}
int main() suma y luego imprimiendo nuestro
{ resultado.
Fraction myfraction(3, 5);
cout << myfraction; También podríamos reescribir el operador de
return 0;
suma como una función amiga. 37
}
Programación Orientada a Objetos
friend Fraction operator +(const Fraction &frac1
La clase Fracción completa en C++
, const Fraction &frac2);
def obtenerEtiqueta(self):
return self.etiqueta
def obtenerSalida(self):
self.salida = self.ejecutarLogicaDePuert
a()
return self.salida
41
Programación Orientada a Objetos
El otro comportamiento que necesita toda El parámetro self es una referencia al
puerta lógica es la capacidad de conocer su verdadero objeto puerta que invoca el
valor de salida. Esto requerirá que la método.
puerta lleve a cabo la lógica apropiada
Cualquier puerta lógica nueva que se
con base en la entrada actual.
agregue a la jerarquía simplemente tendrá
Con el fin de producir la salida, la puerta que implementar la función
tiene que saber cuál es esa lógica ejecutarLogicaDePuerta y se utilizará en
el momento apropiado
En este punto, no implementaremos la
función ejecutarLogicaDePuerta.
La clase PuertaBinaria será una subclase
Todavía no sabemos cómo llevará a cabo
de PuertaLogica y agregará dos líneas de
cada puerta su propia operación lógica. entrada.
Estamos escribiendo un método que usará
código que aún no existe. La clase PuertaUnaria también será
subclase de PuertaLogica pero sólo
Estos detalles serán incluidos por cada contará con una única línea de entrada.
puerta individual que se añada a la
jerarquía.
42
Programación Orientada a Objetos
En el diseño de circuitos digitales, estas heredados de PuertaLogica. En este caso,
líneas a veces se llaman “pines” por lo que eso significa la etiqueta para la puerta. A
vamos a utilizar esa terminología en nuestra continuación, el constructor agrega las dos
implementación. líneas de entrada (pinA y pinB).
def ejecutarLogicaDePuerta(self):
class PuertaNOT(PuertaUnaria):
a = self.obtenerPinA()
def __init__(self,n):
b = self.obtenerPinB()
PuertaUnaria.__init__(self,n)
if a==1 and b==1:
return 1
def ejecutarLogicaDePuerta(self):
else:
if self.obtenerPin():
return 0
return 0
else:
return 1
44
Programación Orientada a Objetos
Vamos a centrar nuestra atención en la Ahora, con la clase Conector, decimos que
construcción de circuitos. Para crear un un Conector TIENE-UNA PuertaLogica lo
circuito, necesitamos conectar las puertas cual significa que los conectores tendrán
juntas, la salida de una fluirá hacia la instancias de la clase PuertaLogica dentro
entrada de otra. Para ello, de ellos, pero no forman parte de la
implementaremos una nueva clase llamada jerarquía.
Conector.
Al diseñar clases, hay que distinguir entre
Esta clase usará la jerarquía de ellas por el aquéllas que tienen la relación ES-UN(A) (lo
hecho que cada conector tendrá dos cual requiere herencia) y aquéllas que
puertas, una en cada extremo. tienen relaciones TIENE-UN(A) (sin
herencia).
Esta relación es muy importante en la
programación orientada a objetos. class Conector:
def __init__(self, deComp, aComp):
self.dePuerta = deComp
self.aPuerta = aComp
aComp.asignarProximoPin(self)
def obtenerFuente(self):
return self.dePuerta
def obtenerDestino(self): 45
return self.aPuerta
Programación Orientada a Objetos
En la clase PuertaBinaria, para puertas Ahora es posible obtener entradas desde dos
con dos posibles líneas de entrada, el lugares: externamente, como antes, y desde
conector debe conectarse a una sola línea. la salida de una puerta que está conectada a
esa línea de entrada.
Si ambas están disponibles, elegiremos pinA
de forma predeterminada. Esto requiere un cambio en los métodos
Si pinA ya está conectado, entonces obtenerPinA y obtenerPinB
elegiremos pinB. Si la línea de entrada no está conectada a
nada (None), entonces se pide al usuario que
No es posible conectarse a una puerta sin
introduzca el valor externamente como antes.
líneas de entrada disponibles.
Sin embargo, si hay una conexión, se accede a
def asignarProximoPin(self,fuente): ella y se consulta el valor de salida de
if self.pinA == None:
self.pinA = fuente
dePuerta.
else:
if self.pinB == None:
self.pinB = fuente
else:
raise RuntimeError("Error: NO HAY PIN
ES DISPONIBLES")
Programación Orientada a Objetos
def obtenerPinA(self): def obtenerSalida(self):
if self.pinA == None: self.salida = self.ejecutarLogicaDePuerta
return int(input("Introduzca la ()
entrada del Pin A para la Puerta return self.salida
"+self.obtenerNombre()+"--> "))
else: class PuertaBinaria(PuertaLogica):
return def __init__(self,n):
self.pinA.obtenerFuente().obtenerSalida() PuertaLogica.__init__(self,n)
self.pinA = None
self.pinB = None
Esto, a su vez, hace que esa puerta
procese su lógica. Se continúa este def obtenerPinA(self):
if self.pinA == None:
proceso hasta que todas las entradas estén
return int(input("Introduzca la entra
disponibles y el valor de salida final se da del Pin A para la Puerta "+self.obtenerNombre(
)+"--> "))
convierta en la entrada requerida para la
else:
puerta en cuestión. return self.pinA.obtenerFuente().obtenerSalida()
#Class_Puertas
def obtenerPinB(self):
class PuertaLogica: if self.pinB == None:
def __init__(self,n): return int(input("Introduzca la entra
self.nombre = n da del Pin B para la Puerta "+self.obtenerNombre(
self.salida = None )+"--> "))
else:
def obtenerNombre(self): return self.pinB.obtenerFuente().obte
return self.nombre nerSalida()
Programación Orientada a Objetos
def asignarProximoPin(self,fuente): class PuertaOR(PuertaBinaria):
if self.pinA == None: def __init__(self,n):
self.pinA = fuente PuertaBinaria.__init__(self,n)
else:
if self.pinB == None: def ejecutarLogicaDePuerta(self):
self.pinB = fuente
a = self.obtenerPinA()
else:
b = self.obtenerPinB()
print("No se puede conectar: NO
HAY PINES DISPONIBLES en esta Puerta") if a ==1 or b==1:
return 1
class PuertaAND(PuertaBinaria): else:
def __init__(self,n): return 0
PuertaBinaria.__init__(self,n)
class PuertaUnaria(PuertaLogica):
def ejecutarLogicaDePuerta(self): def __init__(self,n):
a = self.obtenerPinA() PuertaLogica.__init__(self,n)
b = self.obtenerPinB() self.pin = None
if a==1 and b==1:
return 1 def obtenerPin(self):
else: if self.pin == None:
return 0 return int(input("Introduzca la entr
ada del Pin para la Puerta "+self.obtenerNombre(
)+"--> "))
else:
return self.pin.obtenerFuente().obte
nerSalida()
48
Programación Orientada a Objetos
def asignarProximoPin(self,fuente):
El siguiente fragmento construye el circuito
if self.pin == None:
self.pin = fuente mostrado anteriormente:
else:
print("No se puede conectar: NO HA
Y PINES DISPONIBLES en esta Puerta")
class PuertaNOT(PuertaUnaria):
def __init__(self,n):
PuertaUnaria.__init__(self,n)
def ejecutarLogicaDePuerta(self):
if self.obtenerPin():
return 0
else:
#Tema1_18
return 1
class Conector: from Class_Puertas import *
def __init__(self, deComp, aComp): def main():
self.dePuerta = deComp c1 = PuertaAND("C1")
self.aPuerta = aComp c2 = PuertaAND("C2")
aComp.asignarProximoPin(self) c3 = PuertaOR("C3")
c4 = PuertaNOT("C4")
def obtenerFuente(self): c1 = Conector(c1, c3)
return self.dePuerta c2 = Conector(c2, c3)
c3 = Conector(c3, c4)
def obtenerDestino(self): print(c4.obtenerSalida())
return self.aPuerta main() 49
Programación Orientada a Objetos
La clase PuertasLógicas en C++ clases derivadas pueden acceder a él. La
Veamos cómo vamos construyendo la clase palabra clave de acceso protected se utiliza
puertaslogicas en C++. Inicialmente la para esto
clase completa será La clase BinaryGate será una subclase de
LogicGate y agregará dos líneas de
class LogicGate {
public: entrada. La clase UnaryGate también tendrá
LogicGate(string n) {
label = n; una subclase, LogicGate pero solo tendrá
} una línea de entrada
string getLabel() {
return label;
} class BinaryGate : public LogicGate
bool getOutput() { {
output = performGateLogic(); public:
return output; BinaryGate(string n) : LogicGate(n) {
} pinATaken = false;
protected: pinBTaken = false;}
string label; bool getPinA() {
bool output; if (pinATaken==false) {
}; cout << "Enter Pin input
for gate " << getLabel() << " : ";
cin >> pinA;
Una variable o función protegida de pinATaken = true}
miembro es similar a un miembro privado, return pinA;}