Propiedades – @property – Getter, Setter, Deleter – Encapsulamiento

Anteriormente sin haber hablado de encapsulamiento en python había publicado una entrada sobre propiedades en python. Pero he decidido partir desde el comienzo con este conceptos, así que aquí va de nuevo. Esta vez un poco más claro:

 

¿Qué es el encapsulamiento en Python?

propiedades y encapsulamiento en python

Más precisamente la pregunta que debemos hacernos es, ¿Qué es el encapsulamiento en la programación orientada a objetos?. ¿Cómo lo trabaja Python?

En mi caso vengo de estar leyendo sobre Java (he traicionado un poquillo) y en otros lenguajes como Java contamos al momento de declarar una variable dentro de una clase, como un atributo o método una palabra reservada para indicar si es privada (private) o publica (public). Esto en Python se debe hacer mediante el uso de guion bajo («_» , «__»).

Para empezar a explicar esto debemos entender el concepto de encapsulamiento y porque es utilizado en la programación orientada a objetos.

Para ello recurro a mi mejor amiga la Wikipedia, donde se define encapsulamiento:

En programación modular, y más específicamente en programación orientada a objetos, se denomina encapsulamiento al ocultamiento del estado, es decir, de los datos miembro de un objeto de manera que solo se pueda cambiar mediante las operaciones definidas para ese objeto.

Cada objeto está aislado del exterior, es un módulo natural, y la aplicación entera se reduce a un agregado o rompecabezas de objetos. El aislamiento protege a los datos asociados de un objeto contra su modificación por quien no tenga derecho a acceder a ellos, eliminando efectos secundarios e interacciones.

De esta forma el usuario de la clase puede obviar la implementación de los métodos y propiedades para concentrarse solo en cómo usarlos. Por otro lado se evita que el usuario pueda cambiar su estado de maneras imprevistas e incontroladas.

Como podrás leer se define como el «ocultamiento del estado» osea de «los datos miembros de un objeto». Hablamos de ocultar los datos de atributos o métodos, más específicamente de protegerlos para que solo puedan ser cambiados mediante «operaciones definidas» para ello. Esto nos asegura por ejemplo que «no podremos modificar un atributo si no es a través de un método que hallamos creado específicamente para ello» y aquí es donde nacen los famosos «Getter, Setter, Deleter» etceteres.

 

¿Pero qué es el ámbito «Protegido, privado»?

 

Anteriormente creamos clases con sus atributos y métodos públicos, es decir, que son accesibles desde su instancia. Basta con crear un objeto a partir de esa clase y podremos acceder y modificar libremente sus atributos. Y no solo desde su instancia sino también desde otra clase que herede de la anterior, por lo que un atributo que almacena información sensible puede ser modificado fácilmente.

CUIDADO
En python las propiedades y métodos privados no existen, por lo que son fácilmente sobre-escribibles.

Lo que sucede es que en otros lenguajes es común utilizar el encapsulamiento, pero en Python las propiedades y métodos privados no existen, por lo que son fácilmente sobreescribibles.

Entonces, ¿debo seguir leyendo?

Depende, si vienes de otro lenguaje como Java a programar a Python, seguro te habrás preguntado sobre «Encapsulamiento», pero sino, te recomiendo que no sigas leyendo a menos que sea para organizar tu código de esta manera por propia elección. Es decir que en realidad esto va de la decisión del desarrollador utilizarlo, pero no es obligatorio porque como ya dijimos en Python de todas maneras «lo privado no existe».  Y si a ti de  todas manera no te interesa aplicar este modelo, leer sobre encapsulamiento solo  te servirá en el caso que te encuentres con código de otro desarrollador que si lo aplique, lo que es bastante normal.

Habiendo aclarado esto podemos continuar.

 

Atributos protegidos en Python («_»)

A menudo podemos encontrarnos cuando andamos buscando ejemplos de algún código con algo como esto:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  encaps.py
#  
#  Copyright 2020 Pyr0 Maniac <Pyro@Pyro>

class usuario (object):
	def __init__(self, nombre, clave):
		self.nombre = nombre
		self._clave = clave
		
Usuario1 = usuario ("Roberto", "qwerty")

print (Usuario1.nombre, Usuario1._clave)

Resultado:

(‘Roberto’, ‘qwerty’)

