Saltar a contenido

Clases

En Python todo son objetos y los objetos son instancias de clases.

Una clase es un tipo de dato que incluye en su interior variables que contendrán los datos de su estado y funciones denominadas métodos que establecerán los posibles comportamientos y procesos que soporta.

1
2
3
4
5
print(type('Hola'))
print(type(14))
print(type(2.5))
print(type(True))
print(type([1, 2, 3, 4]))

Resultado:

<class 'str'>
<class 'int'>
<class 'float'>
<class 'bool'>
<class 'list'>

Al ejecutar el ejemplo anterior, vemos que todos los tipos de datos para los tipos de datos básicos devuelven la palabra clave class. Todos esos datos son instancias de una clase predefinida en el lenguaje.

Constructor

Nosotros podemos crear nuestros propios tipos de datos (nuestras propias clases) con la palabra clave class con el siguiente formato:

1
2
3
4
class nombre_clase(SuperClase):
    __init__
    atributos
    metodos

nombre_clase es como queremos nombrar a la clase. SuperClase es el nombre de otra clase existente de la cual queremos heredar nuestra nueva clase (esto es opcional). __init__ es un método especial de la clase llamado constructor que siempre hay que incluir. "atributos" son las variables que queremos definir para nuestra clase y "metodos" las funciones que contendrá la clase.

1
2
3
4
5
6
7
8
9
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)

print(type(p1))

Resultado:

<class '__main__.Persona'>

Vamos a crear una clase denominada Persona. Para ello en la línea 1 iniciamos su definición con la palabra clave class y el nombre de la clase seguida por dos puntos. Indentado cuatro espacios a la derecha definimos, a continuación, el cuerpo de la clase. En este caso consta únicamente de un método especial llamado __init__ que actuará como constructor. Un constructor sirve para pasar datos a los nuevos objetos que se crean de esa clase.

Un método de una clase no es más que una función miembro de la misma y se define como cualquier otra función. En todos los métodos de una clase hay un primer parámetro denominado self. Cuando se llame al método no es necesario proporcionar dicho argumento, ya que Python se encarga de colocar en ese parámetro una referencia al objeto correspondiente sobre el que se está llamando al método.

En el ejemplo, aparte de self que siempre va de forma fija, el constructor admitirá dos parámetros adicionales: nombre y edad. El constructor se encargará de crear en el objeto (self) dos atributos con esos nombres (nombre y edad) y almacenar en esos atributos los valores que recibe en los parámetros. Un atributo no es más que una variable que pertenece al objeto.

Después de definir la clase, en las líneas 6 y 7 se construyen dos objetos de ese tipo. Para crear un nuevo objeto hemos de escribir el nombre de la clase y, a continuación, incluir los argumentos necesarios que deben de corresponder con los que espera el constructor de la clase. Cada uno de los objetos creados se almacenan respectivamente en las variables p1 y p2. Ambos objetos son de la misma clase, pero cada uno tiene su estado propio, es decir, unos valores diferentes para sus atributos (nombre y edad).

Identificador

Con la función id podemos obtener un identificador único de un objeto (en el ámbito de un programa).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Alba', 11)

print(id(p1))
print(id(p2))

Resultado:

2630664414448
2630664620256

En el ejemplo se crean dos objetos independientes con los mismos valores (nombre y edad). A pesar de ser dos objetos aparentemente iguales, son independientes y podemos comprobarlo obteniendo su identificador correspondiente.

Asignaciones

Si en una variable guardamos el valor de otra variable que contiene un objeto, no se realiza una copia del mismo, las dos variables pasan a "apuntar" al mismo objeto. Podemos pensar que son dos etiquetas que se le colocan al mismo objeto para poder identificarle.

Si a una variable que contiene un objeto, le asignamos el valor de otra variable que contiene otro objeto, el objeto de la primera no se ve alterado. Unicamente la primera variable pasa a apuntar al segundo objeto.

Vamos a ilustrar esto con un ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)

p3 = p1
print('p1: ', p1.nombre)
print('p2: ', p2.nombre)
print('p3: ', p3.nombre)

p1 = p2
print('\np1: ', p1.nombre)
print('p2: ', p2.nombre)
print('p3: ', p3.nombre)

Resultado:

p1:  Alba
p2:  Miguel
p3:  Alba

p1:  Miguel
p2:  Miguel
p3:  Alba

Al ejecutar la línea 9 la variable p3 pasa a apuntar también al objeto al que apunta la variable p1 (el objeto con nombre=Alba). Al ejecutar la línea 14 la variable p1 pasa a apuntar también al objeto al que apunta la variable p2. La variable p3 no se ve alterada por ello. Tampoco se ve alterado ninguno de los objetos a los que las variables apuntan.

Atributos

Podemos acceder a los atributos de un objeto utilizando el operador punto .. Para ello es necesario escribir una variable que contenga un objeto, el operador punto y el nombre del atributo (variable de la instancia). Esto vale, no solamente para acceder al valor almacenado en el atributo, sino también para modificarlo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)

p2.edad = 47

