Setters, getters y decorador property

7/25

Lectura

En este punto estamos comenzando a utilizar conceptos en Python que comienzan a ser m谩s avanzados, por lo que es normal que puedan parecerte complejos o dif铆ciles de asimilar, as铆 que te animo a que los repases un par de veces.

Puedes tener la tranquilidad de que si bien, al inicio no los implementas en su totalidad, podr谩s seguir avanzando en el curso y poco a poco incorporarlos a tus proyectos donde lo m谩s importante es que sepas que cuentas con estas herramientas.


Entendiendo el concepto de decorador

Antes de comenzar me gustar铆a que analices el siguiente c贸digo:

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

驴Qu茅 pasar谩 si llamamos a la funci贸n zumbido()? si logras determinar el resultado de salida o entenderlo con detalle, entonces podemos seguir adelante.

Lo que sucede es lo siguiente:

>>> zumbido()
Este es el 煤ltimo mensaje...
Buzzzzzz
Este es el primer mensaje ;)

Si no diste con el resultado no te preocupes, solo hay que analizarlo con detalle y el truco est谩 en la l铆nea zumbido = funcion_decoradora(zumbido). Sucede que la funci贸n wrapper() recibi贸 la la funci贸n zumbido() c贸mo par谩metro y coloca su salida entre los otros dos prints.

Todo lo que sucede se conoce en programaci贸n como metaprogramaci贸n (metaprogramming), ya que una parte del programa trata de modificar a otra durante el tiempo de compilaci贸n. En tanto un decorador b谩sicamente toma una funci贸n, le a帽ade alguna funcionalidad y la retorna.

Mejorando la sintaxis

Definitivamente la forma en que decoramos la funci贸n es complejo, pero afortunadamente Python lo tiene en cuenta y podemos utilizar decoradores con el s铆mbolo @. Volviendo al mismo ejemplo de funcion_decoradora(), podemos simplificarlo as铆:

@funcion_decoradora
def zumbido():
	print("Buzzzzzz")

En solo tres l铆neas de c贸digo tenemos el mismo resultado que escribir zumbido = funcion_decoradora(zumbido).

驴Qu茅 son getters y setters?

A diferencia de otros lenguajes de programaci贸n, en Python los getters y setters tienen el objetivo de asegurar el encapsulamiento de datos. C贸mo habr谩s visto, si declaramos una variable privada en Python al colocar un gui贸n bajo al inicio de esta (_) y normalmente son utilizados para: a帽adir l贸gica de validaci贸n al momento de obtener y definir un valor y, para evitar el acceso directo al campo de una clase.

La realidad es que en Python no existen variables netamente privadas, pues aunque se declaren con un gui贸n bajo podemos seguir accediendo a estas. En Programaci贸n Orientada a Objetos esto es peligroso, pues podemos alterar el m茅todo de alguna clase y tener efectos colaterales que afecten la l贸gica de nuestra aplicaci贸n.

Clases sin getters y setters

Veamos un ejemplo con una clase que almacena un dato de distancia recorrida en millas (mi) y lo convierte a kil贸metros (km):

class Millas:
	def __init__(self, distancia = 0):
		self.distancia = distancia

	def convertir_a_kilometros(self):
		return (self.distancia * 1.609344)

Ahora creemos un objeto que haga referencia a un viaje:

# Creamos un nuevo objeto
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos el atributo distancia
>>> print(avion.distancia)
200

# Obtenemos el m茅todo convertir_a_kilometros
>>> print(avion.convertir_a_kilometros())
321.8688

Utilizando getters y setters

Incluyamos un par de m茅todos para obtener la distancia y otro para que no acepte valores inferiores a cero, pues no tendr铆a sentido que un veh铆culo recorra una distancia negativa. Estos son m茅todos getters y setters:

class Millas:
	def __init__(self, distancia = 0):
		self.distancia = distancia

	def convertir_a_kilometros(self):
		return (self.distancia * 1.609344)

	# M茅todo getter
	def obtener_distancia(self):
		return self._distancia

	# M茅todo setter
	def definir_distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		self._distancia = valor

El m茅todo getter obtendr谩 el valor de la distancia que y el m茅todo setter se encargar谩 de a帽adir una restricci贸n. Tambi茅n debemos notar c贸mo distancia fue reemplazado por _distancia, denotando que es una variable privada.

Si probamos nuestro c贸digo funcionar谩, la desventaja es que cualquier aplicaci贸n que hayamos creado con una base similar deber谩 ser actualizado. Esto no es nada escalable si tenemos cientos o miles de l铆neas de c贸digo.

Funci贸n property()

Esta funci贸n est谩 incluida en Python, en particular crea y retorna la propiedad de un objeto. La propiedad de un objeto posee los m茅todos getter(), setter() y del().

En tanto la funci贸n tiene cuatro atributos: property(fget, fset, fdel, fdoc) :

  • fget : trae el valor de un atributo.
  • fset : define el valor de un atributo.
  • fdel : elimina el valor de un atributo.
  • fdoc : crea un docstring por atributo.

Veamos un ejemplo del mismo caso implementando la funci贸n property() :

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	def definir_distancia(self, recorrido):
		print("Llamada al m茅todo setter")
		self._distancia = recorrido

	# Funci贸n para eliminar el atributo _distancia
	def eliminar_distancia(self):
		del self._distancia

	distancia = property(obtener_distancia, definir_distancia, eliminar_distancia)

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos su atributo distancia
>>> print(avion.distancia)
Llamada al m茅todo getter
Llamada al m茅todo setter
200

Aunque en este ejemplo hay una sola llamada a print, tenemos tres l铆neas como salida pues esta llama a los primeros dos m茅todos. Por lo que la propiedad distancia es una propiedad de objeto que ayuda a mantener el acceso de forma privada.

Decorador @property

Este decorador es uno de varios con los que ya cuenta Python, el cual nos permite utilizar getters y setters para hacer m谩s f谩cil la implementaci贸n de la programaci贸n orientada a objetos en Python cambiando los m茅todos o atributos de las clases de forma que no modifiquemos el c贸digo.

Pero mejor veamos un ejemplo en acci贸n:

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@obtener_distancia.setter
	def definir_distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		print("Llamada al m茅todo setter")
		self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos su atributo distancia
>>> print(avion.definir..distancia)
Llamada al m茅todo getter
Llamada al m茅todo setter
200

De esta manera usamos el decorador @property para utilizar getters y setters de una forma m谩s prolija e incluimos una nueva funcionalidad a nuestro m茅todo definir_distancia() , al mismo tiempo protegemos el acceso a nuestras variables privadas y cumplimos con el principio de encapsulaci贸n.

Aportes 112

Preguntas 16

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

Amigos, la verdad yo tampoco la ten铆a(nada) clara, pero hay que ser parte de la soluci贸n y no del problema, entre los comentarios encontr茅 un enlace a un blog, que para m铆, ha sido de mucha ayuda.

Propiedades en Python

Decoradores en Python

Espero les ayude c贸mo a m铆. 馃槂

Gente! Espero les sirva
1.- Decoradores = Son funciones que tiene como par谩metro otras funciones con el objetivo de a帽adirle funcionalidades sin la necesidad de modificarla. Ejemplo: Tengo una funci贸n que hace un helado y quiero a帽adirle chispas de chocolate o chispas de colores, sin tener que modificar la funci贸n original helado.

def helado():       #funci贸n que no deseo modificar
    print("Helado de vainilla")

def chispas_de_chocolate(fun):  #decorador 
    def run():
        fun()           #llamo a funci贸n que recib铆 como parametro      
        print('A帽adiendo chispas de chocolate') #a帽ado una nueva funcionalidad
    return run()        #ejecuto la funci贸n run()

def chispas_de_colores(fun):  #decorador 
    def run():
        fun()           #llamo a funci贸n que recib铆 como parametro       
        print('A帽adiendo chispas de colores') #a帽ado una nueva funcionalidad
    return run()        #ejecuto la funci贸n run()

