1 – Clases y Objetos

class Fabrica():
    pass #esto determina que es una clase vacía

print(type(Fabrica()))

##################

celular = Fabrica()

print(type(celular))

2 – Metodos y Atributos

# clase
class Persona():

    # atributos
    nombre = "Carlos"
    apellido = "Vergara"
    sexo = "Masculino"
    edad = 30

    # metodos
    def hablar(self, mensaje):
        return mensaje

###########################


# objeto
persona = Persona()

print(persona.hablar("Hola soy"), "{} y mi apellido es {}, tengo {} y de sexo {}".format(
    persona.nombre, persona.apellido, persona.edad, persona.sexo))

3 – Self – Init

class Persona():
    nombre = False

    ## __init__ es el PRIMER METODO que se ejecuta en toda la clase
    ## sirve para inicializar los atributos de una clase
    def __init__(self):
        print("Has creado el objeto Persona")

    ##self es un atributo que reemplaza a global, engloba las variables para que se usen en TODA la clase
    def datos(self): 
        self.nombre = True

##

# persona = Persona()
# persona.datos()
# print(persona.nombre)
class Persona():
    nombre = ""
    apellido = ""

    ## __init__ es el PRIMER METODO que se ejecuta en toda la clase
    ## sirve para inicializar los atributos de una clase
    def __init__(self, pNombre, pApellido):
        self.nombre = pNombre
        self.apellido = pApellido
        print("Has creado el objeto Persona llamado {} {}".format(pNombre,pApellido))
    

persona = Persona("Juan","Perez")

4 – Métodos especiales

class Persona():
    nombre = ""
    apellido = ""

    ## __init__ es el PRIMER METODO que se ejecuta en toda la clase
    ## sirve para inicializar los atributos de una clase
    ## vendría a ser el CONSTRUCTOR de la clase
    def __init__(self, pNombre, pApellido):
        self.nombre = pNombre
        self.apellido = pApellido
        print("El objeto {} {} ha sido creado".format(self.nombre,self.apellido))

    # es un símil al método toString de Java
    def __str__(self):
        return "El objeto tiene como atributo el nombre {} y el apellido {}".format(self.nombre,self.apellido)

    # metodo DESTRUCTOR, los objetos creados en memoria creados por init, 
    # lo elimina y lo reemplaza por otro
    def __del__(self):
        print("El objeto {} {} ha sido destruido".format(self.nombre,self.apellido))
        

persona = Persona("Juan","Perez")
print(str(persona))

5 – Encapsulamiento

class A():
    def __init__(self):
        self.contador = 0
    def incrementar(self):
        self.contador += 1
    def cuenta(self):
        return self.contador

class B():
    def __init__(self):
        self.__contador = 0 ## a un atributo, al ponerle el doble guion bajo está encapsulado, no puede ser ocupado FUERA de la clase B
    def incrementar(self):
        self.__contador += 1
    def cuenta(self):
        return self.__contador

a = A()
a.incrementar()
a.incrementar()
print(a.cuenta())
print(a.contador)

b = B()
b.incrementar()
b.incrementar()
print(b.cuenta())
# print(b.contador) # esta linea devuelve un error porque el atributo está ENCAPSULADO (solo visible dentro de su clase)
print(b._B__contador) # así se puede acceder a un atributo encapsulado dentro de una clase

6 – Herencia

class Animales():
    def __init__(self,descripcion,especie,hablar):
        self.descripcion = descripcion
        self.especie = especie
        self.hablar = hablar

    def habla(self):
        print("Yo hago ",self.hablar)

    def describir(self):
        print("Soy de la especie ",self.especie)

class Perro(Animales): # HERENCIA EN PYTHON
    pass

class Abeja(Animales): #HERENCIA
    def sonido(self,sonido):
        self.sonido = sonido
        print(self.sonido)

##############

perro = Perro("Perro","Mamífero","Guau!")
perro.habla()
perro.describir()

abeja = Abeja("Abeja","Insecto","brr")
abeja.habla()
abeja.describir()
abeja.sonido("brr")

7 – Polimorfismo

##Los metodos de las clases HIJAS pueden modificarse aunque en las clases PADRE ya estén definidas

class Animales():
    def __init__(self,nombre,mensaje):
        self.nombre = nombre
        self.mensaje = mensaje

    def hablar(self):
        print(self.mensaje)


class Perro(Animales): # HERENCIA EN PYTHON
    def hablar(self):
        print("Yo hago Guau!")

class Pez(Animales):
    def hablar(self):
        print("Yo no hablo")

##############

perro = Perro("Firulais","Guau!")
perro.hablar()

# creando una lista de clases
listaAnimales = [Perro("Perrito","Guau2"),Pez("Nemo","nada")]
for i in listaAnimales:
    i.hablar()

8 – Herencia Múltiple

class claseA():
    def mensaje(self):
        print("Esta es la clase A")
    def primera(self):
        print("Estas dentro de la clase A")

class claseB():
    def mensaje(self):
        print("Esta es la clase B")
    def segunda(self):
        print("Estas dentro de la clase B")

class claseC(claseA,claseB): ##toma como primaria la clase más a la izquierda
    pass

objetoC = claseC()
objetoC.primera()
objetoC.segunda()