Propiedades controladas
Es posible tomar los valores que el usuario nos pasa al inicializar los objetos
(en __init__) y guardarlos en variables privadas. Usamos el guión bajo inicial
en la propiedades para indicar que estas son internas de la clase y que no deberián
ser usadas. Esto es una convención pero no es obligatorio. Python no lanzará un error
si los usuarios de nuestra clase usan estas propiedades privadas.
1class Persona:
2 def __init__(self, nombre, apellido):
3 self._nombre = nombre
4 self._apellido = apellido
5
6 @property
7 def nombre(self):
8 return self._nombre
9
10 @property
11 def apellido(self):
12 return self._apellido
¿Que es @property?
Antes de la definición de una función (esto puede hacerse dentro y fuera de las clases)
es posible agregar lo que llamamos un decorador (o función decoradora). La sintaxis para
hacerlo es simplemente agregar esta línea sobre la definición de una función y comenzarla
con un @. Estos decoradores se usan para modificar a la función de
formas que por el momento exceden lo que necesitamos conocer.
En particular, @property es usado por las clases en Python para marcar que una
propiedad existe y que la funcion decorada
sera la encargada de atender las llamadas de lectura de esta propiedad.
La escritura/modificación de cada propiedad se hace de otra forma.
Hasta aquí estas propiedades son solo lectura
1victor = Persona('Victor', 'Fernandez')
2print(victor.apellido)
3# funciona y devule
4'Fernandez'
5# La siguiente línea fallará porque no esta todavía definido como funcionará
6# la asignación de esta propiedad
7victor.apellido = 'Gonzalez'
A las funciones de una clase para leer una propiedad se les llama getter
y las las funciones para escribir un nuevo valor a una propiedad se las llama
setter (por get y set del ingles: obtener y definir).
Formalmente ahora podemos decir que nuestras propiedades nombre y apellido
tienen getter pero no setter.
Veamos un ejemplo de setter para la propiedad nombre de la clase Persona:
1@nombre.setter
2def nombre(self, value):
3 # Antes de escribir mi variable privada _nombre, revisar que
4 # cuampla con los requisitos definidos
5 if type(value) != str:
6 # Si no es del tipo *string* lanzaremos (raise) un error
7 # (excepción) del Tipo Exception (hay otros tipos).
8 raise Exception('Nombre inválido. Solo string permitido')
9
10 # solo si pasa las validaciones (podrían ser varias)
11 # sobreescribimos nuestra variable privada con el nuevo valor.
12 self._nombre = value
La función definida para ser setter debe cumplir las siguientes condiciones:
Tener un decorador de la forma
@NOMBRE_DE_LA_PROPIEDAD.setter.Tener el mismo nombre que la función
getter.Incluir un parámetro para recibir el valor que el usuario quiere definir (usualmente lo llamaremos
value).
Es posible tambien definir propiedades personalizadas a gusto.
1@property
2def nombre_completo(self):
3 """ devuelve el nombre completo """
4 return f'{self._nombre} {self._apellido}'
5
6@property
7def nombre_formal(self):
8 """ devuelve el nombre completo """
9 return f'{self._apellido}, {self._nombre}'
Estas propiedades solo tienen sentido para ser leidas. Es por esto que no tienen una funcion setter.
Ejemplos de uso:
juan = Persona('juan carlos', 'perez')
print(juan.nombre_completo)
'juan carlos perez'
print(juan.nombre_formal)
'perez, juan carlos'
# Si intento asignar una propiedad que es solo lectura (no tienen una funcion setter)
# dará un error "can't set attribute" (no se puede asignar esta propiedad)
juan.nombre_completo = 'Nuevo nombre completo'
Las propiedades nombre y apellido se pueden leer y escribir.
Las propiedades nombre_completo y nombre_formal son simplemente combinaciones útiles
de otras propiedades básica. Solo se puede leer.
Funciones de mi clase
Es también posible definir funciones
def limpiar(self):
""" Mejorar el nombre y el apellido """
self._nombre = self._nombre.strip().title()
self._apellido = self._apellido.strip().title()
def encabezado(self, titulo, limpiar=True):
""" Genera y devuelve el nombre completo con
"Sr." "Sra." o algun otro titulo.
Opcionalmente se puede limpiar el nombre """
# limpiar el nombre si se solicita
if limpiar:
self.limpiar()
return f'{titulo} {self.nombre_completo}'
# podemos tambien emular el comportamiento de los strings
# e incluso copiar nombres de funciones de ellos
def lower(self):
""" devuelve el nombre completo en minusculas """
return self.nombre_completo.lower()
def upper(self):
""" devuelve el nombre completo en minusculas """
return self.nombre_completo.upper()
Algunos ejemplos de uso con estas nuevas funciones:
juan = Persona('juan carlos', 'perez')
print(juan.nombre_completo)
# 'juan carlos perez'
print(juan.nombre_formal)
# 'perez, juan carlos'
enc = juan.encabezado('Sr.', limpiar=False)
print(enc)
# 'Sr. juan carlos perez'
enc = juan.encabezado('Sr.')
print(enc)
# 'Sr. Juan Carlos Perez'
print(juan.nombre)
# El nombre fue limpiado
# 'Juan Carlos'
print(juan.upper())
# 'JUAN CARLOS PEREZ'
Nota importante: Las funciones se llaman con los parentesis (y parámetros si se requieren) y las propiedades se llaman sin ellos (y no puede requerir parámetros).
Código de la clase final aquí.
class Persona:
def __init__(self, nombre, apellido):
self._nombre = nombre
self._apellido = apellido
@property
def nombre(self):
return self._nombre
@nombre.setter
def nombre(self, value):
if type(value) != str:
raise Exception('Nombre inválido. Solo string permitido')
self._nombre = value
@property
def apellido(self):
return self._nombre
@nombre.setter
def apellido(self, value):
if type(value) != str:
raise Exception('Apellido inválido. Solo string permitido')
self._nombre = value
@property
def nombre_completo(self):
""" devuelve el nombre completo """
return f'{self._nombre} {self._apellido}'
@property
def nombre_formal(self):
""" devuelve el nombre completo en modo formal """
return f'{self._apellido}, {self._nombre}'
def limpiar(self):
""" Mejorar el nombre y el apellido """
self._nombre = self._nombre.strip().title()
self._apellido = self._apellido.strip().title()
def encabezado(self, titulo, limpiar=True):
""" Genera y devuelve el nombre completo con
"Sr." "Sra." o algun otro titulo.
Opcionalmente se puede limpiar el nombre """
# limpiar el nombre si se solicita
if limpiar:
self.limpiar()
return f'{titulo} {self.nombre_completo}'
# podemos tambien emular el comportamiento de los strings
# e incluso copiar nombres de funciones de ellos
def lower(self):
""" devuelve el nombre completo en minusculas """
return self.nombre_completo.lower()
def upper(self):
""" devuelve el nombre completo en minusculas """
return self.nombre_completo.upper()
Tareas
Hacer un PR a la clase Persona para agregar la propiedad edad.
Hacer un PR a la clase Carta para validar que el
paloes string en su funciónsetter.Hacer un PR a la clase Carta para validar que el
numeroes mayor que cero y menor o igual que 12 en su funciónsetter.
Algunos ejemplos de uso
"""
Clase Carta para juegos de cartas
"""
class Carta:
def __init__(self, numero, palo):
self._numero = numero
self._palo = palo
@property
def numero(self):
return self._numero
@numero.setter
def numero(self, value):
if type(value) != int:
raise Exception('Solo están permitidos numeros')
self._numero = value
@property
def palo(self):
return self._palo
@palo.setter
def palo(self, value):
self._palo = value
def __str__(self):
return f'{self.numero} de {self.palo}'
# Pruebas
carta1 = Carta(3, 'espada')
print(str(carta1))
'3 de espada'