El objetivo de una clase es agrupar racionalmente nuestra información/data en funciones que podamos reutilizar.
Los datos y funciones asociados a una clase se llaman: attributes and methods
Method: una función asociada a una clase
Attribute: es una información/data de una clase u objeto en esa clase
Vamos a crear una clase para el departamento de RRHH: cada empleado tiene atributos específicos y métodos.
Cada empleado tiene un nombre, un e.mail, un salario, etc.
Definir una clase nos permitiria rapidamente asingar estos atributos a los empleados y aplicar funciones que permitan caracterizar a cada empleado en particular.
Vamos a crear una clase específica para empleados como guía de aprendizaje
Crear una clase es tan fácil como poner
class Empleados:
Vamos a simplemente crear esto, y para que funciones, insertaremos pass
statement
class Empleados:
pass
Aquí ya hemos creado una clase, aunque no tiene ni atributos, ni métodos.
Empleados()
Hay que diferenciar una clase de una instancia de una clase.
Una clase es una fábrica para crear instancias (instances)
Cada empleado que fabriquemos con esa clase, será una instance de esa clase
Si bien la clase crea instancias iguales (empleados), cada uno tiene su particularidad, sus atributos (nombre, telefono...)
Por ejemplo
emp_1 = Empleados()
emp_2 = Empleados()
Cada uno de estos es una instancia, un empreado distinto, de una misma clase, Empleados.
print(emp_1)
print(emp_2)
Esto va a resultar importante cuando hablemos de instance variables o class variables.
Aquí nos quedaremos con instance variables. Luego hablaremos de class variables
Instance variables: contiene data que es única para cada instancia.
Por ejemplo, podríamos crear manualmente estas variables de instancia de forma manual:
Nombre y apellido del empleado; su e.mail y su paga.
emp_1.nombre = "Daniel"
emp_1.apellido = "Miles"
emp_1.email = "Daniel.Miles@emp.com"
emp_1.salario = "100000"
emp_2.nombre = "Ines"
emp_2.apellido = "Gonzalez"
emp_2.email = "Ines.Gonzalez@emp.com"
emp_2.salario = "150000"
Ahora cada instancia: empleado 1 y empleado 2, tienen sus propios atributos, características.
print(emp_1.email)
print(emp_2.email)
Crear las instancias de esta manera no tienen ninguna ventaja
En otras palabras: que aporta una clase si tenemos que escribir todo.
Idea: por que no buscamos alguna forma de no tener que crear esto manualmente cada vez que un empleado llega a la empresa
Para crear una instancia, vamos a utilizar el dunder método __init__
Podemos pensar en este método como que se utiliza para inicializar la instancia o el constructor
El primer argumento de este método es la instancia, y por convención y legibilidad se le llama self
__init(self,...)
Después de self, incluiremos los argumentos que nos gustaría que inicialmente definen los atributos básicos, e.g.,
__init__(self,nombre, apellido, salario)
class Empleados:
def __init__(self, nombre, apellido, salario):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
Cuando escribimos self.nombre = nombre
es equivalente a lo que hemos escrito antes, emp_1.nombre = "Daniel"
self
toma el nombre de la instancia emp_1
El nombre del atributo dentro de la clase puede ser cualquiera, e.g., self.NMB = nombre
pero lo que se busca siempre es una legibilidad, sobre todo para quien no escribió el script y quiere entenderlo.
Vamos a craer una instancia
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
print(emp_1.email)
print(emp_2.email)
Nota: es importante no incorporar un return
en __init__()
pués, si no, da un error
class Empleados:
def __init__(self, nombre, apellido, salario):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
return nombre
emp_1 = Empleados("Daniel", "Miles", 100000)
Nota: Es posible inicializar un objeto con argumentos opcionales, i.e., con un conjunto amplio de argumentos que pueden o no usarse
class Empleados:
def __init__(self, nombre, apellido, salario, edad = None, tenure = None):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
self.edad = edad
self.tenure = tenure
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_1.edad
emp_1 = Empleados("Daniel", "Miles", 100000, 25)
emp_1.edad
otros = {"edad" :25, "tenure" : 3 }
emp_1 = Empleados("Daniel", "Miles", 100000, **otros)
emp_1.tenure
Nota: Cuando se llama a Python para crear una nueva instancia de una clase, en realidad concurren dos métodos especiales de forma simultanea:
1.- __new__()
: crea una nueva instancia -objeto- de la clase que esta vacio. (instance creator)
2.- __init__()
: coge este objeto y le da sus atributos. (instance initializer)
A modo de ejemplo, y sin intensión de profundidad, veamos un ejemplo (obtenido de Real Python)
class Punto:
def __new__(cls, *args, **kwargs):
print("Primer paso: creamos una instancia vacia")
return super().__new__(cls)
punto = Punto()
punto
isinstance(punto, Punto)
punto.x
Da error por que no lo hemos inicializado
Vamos a introducir el método __init__()
class Punto:
def __new__(cls, *args, **kwargs):
print("Primer paso: creamos una instancia vacia")
return super().__new__(cls)
def __init__(self, x, y):
print("Segundo paso: caracterizamos la nueva instancia")
self.x = x
self.y = y
punto = Punto(21,22)
punto.x
Todo lo que hemos creado son atributos, caracteristicas de los miembros de la clase.
Vamos a crear un método: algo aplicable a los miembros de la clase.
Por ejemplo, si quiero tener el nombre completo del trabajador podria hace
print(f"{emp_1.nombre} {emp_1.apellido}")
Dado que tener el nombre completo es una tarea que se realiza frecuentemente lo que haremos será una función, dentro de la clase, para que me lo haga automaticamente
class Empleados:
def __init__(self, nombre, apellido, salario):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
def nombre_completo(self):
print(f"{self.nombre} {self.apellido}")
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
emp_1.nombre_completo
emp_1.nombre_completo()
Como es un método, necesitamos agregarle ()
Un error normal es olvidarse el self
argument en los métodos, e.g.,
def nombre_completo():
class Empleados:
def __init__(self, nombre, apellido, salario):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
def nombre_completo():
print(f"{self.nombre} {self.apellido}")
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
Observar que, en principio, todo parece razonable pues el script se ha ejecutado correctamente.
Esto es, el interprete de Python no vio ningún sintax error
Pero qué pasa si llamamos al método
emp_1.nombre_completo()
El error hace referencia a que nos hemos olvidado de un argumento necesario para ejecutar la función.
El interprete del método necesita coger la instancia, self
, como argumento, para introducirla en la función.
Nota: puedo llamar la instancia directamente desde la clase
Empleados.nombre_completo(emp_1)
Observar que en este caso, tengo que ponerle directamente, como argumento, la instancia, i.e., self
.