chispas_de_chocolate(helado)    #sintaxis no prolija / no se recomienda usar :'(

Gracias al maravilloso mundo de python podemos usar los decoradores usando una sintaxis mucho m谩s f谩cil de entender. De esta forma:

@chispas_de_chocolate
def helado(): 
    print("Helado de vainilla")

Ahora nos damos cuenta del porqu茅 del nombre _decoradores _ 馃槃 Se entiende mucho mejor!!. Al leer la funci贸n nos damos cuenta de que estamos a帽adiendo una funcionalidad @chispas_de_cholote a la funci贸n helado()

Ahora bien, esto funciona muy bien para funciones o m茅todos(si usamos clases). Pero digamos que queremos a帽adir una nueva funcionalidad a los atributos de una clase. 驴C贸mo lo har铆amos?. Supongamos que trabajamos para una empresa que renta deparmentos y creamos una clase Departamentos.

class Departamentos:

    def __init__(self, precio):
        self.precio = precio        #atributo p煤blico

Y usamos la clase para una pagina web y un aplicativo m贸vil. En donde constantemente estamos creando instancias de la clase Departamento, como estas:

casa_playa = Departamentos(60000) # instancia
pen_house = Departamentos(180000) #instancia

Supongamos que pas贸 much铆simo tiempo, y en total tenemos m谩s de 10000 instancias repartidas en una pag web y un app m贸vil. Y de la noche a la ma帽ana, aparece un hacker que est谩 accediendo directamente al atributo precio de las instancias y los esta cambiando por valores muy bajos, es esta manera:

casa_playa.precio = 100    #cambiamos directamente los valores
pen_house = 50

驴Qu茅 har铆amos? Lo primero que se nos ocurre es cambiar el atributo p煤blico por privado en el contructor de la clase.

class Departamentos:

    def __init__(self, precio):
        self._precio = precio        #atributo privado

Pero al hacer esto, tendr铆amos que cambiar toooodas las l铆neas de nuestro c贸digo donde usamos self.precio por self._precio. Obviamente entrar铆amos en p谩nico x鈥檇dd. Pero calma鈥 llegan a nuestro rescate los metodos property.

**Un captador : **para acceder al valor del atributo.
**Un setter :** para establecer el valor del atributo y _cuidarlo_
**Un eliminador **: para eliminar el atributo de la instancia, si es necesario.

Primero, con el captador, har茅mos que precio sea una propiedad de nuestra clase. Es decir, cuando nuestro c贸digo trate de modificar el atributo de las instancias, por ejemplo: casa_playa.precio = 10 o pen_house.precio = 30. Lo haga sin romperse. **Recuenden que ya cambiamos el nombre del atributo precio por: _precio. Es como enga帽ar a python, al decirle de que precio == _precio.

class Departamentos:

    def __init__(self, precio):
        self._precio = precio        #atributo privado
    
    @property
    def precio(self):               #cuando se ejecute, por ejemplo -> departamento.precio
        return self._precio         #retornar el atributo privado _precio

De esa manera, ya no es necesario cambiar tooodas las lineas de nuestro c贸digo en la pag web y el m贸vil.
Segundo, usando setter establecer茅 algunas restricciones al cambiar el valor. Su pongamos que el due帽o nos dice que los valores para cada departamento no pueden ser menores a 1000 o mayores a 200000, entonces:

class Departamentos:

    def __init__(self, precio):
        self._precio = precio        #atributo privado
    
    @property
    def precio(self):               #cuando se ejecute departamento.precio
        return self._precio         #retornar el atributo privado _precio
    
    @precio.setter                  #setter, se ejecutar谩 cuando se intente cambiar los valores
    def precio(self, nuevo_valor):  
        if nuevo_valor < 1000 or nuevo_valor > 200000:  #si valor es menor a 1000 o mayor a 200000
            raise ValueError('No es posible cambiar a estos valores')   #arrojamos error
        else:                                          
            self._precio = nuevo_valor                  #asignamos el nuevo valor
            print(f'El nuevo valor del departamento es: {self._precio}')    #imprimimos el nuevo valor

**Ojo en la sintaxis: con property usted define precio como una propiedad, y _automaticamente _se crean precio.setter y precio.deleter detr谩s de escena, por as铆 decirlo.

Entonces, al ejecutar:

casa_playa = Departamentos(60000) # instancia
pen_house = Departamentos(180000) #instancia

casa_playa.precio = 70000   #valor permitido
pen_house.precio = 10   #valor no permitido

Nos arroja este resultado:

>>> El nuevo valor del departamento es: 70000
>>> ValueError: No es posible cambiar a estos valores

Como no se admiten valores inferiores a 1000 entonces nos arroja el value error 馃槃

Finalmente tenemos el deleter, donde se usa el parametro del para borrar el atributo de instancia.

class Departamentos:

    def __init__(self, precio):
        self._precio = precio        #atributo privado
    
    @property
    def precio(self):               #cuando se ejecute departamento.precio
        return self._precio         #retornar el atributo privado _precio
    
    @precio.setter                  #setter, se ejecutar谩 cuando se intente cambiar los valores
    def precio(self, nuevo_valor):  
        if nuevo_valor < 1000 or nuevo_valor > 200000:  #si valor es menor a 1000 o mayor a 200000
            raise ValueError('No es posible cambiar a estos valores')   #arrojamos error
        else:                                          
            self._precio = nuevo_valor                  #asignamos el nuevo valor
            print(f'El nuevo valor del departamento es: {self._precio}')    #imprimimos el nuevo valor
    
    @precio.deleter                 #supersor
    def precio(self):
        del self._precio            #borramos el atributo

Cuando se borra, ya no es posible acceder al atributo, por ende, nos arrojar谩 un error. Como se muestra a continuaci贸n:

# Creamos una instancia
>>> casa_playa = Departamento(60000)

# El atributo de la instancia existe
>>> casa_playa.precio
60000

# Borramos el atributo
>>> del casa_playa.precio

# El atributo de instancia ya no existe
>>> casa_playa.precio
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    casa_playa.precio
  File "<pyshell#20>", line 8, in price
    return self._precio
AttributeError: 'Departamento' object has no attribute '_precio'

Un saludo. Nunca dejen de aprender!

Honestamente, no entend铆 nada, lo le铆 unas 5 veces, pero no cuajo

Decorador @Property

El c贸digo tal como esta no funciona, para que funcione se debe hacer lo siguiente

agregar los dos puntos al final de la linea 14 def definir_distancia(self, valor):

y la linea 24 pasa de avion.distancia = 200 =====> avion.definir_distancia = 200

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@obtener_distancia.setter
	def definir_distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		print("Llamada al m茅todo setter")
		self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.definir_distancia = 200

# Obtenemos su atributo distancia

