Aquí diferenciaremos lo que es una variable de una instancia de lo que es una variable de una clase.
Como su nombre lo indica, la variable de clase afecta a todas las intancias de la clase.
Recordar: una variable de instancia es única para cada instancia
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)
Tenemos creada la clase de Empleados y ahora, por convenio salarial, se fima un incremento general del salario de un tanto %, para todos los empleados.
Esta "variable" es común para todos los empleados.
Una primera forma es crear un método que ya incluya ese incremento salarial, e.g. un $10\%$.
Esta sería una manera bruta de hacerlo
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}")
def incremento_salarial(self):
self.salario = int(self.salario * 1.1)
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
print(emp_1.salario)
emp_1.incremento_salarial()
print(emp_1.salario)
Si bien obtenemos el incremento salarial, hay dos problemas:
1.- Hay que entrar en el código para modificar el porcentaje de incremento. Eso hace complejo y de facil error el script.
2.- No hay forma de sacar, como atributo, el incremento de salario, e.g., ya sea mediante emp_1.tasa_incremento_salarial
o, como es algo que afecta a toda la clase Empleados.tasa_incremento_salarial
por que no existe.
Vamos a crear una class variable y escribir el código para ello
class Empleados:
tasa_incremento_salarial = 1.1
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}")
def incremento_salarial(self):
self.salario = int(self.salario * tasa_incremento_salarial)
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_1.incremento_salarial()
Pone un name error: la variable no está definida.
Cuando accedemos a las variables de clase, debemos acceder por medio de la clase, i.e., Empleados.tasa_incremento_salarial
o mediante una instancia de una clase (self
, pero esta última es confusa, ya que hace referencia a la instancia cuando es una variable de clase, por ello tenemos que tener claro cuando lo haremos)
class Empleados:
tasa_incremento_salarial = 1.1
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}")
def incremento_salarial(self):
self.salario = int(self.salario * Empleados.tasa_incremento_salarial)
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_1.incremento_salarial()
emp_1.salario
Veamos que podemos acceder a la variable de clase tanto desde la clase como desde las instancias
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
print(Empleados.tasa_incremento_salarial)
print(emp_1.tasa_incremento_salarial)
print(emp_2.tasa_incremento_salarial)
Scope: cuando accedemos a un attributo de una instancia, primero busca en el local, es decir, si la instancia tiene definida ese atributo.
Si no lo tiene, lo busca en el enclosing, que es la clase que lo ha construido.
Veamos el namespace de una instancia y veremos que no tiene definido el atributo tasa_incremento_salarial
emp_1.__dict__
Empleados.__dict__
¿Que pasa si cambia el porcentaje de incremento?
Empleados.tasa_incremento_salarial = 1.05
print(Empleados.tasa_incremento_salarial)
print(emp_1.tasa_incremento_salarial)
print(emp_2.tasa_incremento_salarial)
Cambia para la clase y para las instancias.
Qué pasaría si lo hacemos para una sola instancia.
emp_1.tasa_incremento_salarial = 1.15
print(Empleados.tasa_incremento_salarial)
print(emp_1.tasa_incremento_salarial)
print(emp_2.tasa_incremento_salarial)
Cuando se escribe emp_1.tasa_incremento_salarial
se crea el atributo particular para la instancia (mirar que antes, no lo tenia)
emp_1.__dict__
Esto puede afectar los métodos que incluyen variables de clase, según como los definamos, e.g.
Con referencia a la clase
def incremento_salarial(self):
self.salario = int(self.salario * Empleados.tasa_incremento_salarial)
o con referencia a la instancia
def incremento_salarial(self):
self.salario = int(self.salario * self.tasa_incremento_salarial)
Vamos a modificar la clase y poner self
para poder individualizar el incremento salarial.
Además, como veremos, esto permitirá a una subclase modificar esta constante.
class Empleados:
tasa_incremento_salarial = 1.1
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}")
def incremento_salarial(self):
self.salario = int(self.salario * self.tasa_incremento_salarial)
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
Veamos un ejemplo donde no tiene sentido utilizar la instancia self
por que hace referencia a algo que es general.
Por ejemplo, el número total de empleados
Vamos a crear una variable de clase, num_de_emps
y, como inicialización, le daremos el valor $0$.
A medida que creamos empleados, vamos añadiendo valores a esta función.
Como cada empleado es una instancia que se crea con el constructor __int__
incrementaremos (redefiniremos) este valor cada vez que se cree una nueva instancia.
class Empleados:
tasa_incremento_salarial = 1.1
num_de_emps = 0
def __init__(self, nombre, apellido, salario):
self.nombre = nombre
self.apellido = apellido
self.salario = salario
self.email = nombre + "." + apellido + "@emp.com"
Empleados.num_de_emps += 1
def nombre_completo(self):
print(f"{self.nombre} {self.apellido}")
def incremento_salarial(self):
self.salario = int(self.salario * self.tasa_incremento_salarial)
Empleados.num_de_emps
emp_1 = Empleados("Daniel", "Miles", 100000)
emp_2 = Empleados("Ines", "Gonzalez", 150000)
Empleados.num_de_emps
Python proporciona métodos especiales que resultan útiles.
Por ejemplo, recuerda que las listas y tuplas tienen una noción de longitud y que esta longitud puede ser consultada mediante la función len
x = (10, 20)
len(x)
Si desea proporcionar un valor de retorno para la función len
cuando se aplica a
su objeto definido por el usuario, utilice el método especial __len__
.
class Foo:
def __len__(self):
return 42
Por tanto, tenemos
f = Foo()
len(f)
Un método especial que se usa regularmente es el método __call__
.
Este método se puede utilizar para hacer que tus instancias sean callables, al igual que las funciones
class Foo:
def __call__(self, x):
return x + 42
Entonces, creamos la instancia f
y vemos que es una función, ya que la puedo llamar
f = Foo()
f(8) # Es equivalente a
# Podriamos hacer
f.__call__(8)
1.- Dado el polinomio
$$ p(x) = a_0 + a_1 x + a_2 x^2 + \cdots a_N x^N = \sum_{n=0}^N a_n x^n \qquad (x \in \mathbb{R}) \tag{1} $$Escriba la función p(x, coeff)
para que evalue el polinomio (1) en un punto $x$ y para un conjunto de coeficientes $(a_0,...,a_N)$
2.- Ahora escriba una clase, llamada Polynomio
para representar y manipular polinomis tipo (1)
Ayuda: La instancia serán los coeficientes del polinomio, i.e., los números $(a_0, \ldots, a_N )$.
Debe haber dos métodos:
A. Que evalue el polinomio en un punto, i.e., que retorne $p(x)$ para cada $ x $.
B. Que retorne la derivada del polinomio reemplazando los coeficientes por los de su derivada $ p' $.
# Parte 1
def p(x, coeff):
return sum(a * x**i for i, a in enumerate(coeff))
class Polinomio:
def __init__(self, coeficientes):
"""
Crea una instancia de la clase Polinomio que representa a
p(x) = a_0 x^0 + ... + a_N x^N,
donde a_i = coeficientes[i].
"""
self.coeficientes = coeficientes
def __call__(self, x):
"Evalue el polinomio en x."
y = 0
for i, a in enumerate(self.coeficientes):
y += a * x**i
return y
def derivada(self):
"""Devuelve los coeficientes de las derivadas
Reasigna los coeficientes en self.coeficientes a p' en vez de los de p."""
d_coeficientes = []
for i, a in enumerate(self.coeficientes):
d_coeficientes.append(i * a)
# Quito el primer elemento, ya que su derivada es zero
del d_coeficientes[0]
# Reasingo los coeficientes a los nuevos valores
self.coeficientes = d_coeficientes
return d_coeficientes
p1 = Polinomio([1,2,4])
p1.coeficientes
p1(5)
p1.derivada()