#!/usr/bin/env python # coding: utf-8 # Open In Colab # # Atributos de clase y atributos de instancia # # * Los **atributos de clase** son compartidos por todas las instancias de una clase, mientras que los **atributos de instancia** son únicos para cada instancia de la clase. # * Los **atributos de clase** se definen dentro de la clase, pero fuera de cualquier método, y se acceden utilizando el nombre de la clase. # * Los **atributos de instancia** se definen dentro del método ```__init__``` de la clase y se acceden utilizando la sintaxis ```self.nombre_atributo``` # * Un ejemplo de la clase Persona con los atributos nombre y edad, donde nombre es un atributo de instancia y edad es un atributo de clase. # - En este ejemplo, esperanza_vida es un atributo de clase porque se define dentro de la clase, pero fuera del método ```__init__``` # - Por otro lado, nombre y edad son atributos de instancia porque se definen dentro del método ```__init__``` y se acceden utilizando la sintaxis ```self.nombre``` y ```self.edad``` # In[ ]: class Persona: # Atributo de clase esperanza_vida = 80 def __init__(self, nombre, edad): # Atributo de instancia self.nombre = nombre self.edad = edad # Crear dos objetos de la clase Persona persona1 = Persona("Juan", 30) persona2 = Persona("María", 25) # Acceder a los atributos de cada objeto print(persona1.nombre) # Salida: "Juan" print(persona1.edad) # Salida: 30 print(persona2.nombre) # Salida: "María" print(persona2.edad) # Salida: 25 # Acceder al atributo de clase desde la clase print(Persona.esperanza_vida) # Salida: 80 # Acceder al atributo de clase desde un objeto print(persona1.esperanza_vida) # Salida: 80 print(persona2.esperanza_vida) # Salida: 80 # Podemos crear **varios objetos** de una misma clase. Cada uno de estos objetos puede tener **diferentes valores para estos atributos**, lo que les confiere sus propias **características**. # # Por ejemplo, podemos tener dos instancias de la clase Perro (bobby y teddy) que tengan atributos como nombre, edad, raza, pelo, ... que sean diferentes y que identifican el carácter propio de cada uno de ellos. # # Tener diferentes valores de los atributos es lo que define el **estado de un objeto**. # # Existen dos tipos de atributos: # - Los **atributos de clase**: se definen en la clase y son comunes a **todos** los objetos de esa clase. # Por ejemplo, para la clase Perro son comunes genero = "Canis", orden = "Carnívora", cuadrupedo = True. # - Los **atributos de instancia**: son **propios** de cada objeto y pueden tener diferentes valores. Podemos crear atributos durante la instanciación. # Por ejemplo, nombre, edad, raza, pelo,... # En general: # * los atributos de clase son para atributos (y métodos) compartidos por todas las instancias de la clase # * los atributos de instancia son para datos únicos de cada instancia # In[ ]: class Perro: genero = "Canis" # atributos de clase compartidos por todas las instancias orden = "Carnívora" cuadrupedo = True def __init__(self, nombre, raza): self.nombre = nombre # atributos de instancia únicos para cada instancia self.raza = raza l = Perro('Laika', 'Siberiana') k = Perro('Kasper', 'Pastor alemán') # In[ ]: l.genero # compartido por todos los perros # In[ ]: k.genero # compartido por todos los perros # In[ ]: l.nombre # único para l # In[ ]: k.nombre # único para k # ## Crear una variable como atributo de clase o de instancia # * Al definir una variable nueva podemos tener dudas sobre si debe ser un atributo de clase o un atributo de instancia. # * Los datos compartidos pueden tener efectos inesperados no deseados al usar objetos mutables, tales como pueden ser listas y diccionarios. # # ### Ejemplo # Supongamos que para recoger los trucos que saben hacer los perros creamos una variable *trucos* en forma de lista. Nuestro error será crearla como atributo de clase, común para todas las instancias, cuando debiera haber sido creado como atributo de instancia. # #### Forma incorrecta # * Usando el atributo trucos fuera del constructor estamos creando una forma incorrecta de hacerlo # * ya que los trucos que se aprenda un perro se asignarán también al otro perro, y esto no debiera ser así. # * Los trucos de cada perro deberían estar separados # In[ ]: class Perro: trucos = [] # uso incorrecto de una variable de clase def __init__(self, nombre): self.nombre = nombre def incluir_truco(self, truco): self.trucos.append(truco) l = Perro('Laika') k = Perro('Kasper') l.incluir_truco('darse la vuelta') k.incluir_truco('hacerse el muerto') l.trucos # inesperadamente compartido por todas las instancias de la misma clase # #### Forma correcta de usar la variable como atributo de instancia # In[ ]: class Perro: def __init__(self, nombre): self.nombre = nombre self.trucos = [] # creamos una lista vacía para cada perro def incluir_truco(self, truco): self.trucos.append(truco) l = Perro('Laika') k = Perro('Kasper') l.incluir_truco('darse la vuelta') k.incluir_truco('hacerse el muerto') print(l.trucos) # ahora los trucos que sabe hacer cada perro están separados print(k.trucos) # ## Modificación de atributos de clase # Se pueden modificar los atributos de clase desde fuera de la propia clase. # Si existe el mismo nombre de atributo en la clase y en la instancia se prioriza el de la instancia. # In[ ]: class Bomberos: helicoptero = False # atributos de clase ¿compartidos por todas las instancias? region = 'Norte' # ¿Todas las estaciones de bombero que se instancian serán del Norte y sin helicóptero? b1 = Bomberos() # Instanciamos la primera estación de bomberos y resulta ser del Norte y sin helicóptero print(b1.helicoptero, b1.region) # In[ ]: b2 = Bomberos() # Instanciamos la segunda estación de bomberos b2.helicoptero = True # Podemos acceder a modificar el atributo de clase desde fuera de la clase print(b2.helicoptero, b2.region) # Ahora vemos que esta estación de bomberos si tiene helicóptero # In[ ]: # cambiar el atributo de b2 no afecta a b1 print(b1.helicoptero) # b1 continúa sin tener helicópteros # Python, por defecto, no impide, salvo por convenio, que un usuario pueda modificar un atributo de clase desde fuera de su propia clase. # En otros lenguajes, por ejemplo, en JAVA cualquier atributo que se declara ha de ser público (Public) o privado (Private), de forma que los privados no son accesibles desde fuera de su propia clase. # ## Invocar a un método desde otro método # Al igual que una función puede desde el interior de su código llamar a otra función, un método (que es una función) puede llamar a otro método ambos dentro de una clase. # In[ ]: class Acuario: def __init__(self): self.poblacion = [] # atributo población en forma de lista, inicialmente vacía def incluir(self, pez): self.poblacion.append(pez) def incluir_pareja(self, pez): self.incluir(pez) # invocamos desde un método otro método dentro de la misma clase self.incluir(pez) # volvemos a invocar el método incluir para hacer la pareja a = Acuario() # instanciamos un objeto de tipo Acuario a.incluir('Guppy') # Añadimos nuestro primer pez a.poblacion # consultamos nuestra variable población # In[ ]: a.incluir_pareja('Neón') # incluimos una pareja de peces a.poblacion # al consultar la población vemos que se ha añadido la pareja # ## Aplicar a un objeto ```type``` y ```dir``` # In[ ]: type(a) # la instancia 'a' es un objeto de tipo Acuario # In[ ]: type(a.incluir) # el método 'incluir' es de método # In[ ]: type(a.poblacion) # el atributo 'poblacion' es una variable de tipo lista # In[ ]: dir(a) # al hacer un dir de un objeto obtenemos todos sus atributos y métodos incluidos los especiales