POO: Encapsulamiento en Python

Publicado por Andrea Navarro el

En este artículo veremos el concepto de encapsulamiento en programación orientada a objetos, específicamente encapsulamiento en Python. Veremos los diferentes tipos de atributos y los niveles de encapsulamiento.

Encapsulamiento en python

El encapsulamiento es una técnica en POO que restringe el acceso directo a los atributos internos de un objeto. Esto significa que si un atributo se encuentra encapsulado no será posible asignarle un valor o obtener el valor actual de manera directa a partir del objeto con la forma objeto.atributo . Estas funcionalidades deberán realizarse a través de la creación y uso de métodos en el objeto.

Esta técnica tiene como objetivo mantener la coherencia, seguridad y control de los datos. Esto se logra permitiendo definir que comportamientos deben tener lugar antes de acceder a los atributos del objeto.

El nivel de encapsulamiento de un atributo es dado por el nombre del mismo.

  • Si un atributo comienza con dos guiones bajos __ este es privado (también llamado propiedad), por lo que no podrá ser accedido por fuera de la clase.
  • Si un atributo comienza con un solo guion bajo _ se considera protegido, estas solo deberían ser accedidas dentro de la propia clase o de las clases heredades.
  • Finalmente si no contiene guiones al principio es un atributo publico y puede accederse desde cualquier lugar.
variablePúblicoAccesible desde cualquier lugar
_variableProtegido (convención)Indica uso interno, no debe accederse fuera de la clase o clases heredadas
__variablePrivadoNo se puede acceder directamente fuera de la clase
class EjemploClase:
	def __init__(self, atributoA, atributoB, atributoC):
		self.atributoA = atributoA # Público
		self._atributoB = atributoB # Protegido
		self.__atributoC = atributoC # Privado

Importante! A diferencia de otros lenguajes de programación, Python establece los atributos protegidos como una convención. Esto significa que no existen mecanismos reales dentro del lenguaje para prevenir que los atributos sean accedidos por fuera de la clase. Al crear un atributo cuyo nombre va precedido por un guion _ se está señalando un uso interno del mismo y respetar esta convención es parte de las buenas prácticas de programación.

Tipos de atributos

Dentro de la POO en Python pueden definirse tres tipos de atributos.

Los atributos de instancia son aquellos que se definen en el constructor de la clase y su valor solo afecta al objeto al que pertenece. Su valor puede ser asignado y obtenido accediendo desde el objeto como objeto.atributo .

class EjemploClase:
	def __init__(self, atributoA):
		self.atributoA = atributoA
		
ejemplo = EjemploClase("Valor1")
ejemplo2 = EjemploClase("Valor2")
print(ejemplo.atributoA)
print(ejemplo2.atributoA)

Los atributos de clase por otro lado se comparten con todas las instancias. Si el valor es modificado desde la clase, el cambio se refleja en el resto de los objetos existentes de la misma clase. Sus valores pueden ser accedido a través de un objeto o una clase.

class EjemploClase:
    atributo = "Valor 1" # Atributo de clase

ejemplo = EjemploClase()
ejemplo2 = EjemploClase()
EjemploClase.atributo = "Valor 2"
print(ejemplo.atributo)
print(ejemplo2.atributo)

En este caso los atributos de ambos objetos muestran el mismo valor ya que este se comparte entre las instancias y fue modificado desde la clase.

Valor 2
Valor 2

Importante! Modificar el valor del atributo de clase solo funciona si se hace desde la clase y no desde un objeto. Al modificar el valor del atributo desde un objeto lo que se está haciendo es crear una nueva variable de instancia y su valor, por lo tanto, no se verá reflejado en otras instancias.

class EjemploClase:
    atributo = "Valor 1" # Atributo de clase

ejemplo = EjemploClase()
ejemplo2 = EjemploClase()
ejemplo2.atributo = "Valor 2" #Se crea un atributo de instancia
print(ejemplo.atributo)
print(ejemplo2.atributo)

Al imprimir los valores del atributo solo se ha modificado el de el objeto ejemplo2

Valor 1
Valor 2