# Obtenemos su atributo distancia
print('llamando',avion.obtener_distancia)```


Estimados, soy un alumno que comenz贸 esta escuela de data science sabiendo absolutamente nada de programaci贸n, nada de nada. Por lo que se agradecer铆a que pongan m谩s cuidado en la redaccion de las clases, por ejemplo: "A diferencia de otros lenguajes de programaci贸n, en Python los getters y setters tienen el objetivo de asegurar el encapsulamiento de datos. C贸mo habr谩s visto, si declaramos una variable privada en Python al colocar un gui贸n bajo al inicio de esta (_) y normalmente son utilizados para: a帽adir l贸gica de validaci贸n al momento de obtener y definir un valor y, para evitar el acceso directo al campo de una clase"
La palabra que esta entre lo signos +, me produjo un tremendo problema al no entender finalmente el contexto general. Cuesta unir el resto del p谩rrafo a esa palabra.
Y para alguien como yo, que no sabia nada de nada de progrmaacion, cualquier evento como estos, nos causa problemas de comprensi贸n.
Solo eso. Slds.

Los ejemplos del art铆culos est谩n bastantes reguleros

Bueno, llevo dos d铆as tratando de comprender lo que son los decoradores y creo ya tuve un entendimiento.
a continuaci贸n les adjunto un c贸digo en el cual pongo un ejemplo explicativo (la forma en la que yo me ayude a comprenderlo) para tener el concepto en mente, y luego les pongo otro ejemplo para que quede un poco mas claro.
disculpen si esta muy largo por el texto pero creo que era necesario.
Recomiendo que copien el codigo y lo peguen en su editor.


def f1(fun): # f1 es el empresario que recibe los contratos, pero su empleado (la funcion dentro del) es quien har谩 todo el trabajo (el trabajo de decorar). el empresario solo puede recibir un contrato (osea, solo puede recibir el nombre de una funcion como argumento)
    def wrapper(): # esta es la funcion que har谩 todo el trabajo (el empleado), dentro de esta funcion se llamar谩 a la funcion que queremos decorar, y debe tener los mismos algumentos que tiene la funcion que queremos decorar
        print('started') # aqui ya estamos dentro de la funcion que har谩 el trabajo de decorar y es en este cuerpo el lugar en donde podemos usar los argumentos que tiene la funcion que hara el trabajo de decorar
        fun() # despues de usar los argumentos pues ya podemos llamar a la funcion que queriamos decorar. y la debemos llamar con todo y sus argumentos
        print('ended') # esto es solo broche de oro.
    return wrapper # aqui debemos llamar a la funcion que hizo todo el trabajo de decorar para que el empresario pueda entregar lo que se pidi贸

@f1 # esta es una forma en la que python permite llamar a la empresa 'decoradora' para poder decorar a la funcion que esta debajo del.
def f(): #aqui declaramos la funcion que queremos decorar y le decimos que haga todo lo que nosotros queremos que haga. y los argumentos de esta funcion deben ser los mismos que se le entregaran al empleado que lo va a decorar
    print('Hello') #en este caso solo quiero que la funcion imprima en pantalla la palabra 'Hello'

# ahora ya podemos usar nuestra funcion que queriamos decorar. en este punto la funcion ya est谩 decorada. la debemos llamar con todo y sus argumentos que nosotros establecimos
f()

print()
#---------------------------------------------------------------------------------------------
#---------------- E J E M P L O para que termine todo amarre bien -----------------------------
#---------------------------------------------------------------------------------------------


# la funcion check es el empresario que se compromete a entregar algo decorado (una funcion). lo que el empleado debe hacer en este problema es verificar que el divisor no sea cero, y si lo es, pues despliega un mensaje.
def check(fun):
    def result(a, b): # esta funcion (empleado) debe contener los mismos argumentos que la funcion que queremos decorar.
        if b == 0: # aqui este empleado va a checar si el divisor es cero, y su es pues imprime un mensaje, y seguido de eso le agregamos return para que el mensaje para ser desplegado en pantalla
            return ("Can't divide by 0") 
        else:
            return fun(a, b) # aqui llamamos a la funci贸n que queriamos decorar, y la llamamos con todo y sus argumentos pues para que haga todo lo que debe hacer si su divisor No es cero
    return result #debemos regesar la funcion que hizo todo el trabajo de decorar pues para que la decoracion pueda ser devuelta. Cabe mencionar que la accion de decorar para este problema fue lo de checar si el divisor es cero o no. 

@check # la funcion check esta recibiendo a la funcion que esta debajo de el.
def divide(a, b): # esta es la funcion que queremos decorar. esta funcion lo que hace es recibir dos numeros como argumentos y devolver la divicion de esos numeros
    return (a / b)

#ahora ya podemos llamar a nuestra funcion. y cabe mencionar que ya esta decorada.
print(divide(10, 2)) # aqui lo que pasar谩 es que se nos devolver谩 la divicion. osea, 5
print(divide(7, 0)) # aqui sabemos que se nos devolver谩 el mensaje de que No podemos dividir entre cero.


En el 煤ltimo ejemplo de @property, buscando un poco encontr茅 este enlace y el c贸digo ser铆a de esta forma (a帽adiendo tambi茅n un deleter). Luego de llamar al deleter, ya no existir谩 el atributo y si llamamos a este, nos dar谩 un error.

class Millas:
    def __init__(self):
        self._distancia = 0

    # Funci贸n para obtener el valor de _distancia
    # Usando el decorador property
    # Llamamos a este setter haciendo avion.distancia = 20
    @property
    def distancia(self):
        print("Llamada al m茅todo getter")
        return self._distancia

    # Funci贸n para definir el valor de _distancia
    # Llamamos a esta funci贸n simplemente llamando a avion.distancia
    @distancia.setter
    def distancia(self, valor):
        if valor < 0:
            raise ValueError("No es posible convertir distancias menores a 0.")
        print("Llamada al m茅todo setter")
        self._distancia = valor

    # Funci贸n para eliminar el valor de _distancia
    # Llamamos a esta funci贸n llamando a del avion.distancia
    @distancia.deleter
    def distancia(self):
        print("Llamada al m茅todo deleter")
        del self._distancia
        
# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 20

# Obtenemos su atributo distancia
print(avion.distancia)

# Eliminamos el atributo
del avion.distancia

# Salida
#Llamada al m茅todo setter
#Llamada al m茅todo getter
#20
#Llamada al m茅todo deleter

Otra cosa, al momento de empezar los videos de clases, en ningun momento explicaron como se utilzaban las palabras self, el orden de las peticiones a.distancia, etc. Ahora estoy partiendome la cabeza intentado entender como funciona esto.

Yo sigo sin entenderle del todo a todo esto :'v y lo peor es que no puedo decir espec铆ficamente el qu茅 es lo que no entiendo.

Hola a todos, os dejo el enlace a un blog que explica este tema mucho mejor que david:
https://pythones.net/propiedades-en-python-oop/#:~:text=鈥淎tributos manejables鈥 que nos permiten,%2Fa%2C borrado%2Fa.

Es desconcertante que esta clase se haya expuesto as铆

Esta parte del curso deja bastante que desear desde mi punto de vista.

Si bien me parece que es una buena introducci贸n a decoradores, m铆nimo para conocerlos y generar inter茅s, es muy poca informaci贸n la que dan e incluso viendo la siguiente clase queda muy poco claro el tema.

En datacamp hay un tutorial bastante bueno de decoradores que al menos a m铆 me ayud贸 a entender un poco m谩s el tema.

Espero que tambi茅n les sirva!

Todos los programas con un peque帽o resumen

"""En Python los getters y setters tienen el objetivo de asegurar el 
encapsulamiento de datos
"""

#######    CLASE SIN SETTER Y GETTERS    #############

class Millas:
 def __init__(self, distancia = 0):
     self.distancia = distancia
     print('Distancia inicial: ',distancia)
     
 
 def convertirAKilomentros(self):
     return(self.distancia * 1.609344)

#Creamos un nuevo objeto
avion = Millas()

#Indicamos la distancia
avion.distancia = 200    #Regresa el valor a la funci贸n "def __init__"

#Obtenemos el atributo de distancia
print('Distancia final en Millas: ',avion.distancia)   #imprime --> 200

#Obtener el m茅todo convertirAKilometros
print('Distancia final en Kilometros: ',avion.convertirAKilomentros())





######### UTILIZANDO SETTERS & GETTERS ########################
class Millas:
 def __init__(self, distancia = 0):
     self.distancia = distancia
 
 def convertirAKilomentros(self):
     return(self.distancia * 1.609344)

 #M茅todo getter
 def obtener_distancia(self):
     return self._distancia  #_distancia <--- Variable privada

 #M茅todo setter
 def definir_distancia(self, valor):
     if valor < 0:
         raise ValueError('No es posible convertir distancias menores a 0')
     self._distancia = valor





################### PROPERTY () ##############################
""" 
Esta funci贸n crea y retorna la propiedad de un objeto.

La propiedad de un objeto posee los m茅todos --> getter(), setter() y del() 
En tanto la funci贸n tiene cuatro atributos --> property(fget, fset, fsel, fdoc)
       - fget: trae el valor de un atributo
       - fset: define el valor de un atributo
       - fdel: elimina el valor de un atributo
       - fdoc: crea un docstring por atributo
"""       
class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	def definir_distancia(self, recorrido):
		print("Llamada al m茅todo setter")
		self._distancia = recorrido

	# Funci贸n para eliminar el atributo _distancia
	def eliminar_distancia(self):
		del self._distancia

	distancia = property(obtener_distancia, definir_distancia, eliminar_distancia)

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos su atributo distancia
print(avion.distancia)  
"""imprime
        Llamada al m茅todo setter
        Llamada al m茅todo getter
        200
"""



################## Decorador @property ##################
class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@obtener_distancia.setter
	def definir_distancia(self, valor):
	 if valor < 0:
		 raise ValueError("No es posible convertir distancias menores a 0.")
	 print("Llamada al m茅todo setter")
	 self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos su atributo distancia
print(avion.distancia)```

