En este notebook daremos una introducción general a la programación orientada a objetos en Python. Todo es referente a Python3 (la gran parte compatible con las versiones más recientes de Python2).
Las principales ventajas de la POO son la encapsulación y la reutilización.
La clase mínima en Python es:
In [1]:
class Atom():
pass
La forma habitual de crear una clase (tipo) es crear un objeto (variable) de ella:
In [2]:
atom = Atom()
En este ejemplo, la clase no recibe ningún tipo de configuración (parámetros). Lo habitual es que los objetos tengan unos parámetros que los definen y son definidos al crear el objeto. Por ejemplo, si queremos crear un átomo de Carbono, que tiene 6 electrónes y 6 protones.
In [3]:
class Atom():
def __init__(self, electrons, protons):
self.electrons = electrons
self.protons = protons
carbon = Atom(electrons = 6, protons = 6)
Para poder configurar la clase en la creación de objetos, necesitamos el método de inicialización de clase "init". Este método, al igual que el esto de métodos de una clase, recibe como primer parámetro "self". Esta variable la gestiona de forma automática Python, y es una referencia al objeto que se ha creado.
Para añadir funcionalidad a una clase la forma de hacerlo es mediante métodos (funciones) de clase. Estos métodos tienen como diferencia con las funciones habituales de Python que reciben como primer parámetro "self".
Vamos a añadir dos nuevos métodos para consultar el número de electrones y de protones de un átomo.
In [4]:
class Atom():
def __init__(self, electrons, protons):
self.electrons = electrons
self.protons = protons
def get_electrons(self):
return self.electrons
def get_protons(self):
return self.protons
carbon = Atom(electrons = 6, protons = 6)
print("The carbon atom has %i electrons" % carbon.get_electrons())
print("The carbon atom has %i protons" % carbon.get_electrons())
Las clases se puede crear a partir de otras clases base. Por ejemplo, vamos a crear una clase específica para representar a los átomos de Carbon.
In [5]:
class Carbon(Atom):
def __init__(self):
self.electrons = 6
self.protons = 6
carbon = Carbon()
print("The carbon atom has %i electrons" % carbon.get_electrons())
print("The carbon atom has %i protons" % carbon.get_electrons())
La nueva clase Carbon es una especialización de la clase general (base) Atom. Ya sabe el número de electrones y protones, por lo que no hay que indicarlos. Y puede utilizar toda la funciolidad que ofrece la clase base.
La clase Carbon puede redefinir como se comparta la clase base allá donde lo necesite. En el ejemplo anterior, redefine la inicialización para poner el número de electrónes y protones, pero reutiliza el resto de métodos de Atom (get_electrons y get_protons).
La clase Carbon además puede añadir nuevos métodos para implementar funcionalidad específica del átomo de Carbon.
El átomo de Carbon tiene hasta 11 isotopos pero el más habitual es el Carbono12, que tiene 6 neutrones en el núcleo.
Podemos crear una nueva clase Carbon12 para modelar a este isotopo.
In [6]:
class Carbon12(Carbon):
def __init__(self):
self.electrons = 6
self.protons = 6
self.neutrons = 6
En general, todos los átomos tienen neutrones en eĺ núcleo, por lo que deberíamos de incluir como parámetro de Atom el número de neutrones, y un método para obtenerlos. Y en vez de crear una nueva clase por casa isótopo, esta información puede estar directamente en la clase Carbon, relejada en el número de neutrones. Con eso quedaría:
In [8]:
class Atom():
def __init__(self, electrons, protons, neutrons):
self.electrons = electrons
self.protons = protons
self.neutrons = neutrons
def get_electrons(self):
return self.electrons
def get_protons(self):
return self.protons
def get_neutrons(self):
return self.neutrons
class Carbon(Atom):
def __init__(self, neutrons=6):
self.electrons = 6
self.protons = 6
self.neutrons = neutrons
Ahora cuando creamos por defecto un átomo de carbón por defecto se crea con 6 neutrones, es decir, es el Carbon12. Pero si queremos otro isótopo de carbón, basta con indicarlo al construir el objeto carbón. Por ejemplo:
In [9]:
carbon13 = Carbon(neutrons=7)
print(carbon13.get_neutrons())
En realidad, analizando la clase Carbon, el número de electrones y de protones siempre es el mismo, independientemente del isótopo de carbón que sea, de la instanacia (objeto) de carbón que sea. Es decir, que esos datos no son específicos de los objetos, sino que están asociados a la clase y son los mismos en todos los objetos.
A estas variables asociadas a la clase se las llama variables estáticas. Y se declaran en el ámbito de clase, es decir:
In [14]:
class Atom():
electrons = None
protons = None
def __init__(self, neutrons):
self.neutrons = neutrons
@classmethod
def get_electrons(cls):
print(cls)
return cls.electrons
@classmethod
def get_protons(cls):
return cls.protons
def get_neutrons(self):
return self.neutrons
class Carbon(Atom):
electrons = 6
protons = 6
def __init__(self, neutrons=6):
self.neutrons = neutrons
In [16]:
carbon = Carbon()
print(Carbon.get_electrons())
print(carbon.get_electrons())
Python ayuda a que se puede llamar directamente a los métodos estáticos utilizando la instancia del objeto: de forma automática transforma la instancia del objeto en la clase a la que pertenece.
In [22]:
class Atom():
electrons = None
protons = None
def __init__(self, neutrons=None):
self.neutrons = neutrons
self.__atom_private()
@classmethod
def get_electrons(cls):
print(cls)
return cls.electrons
@classmethod
def get_protons(cls):
return cls.protons
def get_neutrons(self):
return self.neutrons
def __atom_private(self):
print("Atom private method")
class Carbon(Atom):
electrons = 6
protons = 6
def __init__(self, neutrons=6):
self.neutrons = neutrons
self.__atom_private()
atom = Atom()
carbon = Carbon()