curso
Encapsulación en Python: Guía completa
La encapsulación es un principio fundamental de la orientación a objetos en Python. Protege tus clases de cambios o eliminaciones accidentales y favorece la reutilización y mantenimiento del código. Considera esta sencilla definición de clase:
class Smartphone:
def __init__(self, brand, os):
self.brand = brand
self.os = os
iphone = Smartphone("Apple", "iOS 17")
Muchos programadores de Python definen clases como ésta. Sin embargo, está lejos de las mejores prácticas que siguen los Pythonistas profesionales. El problema de esta clase se hace evidente cuando intentas modificar sus datos:
iphone.os = "Android"
print(iphone.os)
Android
Imagínate un iPhone funcionando con Android, ¡qué barbaridad! Evidentemente, tenemos que establecer algunos límites dentro de nuestra clase para que los usuarios no puedan cambiar sus atributos a lo que quieran.
Si deciden cambiarlas, los cambios deben ser bajo nuestras condiciones, siguiendo nuestras normas. Pero seguimos queriendo que los atributos .os
o .brand
sigan siendo los mismos en la superficie.
Todas estas cosas pueden promoverse utilizando la encapsulación y en este tutorial aprenderemos todo sobre ella. ¡Empecemos!
¿Cómo se consigue la encapsulación en Python?
Python apoya la encapsulación mediante convenciones y prácticas de programación, en lugar de modificadores de acceso forzados. Verás, el principio fundamental que subyace en gran parte del diseño del código Python es "Aquí todos somos adultos". Sólo podemos implementar la encapsulación como una mera convención y esperar que otros desarrolladores de Python confíen en nuestro código y lo respeten.
En otros lenguajes de programación orientada a objetos, como Java y C++, la encapsulación se aplica estrictamente con modificadores de acceso como public
, private
o protected
, pero Python no los tiene:
Función | Python | Java | C++ |
---|---|---|---|
Modificadores de acceso | No hay modificadores obligatorios; utiliza convenciones de nomenclatura (_protected , __private ) |
Ejecutado con public , protected , private keywords |
Ejecutado con public , protected , private keywords |
Métodos Getter/Setter | Opcional, a menudo se utiliza con @property decorador para acceso controlado |
Práctica común, normalmente implementada como métodos | Práctica común, normalmente implementada como métodos |
Acceso a los datos | Accesible mediante convenciones de nomenclatura; depende de la disciplina del desarrollador | Controlados por modificadores de acceso; impuestos por el compilador | Controlados por modificadores de acceso; impuestos por el compilador |
Filosofía | "Aquí todos somos adultos" - se basa en convenciones y confianza | Aplicación estricta del control de acceso | Aplicación estricta del control de acceso |
Así pues, la mayoría, si no todas, las técnicas de encapsulación que voy a mostrarte son convenciones de Python. Pueden romperse fácilmente si así lo decides. Pero confío en que las respetes y las sigas en tus propios proyectos de desarrollo.
Modificadores de acceso en Python
Supongamos que tenemos esta sencilla clase:
class Tree:
def __init__(self, height):
self.height = height
pine = Tree(20)
print(pine.height)
20
Tiene un único atributo de altura que podemos imprimir. El problema es que también podemos cambiarlo por lo que queramos:
pine.height = 50
pine.height
50
pine.height = "Grandma"
pine.height
'Grandma'
Entonces, ¿cómo decimos a los usuarios que está prohibido cambiar la altura? Pues bien, podríamos convertirlo en un miembro protegido añadiendo un único guión bajo precedente:
class Tree:
def __init__(self, height):
self._height = height
pine = Tree(20)
pine._height
20
Ahora bien, las personas que conozcan esta convención sabrán que sólo pueden acceder al atributo y que les desaconsejamos encarecidamente que lo utilicen y lo modifiquen. Pero si quieren, pueden modificarlo, oh sí.
Entonces, ¿cómo podemos evitarlo también? Utilizando otra convención: convierte el atributo en un miembro privado añadiendo dos guiones bajos precedentes:
class Tree:
def __init__(self, height):
self.__height = height
pine = Tree(20)
pine.__height
AttributeError: 'Tree' object has no attribute '__height'
Ahora, Python lanzará un error si alguien intenta acceder al atributo, y mucho menos modificarlo.
Pero, ¿te has dado cuenta de lo que acabamos de hacer? Sólo ocultamos a los usuarios la información relacionada con nuestros objetos. Nuestra clase acaba de volverse inútil porque no tiene atributos públicos.
Entonces, ¿cómo exponemos la altura de los árboles a los usuarios, pero controlando cómo se accede a ellos y cómo se modifican? Por ejemplo, queremos que las alturas de los árboles estén dentro de un rango concreto y sólo tengan valores enteros. ¿Cómo lo hacemos cumplir?
Llegados a este punto, puede que tu amigo que utiliza Java intervenga y te sugiera utilizar métodos getter y setter. Así que, probemos eso primero:
class Tree:
def __init__(self, height):
self.__height = height
def get_height(self):
return self.__height
def set_height(self, new_height):
if not isinstance(new_height, int):
raise TypeError("Tree height must be an integer")
if 0 < new_height <= 40:
self.__height = new_height
else:
raise ValueError("Invalid height for a pine tree")
pine = Tree(20)
pine.get_height()
20
De este modo, creas un atributo privado __height
pero permites que los usuarios accedan a él y lo modifiquen de forma controlada mediante los métodos get_height
y set_height
.
pine.set_height(25)
pine.get_height()
25
Antes de fijar un nuevo valor, set_height
se asegura de que la nueva altura está dentro de un rango determinado y numérico.
pine.set_height("Password")
TypeError: Tree height must be an integer
Pero estos métodos parecen exagerados para una operación sencilla. Además, es feo escribir código así:
# Increase height by 5
pine.set_height(pine.get_height() + 5)
¿No sería más bonito y legible si pudiéramos escribir este código?
pine.height += 5
y seguir aplicando el tipo de datos y el rango correctos para la altura? La respuesta es sí y aprenderemos a hacerlo en la siguiente sección.
Uso del decorador @propiedad en clases Python
Introducimos una nueva técnica: crear propiedades para los atributos:
class Tree:
def __init__(self, height):
# First, create a private or protected attribute
self.__height = height
@property
def height(self):
return self.__height
pine = Tree(17)
pine.height
17
Queremos que los usuarios accedan a un atributo oculto llamado __height
como si fuera un atributo normal llamado height
. Para conseguirlo, definimos un método llamado height
que devuelve self.__height
y lo decoramos con @property
.
Ahora podemos llamar a height
y acceder al atributo privado:
pine.height
17
Pero lo mejor es que los usuarios no pueden modificarlo:
pine.height = 15
AttributeError: can't set attribute 'height'
Así que añadimos otro método llamado height(self, new_height)
que está envuelto por un decorador height.setter
. Dentro de este método, implementamos la lógica que impone el tipo de datos y el rango deseados para la altura:
class Tree:
def __init__(self, height):
self.__height = height
@property
def height(self):
return self.__height
@height.setter
def height(self, new_height):
if not isinstance(new_height, int):
raise TypeError("Tree height must be an integer")
if 0 < new_height <= 40:
self.__height = new_height
else:
raise ValueError("Invalid height for a pine tree")
Ahora, cuando un usuario intenta modificar el atributo height
, se llama a @height.setter
, asegurándose así de que se pasa el valor correcto:
pine = Tree(10)
pine.height = 33 # Calling @height.setter
pine.height = 45 # An error is raised
ValueError: Invalid height for a pine tree
También podemos personalizar cómo se accede al atributo height
mediante la anotación por puntos con @height.getter
:
class Tree:
def __init__(self, height):
self.__height = height
@property
def height(self):
return self.__height
@height.getter
def height(self):
# You can return a custom version of height
return f"This tree is {self.__height} meters"
pine = Tree(33)
pine.height
'This tree is 33 meters'
Aunque creamos pine
con una altura entera, podríamos modificar su valor con @height.getter
.
Estos eran ejemplos de cómo podíamos promover la encapsulación en una clase Python. Recuerda que la encapsulación sigue siendo una convención, porque aún podemos romper el miembro privado interno de __height
:
pine._Tree__height = "Gotcha!"
pine.height
'This tree is Gotcha! meters'
Todo en las clases Python es público, y también lo son los métodos privados. No se trata de un fallo de diseño, sino de un ejemplo del planteamiento "Aquí todos somos adultos".
Aquí tienes un resumen de los modificadores de acceso de Python que acabamos de revisar:
Modificador/Convenio | Descripción | Ejemplo |
---|---|---|
Público | Nivel de acceso por defecto; los atributos y métodos son accesibles desde fuera de la clase | self.attribute |
Protegido | Indicado con un solo guión bajo; una convención para denotar un acceso parcialmente restringido | self._attribute |
Privado | Indicado con doble guión bajo; la deformación del nombre proporciona una restricción de acceso limitada | self.__attribute |
Propiedades | Proporciona acceso controlado a los atributos privados mediante la función @property decorator |
@property def attribute(self): |
Propiedades de sólo lectura | No @attribute.setter ; permite el acceso de sólo lectura a un atributo |
@property def attribute(self): |
Buenas prácticas al implementar la encapsulación
Hay una serie de buenas prácticas que puedes seguir para asegurarte de que tu código se alinea bien con el código escrito por otros OOPistas experimentados:
- Crea atributos o métodos protegidos o privados si sólo los utilizas tú. Los miembros protegidos o privados quedan excluidos de la documentación y señalan a los demás que pueden ser modificados por ti, el desarrollador, sin previo aviso, lo que les disuade de utilizarlos.
- No siempre tienes que crear propiedades para cada atributo de la clase. Para clases grandes con muchos atributos, escribir los métodos
attr.getter
yattr.setter
puede convertirse en un quebradero de cabeza. - Considera la posibilidad de lanzar un aviso cada vez que un usuario acceda a un miembro protegido (
_
). - Utiliza los miembros privados con moderación, ya que pueden hacer que el código resulte ilegible para quienes no estén familiarizados con la convención.
- Prioriza la claridad sobre la oscuridad. Como la encapsulación pretende mejorar la mantenibilidad del código y la protección de los datos, no ocultes completamente los detalles importantes de implementación de tu clase.
- Si quieres crear propiedades de sólo lectura, no implementes el método
@attr.setter
. Los usuarios podrán acceder a la propiedad, pero no modificarla. - Recuerda siempre que la encapsulación es una convención, no un aspecto obligatorio de la sintaxis de Python.
- Para clases sencillas, considera el uso de clases de datos, que te permiten activar la encapsulación de clases con una sola línea de código. Sin embargo, las clases de datos son para clases más sencillas con atributos y métodos predecibles. Consulta este tutorial sobre clases de datos para saber más.
Conclusión y otros recursos
En este tutorial, hemos aprendido uno de los pilares básicos de la programación orientada a objetos en Python: la encapsulación.
La encapsulación te permite definir el acceso controlado a los datos almacenados dentro de los objetos de tu clase. Esto te permite escribir un código limpio, legible y eficaz, y evitar cambios o borrados accidentales de los datos de tus clases.
Aquí tienes más recursos relacionados para mejorar tus conocimientos de programación orientada a objetos:
Aprende Python desde cero
Preguntas frecuentes
¿En qué se diferencia la encapsulación de la abstracción en Python?
La encapsulación consiste en agrupar los datos y los métodos que operan sobre los datos dentro de una unidad, como una clase, y restringir el acceso a algunos componentes. La abstracción, en cambio, consiste en ocultar la realidad compleja exponiendo sólo las partes esenciales. Mientras que la encapsulación se centra en restringir el acceso, la abstracción se centra en simplificar las interacciones.
¿Se puede utilizar la encapsulación en la programación funcional, o es exclusiva de la programación orientada a objetos?
La encapsulación es un concepto estrechamente ligado a la programación orientada a objetos (POO) y no suele utilizarse en la programación funcional. La programación funcional hace hincapié en la inmutabilidad y en las funciones sin estado, a diferencia de la POO, que se centra en los objetos y en encapsular los datos dentro de esos objetos.
¿Cuáles son algunos errores comunes que hay que evitar al implementar la encapsulación en Python?
Entre los errores más comunes están el uso excesivo de variables privadas, que puede dificultar el mantenimiento del código, y no proporcionar métodos de acceso adecuados (getters/setters), lo que puede dar lugar a un código frágil. Además, los desarrolladores pueden olvidar que la encapsulación de Python es por convención y, por tanto, se puede eludir.
¿Cómo puede la encapsulación mejorar la seguridad del código?
La encapsulación puede mejorar la seguridad del código restringiendo el acceso al estado interno de un objeto e impidiendo modificaciones no autorizadas. Controlando cómo se accede a los datos y cómo se modifican, los desarrolladores pueden garantizar que los datos siguen siendo válidos y coherentes.
¿Cómo afecta el uso del decorador @propiedad al rendimiento en Python?
Utilizar el decorador@property
puede introducir una ligera sobrecarga debido a las llamadas adicionales al método, pero en la mayoría de los casos, este impacto es insignificante. Las ventajas de mejorar la legibilidad y la capacidad de mantenimiento del código a menudo superan cualquier pequeño coste de rendimiento.
¿Es posible aplicar la encapsulación estrictamente en Python como en Java o C++?
Python no aplica la encapsulación de forma estricta como Java o C++. Su filosofía se basa en las convenciones y en el entendimiento de que los promotores respetarán el uso previsto de los miembros privados y protegidos. Sin embargo, utilizar convenciones de nomenclatura como los guiones bajos puede desalentar el uso indebido.
¿Cuáles son algunas aplicaciones reales de la encapsulación en proyectos Python?
La encapsulación se utiliza para proteger datos sensibles en aplicaciones como el software bancario, donde se ocultan los datos de las cuentas. También se utiliza en bibliotecas y marcos de trabajo para ocultar detalles de implementación complejos, permitiendo a los desarrolladores interactuar con interfaces sencillas.
¿Cómo puedo probar eficazmente el código encapsulado en Python?
Probar el código encapsulado implica escribir pruebas unitarias para la interfaz pública de una clase. Al probar los métodos y propiedades expuestos al usuario, te aseguras de que los cambios de estado internos se gestionan correctamente y de que la lógica de encapsulación se mantiene.
¿Se puede aplicar la encapsulación a los módulos de Python, o se limita a las clases?
Aunque la encapsulación se asocia principalmente a las clases, el concepto puede extenderse a los módulos utilizando funciones o variables prefijadas con guiones bajos para señalar el uso privado dentro de un módulo.
¿Cómo se relaciona la encapsulación con el polimorfismo en Python?
Tanto la encapsulación como el polimorfismo son principios de la programación orientada a objetos, pero tienen finalidades distintas. La encapsulación restringe el acceso a determinadas partes de un objeto, mientras que el polimorfismo permite tratar los objetos como instancias de su clase padre, lo que facilita la ampliación y el mantenimiento del código. Pueden utilizarse juntos para crear estructuras de código flexibles y seguras.
Soy un creador de contenidos de ciencia de datos con más de 2 años de experiencia y uno de los mayores seguidores en Medium. Me gusta escribir artículos detallados sobre IA y ML con un estilo un poco sarcastıc, porque hay que hacer algo para que sean un poco menos aburridos. He publicado más de 130 artículos y un curso DataCamp, y estoy preparando otro. Mi contenido ha sido visto por más de 5 millones de ojos, 20.000 de los cuales se convirtieron en seguidores tanto en Medium como en LinkedIn.
¡Continúa hoy tu viaje en Python!
programa
Programación en Python
curso