Finalmente las propiedades son atributos controlados o atributos privados que se comportan como atributos normales pero ejecutan código cuando se leen, asignan o eliminan, y no pueden ser accedidas directamente.
Para crear el método encargado de controlar el acceso al atributo se utiliza el decorador @property y se lo define con el mismo nombre que el atributo. Este tipo de métodos también se denomina getter.

Para crear el método encargado de controlar la modificación del atributo se utiliza un decorador que debe tener el nombre del mismo seguido por .setter. Este método puede utilizarse para realizar verificaciones y validaciones pero no debe retornar ningún valor. Si se omite la creación del setter la propiedad será de solo lectura.

class EjemploClase:
	def __init__(self, atributo):
		self.__atributo = atributo

	@property
	def atributo(self):
		print("Ejecutando getter")
		return self.__atributo

	@atributo.setter
	def atributo(self, atributo):
		# Validaciones
		print("Ejecutando setter")
		self.__atributo = atributo
		
ejemplo = EjemploClase("Valor")
print(ejemplo.atributo) #Se accede con el nombre del atributo sin guiones
ejemplo.atributo = "Valor 2" #Se accede con el nombre del atributo sin guiones 

Cuando se intenta acceder al atributo privado tanto para leerlo como para modificarlo en lugar de hacerse de manera directa se ejecutan las funciones correspondientes.

Ejecutando getter
Valor
Ejecutando setter

El método getter, es decir, la definición del atributo como propiedad, debe hacerse siempre antes que la definición del setter en caso de que exista. Aunque internamente se haga referencia al atributo privado con su nombre original precedido por los dos guiones __, al crear las funciones getter y setter estos se omiten, como así también al hacer referencia al atributo desde el objeto.

¿Cuándo es necesario el encapsulamiento?

El encapsulamiento puede ser de utilidad en diversos escenarios durante el desarrollo de una aplicación.

Cuando el valor de una variable debe seguir ciertas reglas el encapsulamiento puede utilizarse para validar datos antes de asignarlos.

class Persona:
    def __init__(self, edad):
        self._edad = edad

    @property
    def edad(self):
        return self._edad

    @edad.setter
    def edad(self, valor):
        if valor < 0:
            raise ValueError("Edad no puede ser negativa")
        self._edad = valor

De manera similar es posible utilizar el setter para convertir o restringir tipos de datos antes de asignarlos. En este caso de modifica el valor del precio para que sea flotante.

class Producto:
    def __init__(self, precio):
        self._precio = float(precio)

    @property
    def precio(self):
        return self._precio

    @precio.setter
    def precio(self, valor):
        self._precio = float(valor)

Crear getter pero no setter es la manera de crear atributos solo lectura que puedan ser accedidos pero nunca modificados.

class Usuario:
    def __init__(self, id_usuario):
        self._id_usuario = id_usuario

    @property
    def id_usuario(self):
        return self._id_usuario

Cuando un valor es el resultado de realizar cálculos sobre otro atributos de ese objeto no es necesario su almacenamiento sino que puede calcularse como parte del getter del atributo.

class Rectangulo:
    def __init__(self, ancho, alto):
        self._ancho = ancho
        self._alto = alto

    @property
    def area(self):
        return self._ancho * self._alto

r = Rectangulo(4, 5)
print(r.area)

Finalmente, el encapsulamiento es utilizado cuando se requieren acciones adicionales antes de asignar un valor. Estas acciones pueden incluir actualizar estados internos relacionados, generar registros de seguimiento o activar procesos dependientes del nuevo valor.

class Cuenta:
    def __init__(self, saldo):
        self._saldo = saldo

    @property
    def saldo(self):
        return self._saldo

    @saldo.setter
    def saldo(self, valor):
        print("Actualizando saldo…")
        self._saldo = valor

En este artículo hemos visto como funciona el encapsulamiento en la programación orientada a objetos en Python, los tipos de atributos y los problemas más comunes para tener en cuenta a la hora de encapsular los atributos en nuestras clases.
Espero que les sirva!


¿Querés aprender más? 📚

👉 Visitá nuestros cursos!
💬 Y si tenés dudas, o querés dejarnos tus comentarios sumate a la Comunidad JuncoTIC en Telegram!
¡Te esperamos!


Andrea Navarro

- Ingeniera en Informática - Docente universitaria - Investigadora