Como ves el atributo clave esta precedido por un guión bajo, lo que indica que es un atributo protegido. Lo cual establece que solo puede ser accedido por esa clase y sus sub-clases, es decir, aquellas que hereden de la clase padre. Se suele ver muy a menudo como una buena practica para atributos o métodos de uso interno y también para evitar la colisión de los mismos nombres de métodos o atributos causado por herencia. Siempre y cuando estemos hablando de programas que recurren a muchas clases, es probable que los veas utilizar.

De lo contrario, resulta, en realidad, innecesario.

 

Atributos privados en Python («__»)

En el caso de un atributo privado estamos indicando que este solo podrá ser accedido o modificado si se especifica la clase precedida por un guión bajo seguida del atributo precedido por doble guión bajo.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  encaps.py
#  
#  Copyright 2020 Pyr0 Maniac <Pyro@Pyro>

class usuario (object):
	def __init__(self, nombre, clave):
		self.nombre = nombre
		self.__clave = clave
		
Usuario1 = usuario ("Roberto", "qwerty")

print (Usuario1.nombre, Usuario1.__clave)

Si hacemos esto obtendremos de salida que no existe el atributo clave que estamos intentando imprimir.

AttributeError: ‘usuario’ object has no attribute ‘__clave’

Puesto que como dije anteriormente la forma correcta de acceder a el sería especificando primero la clase a la que pertenece de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  encaps.py
#  
#  Copyright 2020 Pyr0 Maniac <Pyro@Pyro>

class usuario (object):
	def __init__(self, nombre, clave):
		self.nombre = nombre
		self.__clave = clave
		
Usuario1 = usuario ("Roberto", "qwerty")

print (Usuario1.nombre, Usuario1._usuario__clave)

Resultado:

(‘Roberto’, ‘qwerty’)

 

Como ves podemos acceder igualmente a un atributo por más que sea privado y modificarlo de la misma manera. Pero no es lo que se «considera correcto». Por lo que para ello si deseamos implementar métodos que nos permitan modificar estos atributos de la forma que se suele hacer en otros lenguajes donde se aplica «encapsulamiento» podemos hacerlo utilizando Getter, Setter, Deleter mediante el uso del decorador @Property.

 

 


Propiedades de atributos de clase en Python: Getter, Setter y Deleter

Siguiendo con las propiedades en python tratemos de entenderlo rápidamente.

Atención

En python dentro de las clases podríamos decir que todo son atributos, incluso los métodos podríamos definirlos como «atributos llamables» y las propiedades «atributos personalizables». Por ende ahora:

Las propiedades son atributos que «manejamos» mediante Getter, Setter y Deleter.

«Atributos manejables» que nos permiten invocar código personalizado al ser creados, modificados o eliminados.

Las propiedades nos permiten por ejemplo llamar código personalizado cuando un atributo, método, variable es mostrado/a, modificado/a, borrado/a.

O otro caso de ejemplo seria, automatizar la modificación de una variable «B» cuando se modifique «A» o guardar un historial de modificación de un atributo, entre otros muchos usos..

¿Habéis entendido alguna palabra o solo lié todo?

Pues tranquilo sigue leyendo que aquí es de donde sales entendiendo todo o sino cierro este blog y a otra cosa mariposa.. Si no lo entiendes aquí, yo habré fracasado!!

@Property en python

La función integrada property() nos permitirá interceptar la escritura, lectura, borrado de los atributos y ademas nos permiten incorporar una documentación sobre los mismos. La sintaxis para invocarla es la siguiente:

@property

Si, es un decorador.

Si nosotros no pasamos alguno de los parámetros su valor por defecto sera None.

Getter: Se encargará de interceptar la lectura del atributo. (get = obtener)

Setter : Se encarga de interceptar cuando se escriba. (set = definir o escribir)

Deleter : Se encarga de interceptar cuando es borrado. (delete = borrar)

doc :  Recibirá una cadena para documentar el atributo. (doc = documentación)

Ahora te explico esto con un código sencillo y fácil de entender..:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net

class Perros(object): #Declaramos la clase principal Perros
	def __init__(self, nombre, peso): #Definimos los parámetros 
		self.__nombre = nombre #Declaramos los atributos (privados ocultos)
		self.__peso = peso
	@property
	def nombre(self): #Definimos el método para obtener el nombre
		"Documentación del método nombre bla bla" # Doc del método
		return self.__nombre #Aquí simplemente estamos retornando el atributo privado oculto