El c贸digo del ejemplo tiene un error de sintaxis y no esta bien implementada el uso del decorador property lo que provoca una confusi贸n al estudiante.

El codigo del ejemplo corregido:

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@distancia.setter
	def distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0")
		print("Llamada al m茅todo setter")
		self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

print('>> Asignamos el valor 200 a la propiedad distancia')
avion.distancia = 200
print('>> Obtenemos el valor de la propiedad distancia')
print(avion.distancia)

No entend铆 馃槮

Me sigue costando entender como queda encapsulado el contenido. creo que si veo mas clases lo entender茅 mejor y si no volver茅 a leer esto o buscare alguna documentaci贸n sobre eso.

Est谩 bueno el curso, pero el tema de decoradores no me quedaba claro a pesar de yo ser un profesional con bastante experiencia. Me parece que este tema debi贸 ser explicado con un video, de no ser por el post de Alonso Cangalaya, no hubiese entendido.

Aqu铆 dejo mi c贸digo, se ejecuta bien y todo pero me surgieron mucha preguntas en el camino.
驴Por qu茅 no se indica la variable distancia en la clase? es decir poner:
def __ inir__(self, distancia)
Yo trate de hacerlo as铆 pero no me funcionaba el setter, 驴alguien que me explique por que?
otra pregunta es que si pongo solo:
avion.distancia=45
solo me arroja el valor del setter y ya no del getter, esto es porque es una varibable privada? no me queda del toco claro porque en la funcion getter agregu茅 un par de print as铆 que me debria de haber mostrado algo.
Espero que me puedan ayuda con sus respuestas 馃槃

class Millas:
    def __init__(self):
        self._distancia = 0

    #getter
    @property
    def distancia(self):
        print('Llamando al Getter.')
        print(self._distancia)
        return self._distancia

    #setter
    @distancia.setter
    def distancia(self, valor):
        # Validaci贸n si no ingresa un n煤mero.
        if False is isinstance(valor, (int, float)):
            raise TypeError('Ingrese un n煤mero entero v谩lido.')
        if valor < 0:
            raise ValueError('No es posible convertir distancias menores a 0.')

        print('Llamando al Setter.')
        self._distancia = valor

        print(self._distancia)


    #Funcion que convierte km a millas
    def convertir_a_km(self):
        conversion = self._distancia * 1.609344
        print(f'{self._distancia} Millas convertidas a Kilometros s贸n {conversion}km')


if __name__ == '__main__':
    avion = Millas()
    avion.distancia # el getter muestra 0, devido a que no le pasamos ningun valor
    avion.distancia= 45 # el setter muestra 45 y comprueba que est谩 bien ejecutada

    avion.convertir_a_km() # se transforma la funci贸n 
# resultados
Llamando al Getter.
0
Llamando al Setter.
45
45 Millas convertidas a Kilometros s贸n 72.42048km

Para funcion property() 驴c贸mo o bajo que circunstancias se ocupan fdel y fdoc ?

Le hice unas peque帽as modificaciones al c贸digo鈥 鉂

class Millas:

    def __init__(self):
        self.__distancia = int

    @property
    def distancia(self):
        print('Llamando al Getter.')
        print(self.__distancia)
        print('')
        return self.__distancia

    @distancia.setter
    def distancia(self, valor):
        # Validaci贸n si no ingresa un n煤mero.
        if False is isinstance(valor, (int, float)):
            raise TypeError('Ingrese un n煤mero entero v谩lido.')
        # Validaci贸n si ingresa un n煤mero menor que 0.
        if valor < 0:
            raise ValueError('No es posible convertir distancias menores a 0.')
        print('Llamando al Setter.')
        self.__distancia = valor
        print(self.__distancia)
        print('')

    def convertir_a_km(self):
        conversion = self.__distancia * 1.609344
        print('{} Millas convertidas a Kilometros s贸n {}km'.format(
            self.__distancia, round(conversion, 2)))


if __name__ == '__main__':
    avion = Millas()
    # Getter
    avion.distancia = 23
    # Setter
    avion.distancia
    # Convertir a km
    avion.convertir_a_km()

Est谩 mal la parte de: Mejorando la sintaxis con solo esas l铆neas, no se obtiene el mismo resultado.
Les comparto este blog yo tambi茅n: https://pythones.net/propiedades-en-python-oop/
Ah铆 s铆 explican bien.

Lo entend铆 todo, excepto en la parte de los decoradores de getters y setters, es decir, no entiendo por qu茅 al getter le pone @poroperty y al setter le pone @obtener_distancia.setter (que seg煤n los comentarios deber铆a ser @distancia.setter, pero sigue sin hacerme sentido), es decir, no entiendo que es lo que hacen exactamente >.< Si s茅 que al momento de llamar a avion.distancia = 200 se ejecuta el setter y al llamar avion.ditancia se ejecuta el getter, pero no entiend铆 exactamente esos @ que est谩n ah铆 馃 Adem谩s seg煤n mi l贸gica, la salida deber铆a ser primero setter, luego getter y luego 200:

Llamada al m茅todo setter
Llamada al m茅todo getter
200

El c贸digo que se uso de ejemplo en al final esta mal implementado, por los siguientes puntos:
1.- En la funcion print(), esta mal escrito es nombre del getter, seria "avion.definir_distancia"
2.- La manera de indicar la distancia no es la correcta, ya que si corren ese c贸digo el valor que mostrara sera 0 y es por que as铆 esta definido en el constructor cuando creamos el objeto.

Sabiendo esto, me di la tarea de modificarlo, espero sea mas claro para los dem谩s.
NOTA: Esto me di cuenta ya que he llevado un curso de POO en la universidad antes de tomar este curso y aun as铆, me confund铆. Es una alerta para platzi, deben checar los c贸digos porque hay gente que nunca ha interactuado con estos conceptos y si nos guiamos solamente del ejemplo, a la hora de aplicarlo, estar谩 mal. Aqu铆 les dejo la correcci贸n鈥

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n SETTER para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n GETTER para definir el valor de _distancia
	@obtener_distancia.setter
	def definir_distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		print("Llamada al m茅todo setter")
		self._distancia = valor


if __name__ == "__main__":
    # Creamos un nuevo objeto
    avion = Millas()

    # Indicamos la distancia haciendo el uso del SETTER
    avion.definir_distancia = 200

    #Imprimos el nuevo valor de 'distancia' que obtenemos usanto el 'GETTER'
    print(avion.obtener_distancia)

El profesor dice que la funci贸n Wrapper recibe el par谩metro funci贸n, pero en realidad es la funci贸n_decoradora la que recibe a esa funci贸n (es es zumbido)
Por este y otros errores y la baja calidad en los cursos, es que 煤ltimamente tengo que recurrir a cursos externos y gratuitos de youtube para despejar dudas.
Les dejo este video de un profesor de verdad, quien en vez de confundir, aclara. En los primeros ocho minutos deja clar铆simo el funcionamiento de las decoradoras.

Tengan m谩s cuidado con la redacci贸n, a veces basta con escribir un conector equivocado para que una oraci贸n pierda su sentido, y para los estudiantes m谩s novatos eso puede significar horas de desperdicio tratando de entender algo que no se puede.