print(p2.edad)

Resultado:

47

Representación string

Cuando hemos creado una nueva clase e invocamos la función print sobre un objeto de ese tipo aparece un mensaje indicando el nombre de la clase y la dirección de memoria donde reside el objeto. Este es el comportamiento por defecto.

1
2
3
4
5
6
7
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p = Persona('Alba', 11)
print(p)

Resultado:

<__main__.Persona object at 0x000001A257E864F0>

Podemos sobreescribir este comportamiento incorporando un nuevo método en la clase que tenga el nombre especial __str__. En dicho método podemos programar y devolver que nombre debería tener cada objeto basándonos habitualmente en los atributos del objeto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def __str__(self):
        return 'Nombre: ' + self.nombre + '\nEdad: ' + str(self.edad)

p = Persona('Alba', 11)
print(p)

Resultado:

Nombre: Alba
Edad: 11

docstring

Es opcional incluir un string al inicio de la definición de la clase para documentarla.

Además de clarificar el código, algunas herramientas de desarrollo lo muestran para proporcionar información sobre la clase. También podemos acceder a ese comentario desde nuestro código escribiendo el nombre de la clase, un punto y el nombre de variable __doc__ (dos guiones bajos al principio y dos al final).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Persona:
    """ Clase para representar a una
        persona incluyendo su nombre y edad"""

    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def __str__(self):
        return 'Nombre: ' + self.nombre + '\nEdad: ' + str(self.edad)

p = Persona('Alba', 11)
print(p.__doc__)

Resultado:

 Clase para representar a una
    persona incluyendo su nombre y edad

Métodos

Podemos añadir comportamientos a las clases mediante métodos. Los métodos son funciones que forman parte de una clase y se ejecutan sobre un objeto particular.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Persona:
    """ Clase para representar a una
        persona incluyendo su nombre y edad"""

    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def __str__(self):
        return 'Nombre: ' + self.nombre + '\nEdad: ' + str(self.edad)

    def cumplir_años(self):
        self.edad += 1
        print('Felicidades ' + self.nombre +
              ', ya tienes ' + str(self.edad) + ' años')

p = Persona('Alba', 11)
print(p)
p.cumplir_años()
print(p)

Resultado:

Nombre: Alba
Edad: 11
Felicidades Alba, ya tienes 12 años
Nombre: Alba
Edad: 12

En el ejemplo se crea el método cumplir_años. Todos los métodos deben de llevar el parámetro self que representa al objeto y que ya se encarga Python de rellenar. En el resto de aspectos se definen como cualquier otra función pudiendo incorporar también parámetros de entrada adicionales y valores de retorno.

Eliminar objetos

Python tiene mecanismos que se encargan de la gestión automática de la memoria.

Por ello para eliminar un objeto creado podemos utilizar la palabra clave del o simplemente asignar el valor None a las variables que apuntan a él. La primera forma es más explicita y la segunda permite al "recolector de basura" liberar la memoria correspondiente a un objeto "huerfano", es decir, sin variables que lo referencien.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)

del p1
p2 = None

Atributos intrínsecos

Las clases y los objetos en Python tienen atributos intrínsecos, es decir, atributos especiales que vienen de serie. Todos ellos comienzan y terminan por dos guiones bajos que indican su caracter especial.

Para las clases tenemos los siguientes:

  • __name__ Nombre de la clase.
  • __module__ Módulo o librería del que ha sido cargada.
  • __bases__ Colección de sus clases base (tiene que ver con la herencia).
  • __dict__ Un diccionario conteniendo todos sus métodos y atributos.
  • __doc__ El texto de documentación si lo tiene.

Para los objetos:

  • __class__ El nombre de la clase del objeto.
  • __dict__ Un diccionario conteniendo todos los atributos del objeto.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Persona:
    """ Clase para representar a una
        persona incluyendo su nombre y edad"""

    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def __str__(self):
        return 'Nombre: ' + self.nombre + '\nEdad: ' + str(self.edad)

    def cumplir_años(self):
        self.edad += 1
        print('Felicidades ' + self.nombre +
              ', ya tienes ' + str(self.edad) + ' años')

p = Persona('Alba', 11)

print('Atributos de clase')
print(Persona.__name__)
print(Persona.__module__)
print(Persona.__doc__)
print(Persona.__dict__)

print('Aributos de objeto')
print(p.__class__)
print(p.__dict__)

Resultado:

Atributos de clase
Persona
__main__
Clase para representar a una
    persona incluyendo su nombre y edad
{'__module__': '__main__', '__doc__': ' Clase para representar a una\n\t\tpersona incluyendo su nombre y edad', '__init__': <function Persona.__init__ at 0x000001E788B05A60>, '__str__': <function Persona.__str__ at 0x000001E788B05AF0>, 'cumplir_años': <function Persona.cumplir_años at 0x000001E788B05B80>, '__dict__': <attribute '__dict__' of 'Persona' objects>, '__weakref__': <attribute '__weakref__' of 'Persona' objects>}
Aributos de objeto
<class '__main__.Persona'>
{'nombre': 'Alba', 'edad': 11}