#Hasta aquí definimos los métodos para obtener los atributos ocultos o privados getter.
#Ahora vamos a utilizar setter y deleter para modificarlos

	@nombre.setter #Propiedad SETTER
	def nombre(self, nuevo):
		print ("Modificando nombre..")
		self.__nombre = nuevo
		print ("El nombre se ha modificado por")
		print (self.__nombre) #Aquí vuelvo a pedir que retorne el atributo para confirmar
	@nombre.deleter #Propiedad DELETER
	def nombre(self): 
		print("Borrando nombre..")
		del self.__nombre
		
		#Hasta aquí property#

	def peso(self):	#Definimos el método para obtener el peso
		return self.__peso #Aquí simplemente estamos retornando el atributo privado

#Instanciamos
Tomas = Perros('Tom', 27)
print (Tomas.nombre) #Imprimimos el nombre de Tomas. Se hace a través de getter
#Que en este caso como esta luego de property lo toma como el primer método..

Tomas.nombre = 'Tomasito' #Cambiamos el atributo nombre que se hace a través de setter
del Tomas.nombre #Borramos el nombre utilizando deleter

Resultado:

Tom
Modificando nombre..
El nombre se ha modificado por
Tomasito

Borrando nombre..

 

Se define primero property y luego de ella el método mediante el cual retornamos el nombre (get) que en este caso al ser el primer método luego de property lo toma automáticamente como Getter (linea 10). Luego especificamos el (set) que nos permite lanzar un print al modificar el atributo privado nombre. Y luego el (deleter) que nos permite lanzar otro print al borrarlo.

Y eso es todo, sencillo y sin más vueltas.

En este ejemplo anterior las propiedades no aplican al atributo peso. Cuando llamemos, modifiquemos o eliminemos el atributo privado oculto «__nombre» se aplicaran dichas modificaciones según la acción que se realiza con el. ¿Me explico?. Las propiedades te permiten variar el resultado según la acción que se realiza con el atributo, si lo modificas sucede algo. Pero si lo borras sucede otra cosa.

 

Recordamos que luego de property debes especificar el nombre del método seguido del punto y la propiedad de la que se trate(setter, getter, deleter).

Ahora vamos a agregar el peso, permitirnos modificarlo u borrarlo. En ese caso no podemos colocarlo dentro del mismo, se debe crear otro decorador para dicho método.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net

class Perros(object): #Declaramos la clase principal Perros
	def __init__(self, nombre, peso): #Definimos los parámetros 
		self.__nombre = nombre #Declaramos los atributos
		self.__peso = peso
	@property
	def nombre(self): #Definimos el método para obtener el nombre
		"Documentación del método nombre bla bla" # Doc del método
		return self.__nombre #Aquí simplemente estamos retornando el atributo privado


#Hasta aquí definimos los métodos para obtener los atributos ocultos o privados getter.
#Ahora vamos a utilizar setter y deleter para modificarlos

	@nombre.setter #Propiedad SETTER
	def nombre(self, nuevo):
		print ("Modificando nombre..")
		self.__nombre = nuevo
		print ("El nombre se ha modificado por")
		print (self.__nombre) #Aquí vuelvo a pedir que retorne el atributo para confirmar
	@nombre.deleter #Propiedad DELETER
	def nombre(self): 
		print("Borrando nombre..")
		del self.__nombre
		
	@property
	def peso(self):	#Definimos el método para obtener el peso #Automáticamente GETTER
		return self.__peso #Aquí simplemente estamos retornando el atributo privado

	@peso.setter
	def peso(self, nuevopeso):
		self.__peso = nuevopeso
		print ("El peso ahora es")
		print (self.__peso)
	@peso.deleter #Propiedad DELETER
	def peso(self): 
		print("Borrando peso..")
		del self.__peso
#Instanciamos
Tomas = Perros('Tom', 27)
print (Tomas.nombre) #Imprimimos el nombre de Tomas. Se hace a través de getter
#Que en este caso como esta luego de property lo toma como el primer método..

Tomas.nombre = 'Tomasito' #Cambiamos el atributo nombre que se hace a través de setter
print (Tomas.nombre) #Volvemos a imprimir
Tomas.peso = 28
del Tomas.nombre #Borramos el nombre utilizando deleter

Resultado

Tom
Modificando nombre..
El nombre se ha modificado por
Tomasito
El peso ahora es
28
Borrando nombre..

 

 