Un desastre la redacci贸n鈥 Porque no corrigen esto??? Hay comentarios de hace un a帽o quej谩ndose y sigue igual.

Hola a todos he visto que se complica mucho este tema, as铆 que para hacerlo m谩s manejable, les recomiendo, ver estas tres clases(que est谩n en platzi) que les iluminara el camino en cuanto a los decoradores:
clase 1
clase 2
clase 3
Para entender decoradores antes deben entender una serie de conceptos b谩sicos como el scope de una variable, que son las nested functions, que son los closures, y si quieren aplicar el @property van a tener que entender que es el sugar syntax(bueno no pero conocerlo te iluminara), espero les sea de ayuda.

Les dejo el c贸digo funcionando usando el decorador @property

class Millas(): 
    def __init__(self) -> None:
        self._distancia = 0
    

    @property
    def distancia(self):
        print("Se ejecut贸 el getter")
        return self._distancia
    
    @distancia.setter
    def distancia(self, value): 
        print("Se ejecuto el setter")
        if value < 0: 
            raise ValueError
        self._distancia = value
    
    @distancia.deleter
    def distancia(self): 
        del self._distancia

if __name__ == "__main__": 
    obj = Millas()
    obj.distancia = 20
    print(obj.distancia)

Ac谩 los mensajes un poco corregidos y una invitaci贸n a python tutor para que analicen.

https://bit.ly/3glWX3L

def funcion_decoradora(funcion):
    def wrapper():
        print("Este es el primer mensaje...")
        funcion()
        print("Este es el 煤ltimo mensaje ;)")
    return wrapper

def zumbido():
    print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)
zumbido()

Les dejo un c贸digo sencillo para entender que el nombre de la variable la toma de la funci贸n donde esta el decorador con el setter. Intenten cambiarla y ver谩n que ya no pasa por los prints del get y el set

class Millas:
	def __init__(self):
		self._distanciados = 0

	# Funci贸n para obtener el valor de _distancia
	@property
	def o_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distanciados

	# Funci贸n para definir el valor de _distancia
	#El nombre de la variable la toma del nombre 
	#de la funci贸n decorada con setter
	@o_distancia.setter
	def distancia(self, recorrido):
		print("Llamada al m茅todo setter")
		self._distanciados = recorrido

if __name__ == "__main__":
	avion = Millas()
	avion.distancia = 200
	print(avion.distancia)

Dice que en la funci贸n decorador y la de property protegen la variable haci茅ndole privada. Pero igual acceden a ella cuando le dan un valor a distancia as铆:

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

Este video me ayudo a entender mejor!! https://www.youtube.com/watch?v=DlGPvq9r6Q4

Este aporte lo puse originalmente el el Curso Profesional de Python, pero al ver que se toca de nuevo el tema quise aportar de igual manera lo aprendido.

.
Para entender un poco este tema me remit铆 al curso de Python del canal P铆ldoras inform谩ticas en YouTube. Clic aqu铆 para ver el v铆deo.
.
La sintaxis ser铆a la siguiente:

def <funcion_decorador> (<funcion>):

    def <funcion_interna> ():

        <c贸digo de la funcion_interna>

    return <funcion_interna>

No obstante, dej贸 el c贸digo de ejemplo que us贸 para explicar este tema por si a alguien le interesa:

def funcion_decoradora(funcion_parametro):

    def funcion_interior():

        #Acciones adicionales que decoran

        print("Vamos a realizar un c谩lculo: ")

        funcion_parametro()

        #Acciones adicionales que decoran

        print("Hemos terminado el c谩lculo")

    return funcion_interior

# Para llamar la funci贸n decoradora se usa el car谩cter "@" y a continuaci贸n el nombre de esta.
# Python entiende que la funci贸n adyacente hacia abajo es la funci贸n a decorar y la toma como parametro.
@funcion_decoradora
def suma():
    print(15+20)

suma()

El resultado en consola ser铆a el siguiente:

>>> Vamos a realizar un c谩lculo: 
>>> 35
>>> Hemos terminado el c谩lculo

Algunos conceptos:

  • Metaprogramaci贸n: Una parte del programa trata de modificar a otra durante el tiempo de compilaci贸n.

  • Encapsulamiento: Consiste en la privaci贸n de acceso a atributos y m茅todos internos desde el exterior (aqu铆 entran en juego las clases).

  • Propiedad de un objeto: Asociaci贸n entre un nombre y un valor.
    Cuando el valor de una propiedad es una funci贸n, se conoce como m茅todo.

  • M茅todo: Tareas que puede realizar un objeto.

  • Docstring: Variable de un objeto que indica para qu茅 y c贸mo se utiliza.

  • Propiedad: En python, son 鈥榓tributos especiales鈥 que manejamos con getter, setter y deleter.

  • Atributo: Caracter铆sticas que definimos para un objeto.

Fue muy complicado para mi entender estas primeras clases, los volv铆 a ver como tres veces. Estoy complementando con la app de Sololearn, les recomiendo much铆simo ya que la explicaci贸n sobres este tema est谩 mucho mejor!!

Que es docstring: En python los objetos cuentan con una variable especial llamada 鈥渄oc鈥, mediante a ella podemoss descrubir para que sirven y como se usan los objetos, estas variables reciben el nombre de 鈥渄ocstring鈥, cadena de documentacion.

Ejemplo:

def calculo(n1, n2):
    """ Hola esta es el docstring de la funcion """
    return n1 + n2

resultado = caculo(10+20)
print(resultado)

help(): Es una funcion que nos retorna el docstring que tengamos en nuestros objetos, el parametro que recibe es el objeto que queremos verificar, puede ser una funcion, metodo o clase. help(calculo)

Ejemplo de uso getter y setter:

class Cuenta():
    def __init__ (self, pro, sal, mon):
        self.__propietario = pro
        self.__saldo = sal
        self.__moneda = mon

# Getters (metodo Get)
    def get_Saldo(self):
        return self.__saldo

    def get_Propietario(self):
        return self.__propietario

    def get_Moneda(self):
        return self.__moneda

# Setters (metodo Set)

    def set_Moneda(self, moneda):
        self.__moneda = moneda

def run():

    cuenta1 = Cuenta("Zero", 1000, "Dolares")
    print(cuenta1.get_Saldo())
    print(cuenta1.get_Moneda())
    cuenta1.set_Moneda("Bolivares")
    print(cuenta1.get_Moneda())