Atributos de clase

Las clases también pueden tener atributos (además de los objetos). A estos atributos se les llama atributos de clase o variables de clase.

Aquellas variables definidas dentro de una clase pero fuera de cualquiera de sus métodos quedarán vinculadas con la clase (en lugar de con cualquiera de sus instancias).

Un uso posible de este tipo de variables es, por ejemplo, el recuento de cuantos objetos se han creado de esa clase. Veamos un ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Persona:

    contador = 0

    def __init__(self, nombre, edad):
        Persona.contador += 1
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)
print(Persona.contador)

Resultado:

2

En el ejemplo se define una variable de clase denominada contador. En el constructor, cada vez que se crea una persona se suma 1 a dicha variable. La variable no está vinculada a ningún objeto persona en particular, es común a todos ellos, esta definida a nivel de la clase Persona. Es por ello que para acceder a ella es necesario escribir el nombre de la clase, un punto y el nombre de la variable.

Métodos de clase

También es posible definir métodos vinculados a una clase, en lugar de a un objeto particular. Estos métodos se definen como cualquier otro método de instancia, pero se "decoran" con @classmethod justo antes de su definición.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Persona:

    contador = 0

    @classmethod
    def incrementar_contador_personas(cls):
        cls.contador += 1

    def __init__(self, nombre, edad):
        Persona.incrementar_contador_personas()
        self.nombre = nombre
        self.edad = edad

p1 = Persona('Alba', 11)
p2 = Persona('Miguel', 46)
print(Persona.contador)

Resultado:

2

En el ejemplo creamos ahora un método de clase que se encargue de aumentar el contador cada vez que se cree un nuevo objeto persona. Vemos el decorador @classmethod justo antes del método. También vemos como el método recibe un parámetro llamado cls. Para este parámetro no es necesario suministrar ningún argumento cuando invoquemos al método ya que Python se encarga de colocar en él, el objeto que representa a la propia clase.

Para invocar un método de clase escribimos el nombre de la clase, un punto y el nombre del método suministrando los argumentos necesarios (ninguno en este ejemplo).

Métodos estáticos

En Python, los métodos estáticos no están vinculados a ningún objeto ni a la propia clase. No reciben un primer argumento representando a la clase (cls en los métodos de clase) ni un primer argumento representando al objeto (self en los métodos de instancia).

En realidad son como otras funciones individuales que únicamente se han agrupado por conveniencia.

Para definir métodos estáticos hemos de incluir un método en una clase y decorarlo con @staticmethod justo antes de comenzar su definición.

1
2
3
4
5
6
7
class Calculo(object):

    @staticmethod
    def cubo(n):
        print(n * n * n)

Calculo.cubo(4)

Resultado:

64

Herencia

La herencia permite a una clase heredar el comportamiento de otra para extenderlo.

Para crear una clase que herede de otra utilizaremos la siguiente sintaxis:

1
2
class clase_hija(clase_padre):
    cuerpo de la clase

Se indica cual es la clase padre entre paréntesis justo después del nombre de la clase hija.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Persona:

    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

    def cumplir_años(self):
        self.edad += 1
        print('Felicidades ' + self.nombre +
              ', ya tienes ' + str(self.edad) + ' años')

class Empleado(Persona):

    def __init__(self, nombre, edad, dni):
        super().__init__(nombre, edad)
        self.dni = dni

    def calcular_salario(self, horas):
        sueldo_hora = 25
        if self.edad >= 40:
            sueldo_hora += 5
        return horas * sueldo_hora

class Comercial(Empleado):

    def __init__(self, nombre, edad, dni, region, ventas):
        super().__init__(nombre, edad, dni)
        self.region = region
        self.ventas = ventas

    def calcular_prima(self):
        return self.ventas * 0.25

print('Empleado')
e = Empleado('Leo', 42, 21320187)
print(f'Salario: {e.calcular_salario(1760):.2f}')
print('-' * 20)
print('Comercial')
c = Comercial('Jess', 25, 20321899, 'Norte', 230000)
print(f'Salario: {c.calcular_salario(1150):.2f}')
print(f'Prima: {c.calcular_prima():.2f}')

Resultado:

Empleado
Salario: 52800.00
--------------------
Comercial
Salario: 28750.00
Prima: 57500.00

La clase object

Todos los objetos en Python heredan de la clase object.

El siguiente código que define la clase Persona:

1
2
3
4
class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

es equivalente a haber declarado la herencia de object explicitamente:

1
2
3
4
class Persona(object):
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad

Al heredar de la clase object todos los objetos tienen un conjunto de atributos intrínsecos y métodos especiales que proporciona dicha clase.

Entre los métodos podemos destacar:

  • str() Representación string
  • init() Método Constructor
  • eq() Igualdad
  • hash() Método Hash

Entre los atributos:

  • class Nombre del la clase del objeto
  • dict Diccionario con los atributos del objeto
  • doc El texto de documentación
  • module Modulo o librería al que pertenece