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
palo
es string en su funciónsetter
.Hacer un PR a la clase Carta para validar que el
numero
es 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'