if __name__ == "__main__":
    run()```

Corre el script varias veces y obtendras resultados diferente


import random

class Human:
	def __init__(self, _temperature = None):
		self._temperature = _temperature

	#Obtain the temperature 	
	@property 
	def temperature(self):
		print('Getting Value(getter)')
		return self._temperature

	#Set the temperature
	@temperature.setter
	def temperature(self, temperature):
		print('Setting Value(setter)')
		if temperature < 0 or temperature >41:
			raise ValueError('Temperatura debe ser un valor entre entre 0 y 40')
		self._temperature = temperature

	#Delete the temperature
	@temperature.deleter
	def temperature(self):
		del self._temperature	


if __name__ == '__main__':

	Ana = Human()
	T = [-1,0,25,35,40,41,48]
	Ana.temperature, *_ = random.sample(T,2)
	print(Ana.temperature)

ya lo entend铆 al inicio costo porque me confund铆, pero ya esta todo comprendido

En el return debemos utilizar la funci贸n no solo haciendo el llamado si no ejecutandola con ()

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper() #EDITADO 

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

Para entender un poco mejor los decoradores 鈥

def funcion_decoradora(funcion):
	def wrapper():
		print('Este es el primer mensaje...')
		funcion()
		print('Este es el 煤ltimo mensaje')
	return wrapper()

def zumbido():
	print('Buzzzzzz')


if __name__ == "__main__":
	print('La mosca con funci贸n y zumbido')
	mosca = funcion_decoradora(zumbido)

	print('La abeja con zumbido')
	abeja = zumbido()

	print('Solo zumbido')
	zumbido()

	print('Una decoraci贸n')
	@funcion_decoradora
	def ladra():
		print('guau'*3)

Se para que funcionan los Getters y Setter en otros Lenguajes de Programaci贸n, pero en Python no entiendo por que le ponga o no le ponga los decoradores(de getter y setters) se sigue ejecutando el programa sin ning煤n error.
Si alguien me podr铆a ayudar por favor.

Creo el codigo tiene un error.
el decorador setter debe ser: @obtener_distancia.setter.

Yo modifiqu茅 el ejemplo de esta forma:

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@distancia.setter
	def distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		print("Llamada al m茅todo setter")
		self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

print(avion.distancia)

No entend铆 absolutamente nada 馃槮

Despu茅s de tanto tiempo ya deber铆an haber corregido el error de c贸digo en la parte del decorador @property. Hasta hay un profesor de Platzi que escribe en los comentarios que no entendi贸 el c贸digo por el error que hab铆a. Media pila gente de Platzi. Mucha inteligencia artificial y machine learning, pero no son capaces de leer los comentarios de la gente que se queja y marca sus errores. No es tan dif铆cil detectar y corregir estas cosas.

en la parte en la que dice: 鈥溌縌u茅 pasar谩 si llamamos a la funci贸n zumbido()?鈥. Creo esta mal redactada, si no mal entiendo no se llama a la funci贸n 鈥渮umbido()鈥 en realidad se llama a la variable 鈥渮umbido鈥 que apunta a la 鈥渇uncion_decoradora(zumbido)鈥 que tiene como entrada ahora s铆 la funci贸n zumbido

Genial! Otra lectura, ahora tengo que buscar un video en youtube para entender mejor!

A los que no le entendieron les recomendar铆a que tomen el curso de python profesional primero (aqu铆 se los dejo

Ahi explican mucho mejor todo que significan las nested funcions y los decoradores 馃槂

Hola dejo mi aporte porque el profesor tuvo un error lo que no permite que se ejecute el c贸digo, llev谩ndome un rato largo poder entenderlo.

El escribi贸:

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

A lo que le falta en el return wrapper el (). Sin eso no se ejecuta la funci贸n wrapper() al terminar de ejecutarse funcion_decoradora, no devolviendo nada de nada.

Ser铆a:

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper()

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

No deberian poner estos articulos escritos, todo deberia ser como clases, ya que almenos yo necesito ejemplos claros. Para ponerle un ejemplo, el articulo dice

Si probamos nuestro c贸digo funcionar谩,

Pero鈥β縞贸mo lo pruebo?, e intentado de diferentes formas y lo mas cercano a un resultado que consigo es:
<main.Millas object at 0x000001BBC0D60FD0>

No entiendo la parte de los getters y setters, el c贸digo que ofrece no hace absolutamente nada, ya que usa otros par谩metros y de por s铆 el getter no hace nada, 驴Cu谩l es el punto de explicar algo si va a ser as铆?

Esto me recuerda a JavaScript y su Prototype

Re malo el contenido, si el profesor que escribi贸 esto no es nativo hablante del espa帽ol lo entender铆a pero hay muchas fallas en la redacci贸n que hacen que no se entienda el contenido y sobre todo en este tema que es muy muy importante, opino que mejor busquen informaci贸n del tema en otros medios.

Se puede entender de la siguiente manera, contradiciendo un poquito la descomposici贸n por el bien del ejemplo, partiremos de una clase llamada

Restaurante

donde entre sus muchos atributos y m茅todos existe un atributo llamado ingredientes (verduras, carne, especias, etc鈥) y un m茅todo llamado preparar_comida.

class Restaurante:
    def __init__(self, ingredientes = "fruta"):
        self.ingredientes = ingredientes
            
    def preparar_comida(self):
        "cocinar los ingredientes para hacer platillos"
        return f'picar {self.ingredientes} y servir en un plato'

#En este caso podemos ver que el constructor pasa el valor de "ingredientes" y este es el valor que usamos para preparar un platillo

#creaci贸n del objeto 
Restaurante_ensaladas = Restaurante()

#indicamos la fruta que tenemos
Restaurante_ensaladas.ingredientes = "Sandia"

#que ingredientes tiene este restaurante
print(Restaurante_ensaladas.ingredientes)

#prepara alg煤n platillos
print(Restaurante_ensaladas.preparar_comida())

En el ejemplo todo se ve muy funcional y cool; pero existe un detalle y es que de esta manera "self.ingredientes" es un valor al que todos tienen acceso. Se demuestra en

Restaurante_ensaladas.ingredientes 

Cualquier persona fuera de la clase restaurante la puede ver sin problemas.

Es como si cualquiera que este pasando por tu negocio, entrara a tu cocina para ver que tienes en tu alacena, eso esta un poco mal.

para resolver este problema decimos No amigo esos ingredientes son privados son del restaurante expresado en codigo hacemos:

self._ingredientes

pero como tal no restringe su uso, cualquier persona como el chef, el personal de limpieza, o el gerente, siguen teniendo acceso
a nuestros ingredientes, bueno pues para resolver dicho problema entran en escena nuestro amigo el GETTER

class Restaurante:
    def __init__(self, ingredientes = "fruta"):
        self._ingredientes = ingredientes
        self.encargado_almacen ="Ulises"
            
    def preparar_comida(self):
        "cocinar los ingredientes para hacer platillos"
        ingredientes = self.solicitar_ingredientes()
        return f'picar {ingredientes} y servir en un plato'
    
    #GETTER
    def solicitar_ingredientes(self):
        "ulises el encargado es la unica persona que puede darte los ingredientes"
        return self._ingredientes

Parece que nuestro GETTER (ulises) ayuda much铆simo pues ahora solo existe una forma de acceder a los ingredientes, pero ooo
sorpresa de la vida nuestro amigo ulises es un poco flojo esta aceptando de nuestros proveedores mercanc铆a en mal estado
el chef se quejo miren

#creaci贸n del objeto 
Restaurante_ensaladas = Restaurante()

#indicamos la fruta que tenemos
Restaurante_ensaladas._ingredientes = "Sandia en mal estado"

#prepara alg煤n platillos
print(Restaurante_ensaladas.preparar_comida())

>> picar Sandia en mal estado y servir en un plato

Necesitamos que nuestro amigo revise lo que esta recibiendo (la calidad, la cantidad, el precio, etc鈥) en esta parte nace nuestro SETTER (el proceso de validar)

class Restaurante:
    def __init__(self, ingredientes = "fruta"):
        self._ingredientes = ingredientes
        self.encargado_almacen ="Ulises"
            
    def preparar_comida(self):
        "cocinar los ingredientes para hacer platillos"
        ingredientes = self.solicitar_ingredientes()
        return f'picar {ingredientes} y servir en un plato'
    
    #GETTER
    def solicitar_ingredientes(self):
        "ulises el encargado es la unica persona que puede darte los ingredientes"
        return self._ingredientes
    
    #SETTER
    def agregar_ingredientes(self, valor):
        "ulises tiene la tarea de revisar el estado de los ingredientes"
        if not "Buen estado" in valor:
            raise ValueError(f"los ingredientes no cumplen la calidad que requiere el restaurante")
        
        self._ingredientes = valor

De esta forma cuando llegue el proveedor y nos traiga mercanc铆a en mal estado, podremos tomar medidas
garantiz谩ndole a nuestro chef que la ensalada siempre sera fresca

#llega el proveedor y agregaremos al inventario ingredientes
Restaurante_ensaladas.agregar_ingredientes = "Sandia en mal estado"
>> los ingredientes no cumplen la calidad que requiere el restaurante

Restaurante_ensaladas.agregar_ingredientes = "Sandia en Buen estado"
Restaurante_ensaladas.preparar_comida()
>> picar Sandia en Buen estado y servir en un plato

Espero que hasta este punto quede clara la necesidad de crear un GETTER y un SETTER
ahora tratare de explicar como terminamos trabajando con Property.

Para la siguiente parte nos vamos a poner un poco Gammers y minimalistas

nivel Principiante
en esta clase ya colocamos los getter y setter correspondientes y sus variables protegidas

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

nivel maestro
Utilizar 鈥減roperty鈥 pasandole los m茅todos Getter, setter y delete previamente definidos
de esta forma el manejo de informaci贸n es atraves de la propiedad y no de los m茅todos.

class C:
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value

    def delx(self):
        del self._x

    x = property(getx, setx, delx, "I'm the 'x' property.")

Nivel Experto

existe una forma mas cool de crear una propiedad y es con decoradores

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property. esta es la declaracion de la propiedad (queda como nuestro GETTER)"""
        return self._x

    @x.setter
    def x(self, value):
        """ahora decimos que de la propiedad X de esta funcion sera nuestro metodo SETTER"""
        self._x = value

    @x.deleter
    def x(self):
        """ahora decimos que de la propiedad X de esta funcion sera nuestro metodo Delete"""
        del self._x

Ahora cuando tenemos una instancia de la clase C y queremos ver que contiene hacemos

inst_C = C()

inst_C.x


#Cuando queremos redefinirlo
inst_C.x = 20


#si queremos borrarlo

del inst_C.x

Nota importante no es necesario usar el mismo nombre por m茅todo pueden tener cualquiera, en este caso use ese por que se ve simple
como el decorador esta modificando la funci贸n, pues no tengo problemas de ning煤n tipo

espero que esto les ayude a comprender este texto de una manera mas clara

Hola compa帽eros. Lean esta clase acompa帽ada de los videos de la secci贸n 鈥淐onceptos avanzados de funciones鈥 del curso profesional de Python, se complementan bastante y se entiende m谩s shidito鈥

aqu铆 puedes calarar mas acerca del tema:
https://ellibrodepython.com/decorador-property-python

se me complica mucho estos conceptos.

Creo que en el return tuvo un error. Faltaba a帽adir:

return wrapper()

wrapper a secas hace pensar a Python que es una variable y al no encontrar ninguna variable wrapper no retorna nada. Espero haber ayudado.

Explicacion de este codigo segun como yo lo veo:

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

Recordando que se lee de arriba para abajo y de izquierda a derecha

  1. Se define funcion_decoradora(funcion) que recibe como parametro una funcion.

  2. Se define zumbido()

  3. Se crea una NUEVA (ojo con esto) funcion llamada zumbido que es igual a funcion_decoradora(zumbido) *este ultimo "(zumbido) es del #2 de arriba.

Ahora:
Dentro del Wrapper se va a llamar a la funcion zumbido (que que se recibio como parametro en Funcion_decoradora(zumbido)

y va a retornar afuncion_decoradora(funcion): - lo siguiente:

Este es el 煤ltimo mensaje鈥
Buzzzzzz
Este es el primer mensaje 馃槈

ahora recordemos que se cre贸 una NUEVA funcion Zumbido (#3) que es la que se va a imprimir y que esta funcion Zumbido es igual a lo que retorna funcion_decoradora(funcion) que es igual a:

Este es el 煤ltimo mensaje鈥
Buzzzzzz
Este es el primer mensaje 馃槈

Es por eso que tenemos ese orden (al menos como yo lo entiendo, ya que en VScode me marca que en zumbido = funcion_decoradora(zumbido)
este primer zumbido es una funcion (y vendria a ser la que se va a imprimir al final)

Espero se haya entendido.

De todas maneras, aqu铆 se explica como funciona la funcion decoradora mucho mas simple usando la otra manera

https://youtu.be/DQXm6bIZgvk

Espero estos dos enlaces les sean de utilidad para comprender mejor los conceptos vistos en esta clase:

La verdad no le veo utilidad a los decoradores, solo te ahorra unas cuantas letras -_-

El m茅todo getter obtendr谩 el valor de la distancia que y el m茅todo setter se encargar谩 de a帽adir una restricci贸n. Tambi茅n debemos notar c贸mo distancia fue reemplazado por _distancia, denotando que es una variable privada.

class Millas:
	def __init__(self):
		self._distancia = 0

	# Funci贸n para obtener el valor de _distancia
	# Usando el decorador property
	@property
	def obtener_distancia(self):
		print("Llamada al m茅todo getter")
		return self._distancia

	# Funci贸n para definir el valor de _distancia
	@obtener_distancia.setter
	def definir_distancia(self, valor):
		if valor < 0:
			raise ValueError("No es posible convertir distancias menores a 0.")
		print("Llamada al m茅todo setter")
		self._distancia = valor

# Creamos un nuevo objeto 
avion = Millas()

# Indicamos la distancia
avion.distancia = 200

# Obtenemos su atributo distancia
>>> print(avion.definir..distancia)
Llamada al m茅todo getter
Llamada al m茅todo setter
200

A la hora de la verdad esto no es muy practico pues despu茅s de usar @property, @nnn.setter igual puedo acceder a mi atributo 鈥減rivado鈥 definido en mi metodo constructor y modificarlo a mi antojo. Por consiguiente no hay verdaderamente encapsulamiento.
No le veo funcionalidad l贸gica.

Aqu铆 se observa las funciones decoradores las comparo con las mu帽ecas rusas(Matrioshka) 贸 funciones anidadas

No soy experto pero la realidad es que el c贸digo tiene muchos typos que confunden y hacen m谩s dif铆cil lograr comprender las funcionalidades y al momento de probar el c贸digo este no siempre funciona y es necesario empezar a corregirlo, lo que reitero dificulta el aprendizaje, quiz谩 para este tipo de contenido sea mejor manejarlo con video para que el profesor quiz谩 se fije en estos errores y se corrijan d谩ndole fluidez al proceso de aprendizaje. @teamPlatzi

Esta clase merece un video. Por favor consideren que el profesor explique el contenido en vez de agregar documentos. Gracias.

Aqui les dejo dos ejemplos de encapsulamiento con y sin la herramienta de @property

Ejemplo 1 (sin @property)

class Millas_m1:

    def __init__(self):
        self._distancia =0
    def convertir_a_kilometros(self):
        return(self._distancia*1.609344)

    #Metodo getter
    def obtener_distancia(self):
        return self._distancia
    # Metodo setter
    def definir_distancia(self, valor):
        if valor < 0:
            raise ValueError("No es posible convertir distancias enores a 0.")
        self._distancia = valor

Para hacer uso de esta clase debemos realizar lo siguiente:

  1. instanciar un objeto
  2. definir el valor de la distancia (meidante el metodo difinir_dstancia())
  3. si se quiere ver el valor de la distancia usar el metodo obtener_distancia())
  4. utilizar le metodo <convertir_a_kilometros()> para obtener el resultado
    avion = Millas_m1()
    avion.definir_distancia(200)
    print(avion.obtener_distancia())
    print(avion.convertir_a_kilometros())

Ejemplo 2 (Utilizando @property)

class Millas_m2:
    
    def __init__(self):
        self._distancia = 0
    def convertir_a_kilometros(self):
        return(self._distancia*1.609344)
    
    @property
    def distancia(self):
        return self._distancia

    @distancia.setter
    def distancia(self, valor):
        if valor < 0:
            raise ValueError("No es posible convertir distancias enores a 0.")
        self._distancia = valor

Para hacer uso de esta clase debemos realizar lo siguiente:

  1. Instanciar un objeto
  2. Definir la distancia
  3. Si se quiere ver al valor de la distancia (avion.distancia)
  4. Obtener el resultado de la conversion
    avion = Millas_m2()
    avion.distancia = 200
    print(avion.distancia)
    print(avion.convertir_a_kilometros())

Es importante notar que en este caso para definir el valor de la distanica y para obtenerla utilizamos el mismo metodo <avion.distancia>, en este caso python interpreta esto sin confusiones porque estamos utilizando los decoradores y la funcion property.

Este video me ayudo mucho para aprender decoradores

https://www.youtube.com/watch?v=DQXm6bIZgvk

<class Automovil(object):
    def __init__(self, fabricante, vel_max):
        self.__fabricante = fabricante
        self.__vel_max = vel_max
    @property
    def fabricante(self):

        print("Ejecuci贸n del M茅todo GETTER")
        return self.__fabricante

    @fabricante.setter
    def fabricante(self, nuevo_fabricante):
        print("Ejecuci贸n del M茅todo SETTER\n")
        print("Modificando el nombre del fabricante\n")
        self.__fabricante = nuevo_fabricante
        print("El nombre del fabricante se ha modificado por")
        print(self.__fabricante)

    @fabricante.deleter
    def fabricante(self):
        print("Borrando fabricante")
        del self.__fabricante

    @property
    def vel_max(self):
        print("Ejecutando m茅todo GETTER de VEL MAX")
        return self.__vel_max
    
    @vel_max.setter
    def vel_max(self, nueva_vel_max):
        self.__vel_max = nueva_vel_max
        print("Modificando la velocidad m谩xima...")
        print("La velocidad m谩xima en km/h ahora es:")
        print(self.__vel_max)

    @vel_max.deleter
    def vel_max(self):
        print("Borrando el valor de velocidad m谩xima...")
        del self.__vel_max


Porshe = Automovil("Carrera 911", 350)
print(Porshe.fabricante)
Porshe.fabricante = "Cayman"
print(Porshe.fabricante)
Porshe.vel_max = 300
del Porshe.fabricante> 

Ya lo entendi, es mas facil si se ve desde esta perspectiva:

Suponiendo que tenemos esta clase


class Perros:
    
    def __init__(self, nombre, peso):
        self._nombre = nombre
        self._peso = peso
    
    @property
    def nombre(self):
        """
        Documentacion para el nombre del perro
        """
        return self._nombre

    @nombre.setter
    def nombre(self, nuevo):
        print("Modificando el nombre")
        self._nombre = nuevo
        print(f'El nombre se ha modificado por {self._nombre}')

    @nombre.deleter
    def nombre(self):
        print("Borrando el nombre")
        del self._nombre

    @property
    def peso(self):
        return self._peso

    @peso.setter
    def peso(self, new_peso):
        print(f'Modificando el peso')
        self._peso = new_peso
        print(f'El peso se ha modificado por {self._peso}')

    @peso.deleter
    def peso(self):
        del self._peso


Hulk = Perros("Hulk", 33)
print(Hulk.nombre)

Hulk.nombre = "Tony"
print(Hulk.nombre)

del Hulk.nombre

print(Hulk.peso)
Hulk.peso = 30
print(Hulk.peso)

del Hulk.peso

Con esos decoradores se definen los settes, gettes y deleters de cada propiedad, de esta forma podriamos tener varias funciones con el mismo nombre, por ejemplo def propiedad(self) e incluso con diferentes parametros como el caso de los setters. lo que los diferencia es que el decorador va a extender la funcionalidad de esa funcion al extenderle si es getter, setter o deleter

馃煝 Tal vez estoy equivocado, pero...

驴Alguien m谩s not贸 que el programa perdi贸 sentido y ya no convierte las millas a kil贸metros?

Excelente muchas gracias

P茅sima explicaci贸n. Deber铆an revisar esto para realizar una nueva redacci贸n.

Me gusta m谩s que se vean ejemplos que solo pura lectura

Esta clase deja mucho que desear

encontre este video acerca de getter y setters https://www.youtube.com/watch?v=RHIRwNcBms8

Una peque帽a ayuda para todos los que al igual que a mi, les cuesta entender el tema de los decoradores.
https://www.youtube.com/watch?v=DQXm6bIZgvk

Para todos los que obtengan el error:

NameError: name 'foo' is not defined

*foo = cualquier nombre

  1. El getter debe ser declarado primero y despu茅s del setter.
  2. En python 2, es necesario que la clase herede de la super clase Object para que los decoradores getter y setter funcionen:
Class MyClase(object)

Fuente: https://stackoverflow.com/questions/598077/why-does-foo-setter-in-python-not-work-for-me

Este video me ayud贸 mucho para comprender @proterty
https://youtu.be/jCzT9XFZ5bw

Les recomiendo ver este tutorial. Aunque en ingl茅s, est谩 mucho mejor explicado y comprensible

https://www.youtube.com/watch?v=FsAPt_9Bf3U&ab_channel=CoreySchafer

Estoy volviendo a ver las clases luego de practicar y entiendo un poco m谩s.

Hay que repasar m谩s de una vez para comprender mejor!

class Millas:
    
    def __init__(self, distance = 0):
        self.distance = distance
    
    
    def convert_to_kilometers(self):
        return(self.distance * 1.609344)
    
    
    # getter method
    def get_distance(self):
        return self._distance
    
    
    # setter method
    def define_distance(self, value):
        if value < 0:
            raise ValueError("It is not possible to convert distances smaller that zero")
        self._distance = value

Arreglar el c贸digo en la parte final de @property:

#Indicamos la distancia
avion.definir_distancia=200

#Obtenemo su atributo distancia
print(avion.definir_distancia)

De esta manera si correr谩.

En esta parte del codigo hay un error

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper() #Aqui hay un error, debria ser return wrapper, sin los parentesis '()'

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

Al ejecutarse da un error, la salida por consola es la siguiente:

..
Buzzzzzz
Este es el primer mensaje ;)
Traceback (most recent call last):
  File "C:\Users\Marco\Documents\prog basica\python\curso-python\funcion_decoradora.py", line 11, in <module>
    zumbido()
TypeError: 'NoneType' object is not callable

El codigo corregido seria asi:

def funcion_decoradora(funcion):
	def wrapper():
		print("Este es el 煤ltimo mensaje...")
		funcion()
		print("Este es el primer mensaje ;)")
	return wrapper

def zumbido():
	print("Buzzzzzz")

zumbido = funcion_decoradora(zumbido)

qeu al ejecutarse ya no da ningun error, su salida por consola es la siguiente:

Este es el 煤ltimo mensaje...
Buzzzzzz
Este es el primer mensaje ;)

Un 鈥渢ypo鈥 que en mi caso no me permitia entender bien el concepto de la funcion decoradora, pero es simplemente una forma abreviada de llamar a una funcion de orden superior.

Les recomiendo hacer debuggin con el editor de texto de su preferencia, poner un breakpoint desde el inicio del script y ver c贸mo en tiempo de ejecuci贸n se hacen los llamados a las funciones. Esto me ayud贸 a aclarar el comportamiento de los decoradores y property.

Hola, para que el c贸digo corra solo la quite () de la funci贸n wrapper en el retorno y lo puse solo as铆:

return wrapper

La verdad es que no estoy entendiendo nada bien todo esto. Poras que lo leo no logro entenderlo!

Hay un error en la salida del ultimoejemplo la salida es

Llamada al m茅todo setter
Llamada al m茅todo getter
200

Primero se invoca al setter cuando se asigna 200 a distancia, luego se invoca al metodo getter cuando se imprime la distancia

Adapt茅 este ejemplo de una de las p谩ginas que han mencionado en los comentarios, es un poco m谩s claro el uso de property

class Automovil:
    def __init__(self,nombre,marca):
        self._nombre=nombre
        self._marca=marca

    #getter
    @property
    def name(self): #El nombre que se le da a la funcion getter es el que se usar谩 m谩s abajo en el detter y deleter
        return self._nombre

    #setter
    @name.setter
    def name(self,nuevo):
        print("Modificando nombre :") #Mensaje para saber que estamos en el setter
        self._nombre=nuevo
        print("Nombre modificado por :")
        print(self._nombre)
    
    #deleter

    @name.deleter
    def name(self):
        print("Borrando nombre...")
        del self._nombre

    def marca(self):
        return self._marca

if __name__ == '__main__':
    carro_1=Automovil("Mitsubishi","Renult")
    print(carro_1.name) #getter
    carro_1.name="DeLorean" #Setter
    del carro_1.name```