Modelo de objetos
Todo es un objeto
1. Principios
a. Qué sentido dar a «objeto»
Python es un lenguaje que utiliza varios paradigmas y, entre ellos, el paradigma orientado a objetos. Este se elaboró durante los años 1970 y es, ante todo, un concepto. Un objeto representa:
-
un objeto físico:
-
parcela de terreno, bien inmueble, apartamento, propietario, inquilino...;
-
coche, piezas de un coche, conductor, pasajero...;
-
biblioteca, libro, página de un libro...;
-
dispositivo de hardware, robot...;
-
un objeto informático:
-
archivo (imagen, documento de texto, sonido, vídeo...);
-
servicio (servidor, cliente, sitio de Internet, servicio web...);
-
un flujo de datos, pool de conexiones...;
-
un concepto:
-
portador de alguna noción que pueda compartir;
-
secuenciador, ordenador, analizador de datos...
Uno de los principios es la encapsulación de datos. Esto significa que cada objeto posee en su seno no solo los datos que lo describen y que contiene (bajo la forma de atributos), sino también el conjunto de métodos necesarios para gestionar sus propios datos (modificación, actualización, compartición...).
El desarrollo orientado a objetos consiste, simplemente, en crear un conjunto de objetos que representa de la mejor forma posible aquello que modelan y en gestionar sus interacciones.
Cada funcionalidad se modela, de este modo, bajo la forma de interacciones entre objetos. De su correcto modelado y de la naturaleza de sus interacciones dependen la calidad del programa y también su estabilidad y mantenibilidad.
El paradigma orientado a objetos define, entonces, otros mecanismos para dar respuesta a las distintas problemáticas que se le plantean al desarrollador: polimorfismo, interfaces, herencia, sobrecarga de métodos, sobrecarga de operadores…
Es aquí donde se diferencian los lenguajes entre sí, pues cada uno propone soluciones que le son propias utilizando o no ciertos mecanismos del lenguaje orientado a objetos y de forma más o menos fiel a su espíritu.
b. Adaptación de la teoría de objetos en Python
En lenguajes como PHP, por ejemplo, se agrega una semántica de objetos que permite a los desarrolladores escribir de forma similar a un lenguaje orientado a objetos. Esto se realiza en dos etapas: la posibilidad de declarar clases (con interfaces y herencia simple) y la posibilidad de crear instancias de estas clases...
Otras herramientas de la programación orientada a objetos
1. Principios
En Python, los aspectos esenciales de la programación orientada a objetos se basan en la correcta declaración de las clases, en la flexibilidad del propio lenguaje, que permite acoplar las clases, las instancias, sus atributos y sus métodos tal y como se desee, y en otras cualidades desarrolladas en los dos capítulos anteriores.
Conocer lo expuesto en la sección Todo es un objeto nos permite escribir fácilmente componentes eficaces y arquitecturizarlos conforme a nuestras expectativas. Se trata de funcionalidades ligeras, no restrictivas, muy ágiles y suficientes para responder a todos los casos de uso.
Para los debutantes, esto es suficiente e incluso en muchos casos, para aquellos programadores más experimentados, raras son las veces en las que es necesario utilizar otros conceptos.
Pero Python es un lenguaje muy completo y permite ofrecer funcionalidades más complejas y más completas sin tener, por ello, que imponerlas y hacer su modelo de objetos restrictivo. La libertad que tiene el desarrollador para seleccionar la solución es una regla de su filosofía, pero libertad de elección no significa únicamente «no existen restricciones», significa también un panel de opciones importante y útil.
2. Interfaces
Ahora que sabemos escribir clases, organizarlas, gestionar sus atributos y métodos, es momento de hacerlas dialogar entre sí. Una de las problemáticas consiste en determinar con qué tipo de clase es posible interactuar.
Para ello, Python se fundamenta en el principio de «duck typing». Si funciona como un pato y anda como un pato, entonces será un pato. Dicho de otro modo, si un objeto posee los métodos necesarios, entonces este objeto debe ser el que esperamos.
>>> import csv
>>> csv.reader(file)
El objeto file es una clase que interactúa con reader, pero file no puede ser cualquier clase, del mismo modo que reader la utiliza de una forma determinada.
>>> class File:
... pass
...
>>> file = File()
>>> csv.reader(file)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument...
Funciones principales y primitivas asociadas
1. Personalización
a. Clases
Es posible personalizar las clases utilizando correctamente el método especial __new__ y las metaclases, que hemos visto antes en este capítulo. En este sentido, Python 3 ha mejorado bastante, simplificando y homogeneizando su comportamiento respecto a la versión anterior.
Este método especial es un método de clase (su primer argumento es la clase). Es este método el que crea una instancia de la clase en curso e invoca a su método __init__, que es un método de instancia. Los demás argumentos que se pasan a los dos métodos son idénticos.
La sobrecarga de __new__ permite, por tanto, personalizar la manera en que se crea la instancia, mientras que la sobrecarga de __init__ permite personalizar la propia instancia, colocando atributos, por ejemplo.
He aquí una demostración del orden en que se invocan los métodos:
>>> class A:
... def __new__(cls, info):
... print('A\tNew\t%s\t\t\t%s' % (cls, info))
... return object.__new__(cls, info)
... def __init__(self, info):
... print('A\tInit\t%s\t%s' % (self, info))
... return object.__init__(self, info)
...
>>> class B(A):
... def __new__(cls, info):
... print('B\tNew\t%s\t\t\t%s' % (cls, info))
... return A.__new__(cls, info)
... def __init__(self, info):
... print('B\tInit\t%s\t%s' % (self, info))
... return A.__init__(self, info)
...
He aquí el resultado de las llamadas:
>>> a = A('test 1')
A New <class '__main__.A'> test 1
A Init <__main__.A object at 0x261ea10> test 1
>>> b = B('test 2')
B New...