Descansando en la nube: login offEn la siguiente entrada vamos a ver algunos ejemplos mas prácticos del uso de clases completo. Por ahora solo te dejo este ejemplo sencillo para que comprendas el funcionamiento de las propiedades en python 3. Y comiences a aplicarlo a partir de ahora SI ES QUE LO DESEAS, recuerda NO ES OBLIGATORIO. Hasta la vista!!

 

 

 

Compartir es agradecer! :)

11 comentarios en “Propiedades – @property – Getter, Setter, Deleter – Encapsulamiento”

    1. Mala mía! Esto va de la mano con concepto de encapsulamiento. (Fíjate en conceptos de programación orientada a objetos; pero en Python el encapsulamiento en realidad no existe; se simula y en este caso utilizamos Property para modificar los datos mediante métodos get;set;del.). Pero si necesita una explicación más detallada; faltan desarrollar conceptos aún en el blog y corregir algunas cosillas (espero pronto contar con el tiempo necesario). Un saludo!

  1. Gracias por el articulo y los ejemplos, ha sido clarificador.
    Debo añadir que he visto que de usar la sintaxis property() se debe cambiar varias lineas en el código (https://www.python-course.e… siendo el resultado el mismo.
    Es mas elegante utilizar el «decorador» @property

    Gracias una vez más por el articulo.
    Saludos.

  2. Aún sigo buscando a alguien que hable español y que de verdad sepa explicar los conceptos de programación de manera lógica y explicando la razón de los conceptos y el proceso mental que se tiene que hacer.
    Tu blog me acercó un poco más al entendimiento pero siento que hay cosas al aire.
    de todas formas Gracias.

    1. Hola @disqus_rkPWrnIjFb:disqus podrías expresar puntualmente cual son esas cosas cuya explicación sientes que quedan «al aire». Quizás podría revisarlas mejor y elaborar alguna explicación un poco más detallada.. Mis Saludos y gracias por leer mi blog!

  3. Yo lo entendi mejor leyendolo así.

    class Perros():
    def __init__(self, nombre, peso):
    self.__nombre = nombre
    self.__peso = peso

    @property
    def nombre(self):
    return self.__nombre

    @nombre.setter
    def nombre(self, nuevo):
    self.__nombre = nuevo

    @nombre.deleter
    def nombre(self):
    del self.__nombre

    def peso(self):
    return self.__peso

    —————————–
    @property al ser un decorador tiene que trabajar sobre un METODO(funcion) para modificarlo
    definimos un nuevo METODO(funcion) » def nombre(self): » para poder decorarlo.

    @property
    def nombre(self): #Aqui, » nombre » será el METODO/funcion a llamar luego (en print(Tomas.nombre), lo llamaré «nombre»(#1)
    return self.__nombre #En el blog, cuando se refiere a «Getter: Se encargará de interceptar la lectura del atributo»
    es en este momento, cuando » return self.__nombre » **retorna(valga la redundancia) su valor a
    » def nombre(self): » – y es por esto, que cuando llamas a » nombre (#1)» puedes modificarlo.
    #Recordemos que » self.__nombre » no es accesible ya que tiene «_ _», pero si lo cambiamos a «nombre» (#1) mediante el
    decorador » @property.GETTER » ya podemos acceder a el, ya que lo estamos llamando a travez » nombre(#1) » -se que
    estoy repitiendo, pero es para que se entienda la idea
    —————————

    Tomas = Perros(‘Tom’, 27)

    print(Tomas.nombre) #Tom #1 – aquí estas llamando al atributo «_ _nombre » mendiante «nombr» (#1) que esta modificado por @property.GETTER
    print(Tomas.peso) #27

    print(«»»
    ————-
    «»»»»)

    Tomas.nombre = ‘Tomasito’ #1 – aquí estas llamando al atributo «_ _nombre» mendiante «nombre» (#1) que esta modificado por @property.SETTER
    print(Tomas.nombre) #Tomasito y es por eso que puedes modificarlo

    Espero se haya entendido, tienes que trabajar el codigo del blog para poder captarlo.

  4. La vdd que buena pagina, ya llene 50 paginas de mi carpeta con los apuntes y ejemplos
    no me falta tanto para terminar todos los temas y uufffff he aprendido lo suficiente, gracias

  5. Simplemente genial!! Has conseguido explicarlo de una manera fácil con ejemplos reales que podemos traducir mejor. Estaba pasando un mal bache con Python y has conseguido motivarme 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.