Junto con aquella pregunta de "¿dónde está el switch-case de Python?" que todos nos hacemos al comenzar con este maravilloso lenguaje se encuentra esta otra: "¿cómo declaro una constante en Python?". En este artículo te arrojo un poco de luz sobre este tema.
En Python las constantes no existen. Para guardar un valor constante se utiliza una variable pero, por convención, se utilizan las letras mayúsculas para darle nombre a dicha variable. Así, si al programar nos encontramos con un identificador en mayúsculas sabremos que no debe ser alterado.
Como ves, esto funciona un poco por "honor". No debemos tocar el valor de las variables nombradas con mayúsculas, como por ejemplo PI
o GRAVEDAD
o IVA
. Sucede un poco lo mismo que con los atributos privados de una clase. Si queremos podremos modificar su valor directamente, pero como buenos programadores que somos, no lo haremos porque sabemos que no se debe.
Profundizo un poco más en este tema a continuación y, si realmente necesitas constantes inalterables, te doy algunas alternativas y aproximaciones para intentar simularlas y que no se pueda (o se dificulte) modificar su valor.
Constantes en Python
Python no tiene una sintaxis dedicada para la declaración de constantes. Por tanto, tampoco tiene constantes en el sentido estricto de la palabra.
Así, para definir una constante en Python tienes que usar una variable y asumir que nunca va a cambiar.
Para definir constantes y para poder indicar a otros programadores que un determinado valor debe ser tratado como una constante es necesario utilizar la convención de que su nombre ha de ir en mayúsculas. Esto queda claro en el PEP 8.
De esta manera, si queremos declarar algunas constantes, como el valor del número pi, el número de segundos de una hora o la anchura de un componente determinado podríamos hacer lo siguiente:
PI = 3.14592
SEGUNDOS_HORA = 3600
ANCHURA_VENTANA = 760
print(f'{PI=}')
print(f'{SEGUNDOS_HORA=}')
print(f'{ANCHURA_VENTANA=}')
Resultado:
PI=3.14592 SEGUNDOS_HORA=3600 ANCHURA_VENTANA=760
Si te fijas no hay ninguna diferencia respecto de crear una variable y asignarle un valor, excepto por el nombre, que va todo en letras mayúsculas. Solo por este hecho de ir en letras mayúsculas ya se está comunicando que esos nombres en cuestión deben ser tratados como constantes y que, por tanto, no se les debe asignar otro valor en ninguna otra parte del código. Es más, de hacer eso, podrían surgir comportamientos inesperados y, probablemente, erróneos.
¡Cuidado! Tengo que insistir, pero esto es solo una convención. Python no proporciona ningún mecanismo para evitar nuevas asignaciones a dichas variables.
Es importante atender a las reglas para dar nombre a las variables:
- Deben estar compuestas de letras mayúsculas y números. No pueden empezar por un número.
- Se usa el carácter de subrayado o barra baja para separar distintas palabras dentro del mismo nombre. También se pueden usar como primer carácter del nombre, si quieres comunicar que la constante solo debe ser usada en el módulo que la contiene (constante privada).
- Pueden ser de cualquier longitud.
Recuerda que los nombres de variables y constantes deben ser identificativos. Es mejor usar un nombre largo y descriptivo que uno que pueda resultar ambiguo o del que no se entienda su intención. Intenta evitar abreviaturas en los nombres de las constantes.
Otra recomendación es definir las constantes al principio del fichero de programa, después de los import
. Esto dará información de qué constantes se usarán en el mismo.
Como podemos asumir que las constantes no cambiarán podemos hacer uso de ellas de manera global. Por lo tanto, no es necesario pasarlas como parámetros a las funciones que las necesiten (si tienen acceso a ellas).
Constantes integradas
Existen una serie de constantes integradas en Python que son constantes de verdad, constantes estrictas. No se puede cambiar sus valores. Las principales son las siguientes:
True
yFalse
. Que son los valores booleanos que ya conocemos.None
. Que representa la idea de null o ningún valor en Python.Ellipsis
o...
. Se usa, principalmente, para indicar en tu programa que falta código por escribir. También lo puedes usar para reemplazar apass
.
Si intentas cambiar el valor de algunas de estas constantes obtendrás el error SyntaxError
:
Te envío todos los días un consejo para que cada día seas mejor en Python.
Siempre sobre Python y programación.
Más de 2500 personas como tú los reciben cada día.
Día que estás fuera, consejo sobre Python que te pierdes.
Antes de suscribirte consulta aquí la
Finalidad de recogida y tratamiento de datos personales: enviarte boletín informativo de Python y comunicaciones comerciales.
Legitimación: tu consentimiento.
Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (GetResponse).
Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control.
Más información: política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal.
>>> True = 1
File "<stdin>", line 1
True = 1
^^^^
SyntaxError: cannot assign to True
>>> ... = 10
File "<stdin>", line 1
... = 10
^^^
SyntaxError: cannot assign to ellipsis here. Maybe you meant '==' instead of '='?
Sería interesante tener un mecanismo para lograr este efecto en Python, pero no lo hay. Lo que sí podemos es intentar simularlo. Sigue leyendo...
Aproximaciones y alternativas para declarar constantes en Python
Gracias a que Python es un lenguaje muy flexible, existen varias aproximaciones para la creación de constantes que no puedan ser modificadas (aunque esto no es del todo cierto, como veremos más tarde). Evidentemente, todas tienen sus desventajas, pero si realmente necesitas asegurarte todo lo posible que tus constantes no van a ser modificadas, pueden merecer la pena.
Estas aproximaciones implican la creación de clases pues nos permiten un nivel de configuración que no podemos tener a nivel de módulo, en donde cualquier variable puede ser modificada.
Constantes con el decorador @property
El decorador @property
nos permite establecer y asociar métodos getters y setters a un atributo.
Sin entrar demasiado en detalle en este decorador, pues no es el objetivo de este artículo, a @property
le podemos indicar cuáles serán los métodos encargados de gestionar el atributo en cuestión.
El decorador proviene de la función integrada property()
, sobre la cuál puedes leer más en la documentación oficial.
El pequeño truco aquí es que no definiremos ningún atributo ni ningún método set. Solo crearemos un método get cuyo nombre será el de la constante que queremos definir. Esto simula nuestra constante, pues no podemos darle valor a nuestro método.
Veamos un ejemplo con con la constante PI
. Vamos a crear una clase llamada Constante
:
class Constantes:
@property
def PI(self): # aquí definimos la constante
return 3.141592
constantes = Constantes() # creamos el objeto Constantes
print(constantes.PI) # accdemos a la constante PI
constantes.PI = 10 # intentamos alterar la constante
Como ves, creamos nuestro método PI
que actuará como una constante devolviendo siempre el mismo valor. No hay ningún atributo definido. Si intentamos alterar el valor de PI
obtendremos un error.
El código anterior muestra el siguiente resultado:
3.141592 Traceback (most recent call last): ... AttributeError: can't set attribute 'PI'
Pero hay un problema. @property
crea un atributo de clase en Constantes
que es un objeto de la clase property
. Esto hace que todo funcione. Pero podríamos alterar ese atributo, para darle un nuevo valor, con lo que el objeto property
desaparecería y en su lugar tendríamos un valor cualquiera. Si después accediéramos de nuevo a constantes.PI
obtendríamos ese nuevo valor. De forma que esta solución no es perfecta.
Lo vemos:
constantes = Constantes() # creamos el objeto Constantes
print(constantes.PI) # accedemos a la constante PI
Constantes.PI = 100 # alteramos el atributo de clase
print(constantes.PI) # accedemos a la supuesta constante PI, pero...
Ten esto muy en cuenta.
Vamos a ver otra aproximación que tiene más o menos el mismo problema.
Constantes con el atributo __slots__
Las clases de Python permiten definir un atributo especial de clase que contiene una secuencia de nombres que funcionarán como los atributos de los objetos de esa clase. Es el atributo __slots__
.
Si este atributo está definido no se pueden añadir más atributos de instancia a la clase. Al no definir el atributo __dict__
dentro de __slots__
, este tampoco se crea por defecto y se evita que se puedan añadir otros atributos.
Si quieres más información sobre __slots__
puedes leer la documentación oficial de Python.
Así, utilizando este atributo puedes crear una clase que no admita otros atributos. Además, crearemos un atributo de clase por cada constante que necesitemos y le daremos los valores que deseemos.
15 conceptos fundamentales que necesitas conocer para aprender y dominar Python
Te voy a hacer cuatro regalos (no uno, no dos, no tres, cuatro) que hablan de estos 15 conceptos fundamentales de Python: mi Tutorial Básico Interactivo de Python, una cheat sheet de Python en español: La Hoja de Referencia de Python, una guía de ChatGPT y Python y 30 ejercicios de Python (es un reto para ti).
Estos regalos son exclusivos para los suscriptores de Código Pitón.
Ahora podemos acceder a esos atributos a través de instancias de esa clase, pero no podremos modificarlos. Lo vemos:
class Constantes:
__slots__ = ()
PI = 3.141592
constantes = Constantes() # creamos el objeto Constantes
print(constantes.PI) # accedemos a la constante
constantes.PI = 100 # intentamos alterar el valor de la constante
Esto muestra por pantalla un mensaje similar al siguiente:
3.141592 Traceback (most recent call last): ... AttributeError: 'Constantes' object attribute 'PI' is read-only
Se nos dice que el atributo PI
es de solo lectura.
Listo, ya tenemos nuestra constante. Pero tenemos el mismo problema que en el caso anterior. ¿Qué pasa si intentamos alterar el atributo de clase?
constantes = Constantes() # creamos el objeto Constantes
print(constantes.PI) # accedemos a la constante
Constantes.PI = 100 # alteramos el valor del atributo de clase
print(constantes.PI) # accedemos a la supuesta constante
Y obtendremos lo siguiente:
3.141592 100
Otra solución imperfecta.
Vamos con otra posibilidad.
Constantes con namedtuple
La función namedtuple
de la librería estándar collections
nos permite crear tuplas (recordemos que las tuplas son inalterables) y ponerle un nombre a cada valor de las mismas.
Además nos permite acceder a los valores utilizando la notación del punto y el nombre del valor de la tupla en lugar de usar la notación de corchetes.
Puedes leer más sobre namedtuple
en la documentación oficial de collections
.
Vamos entonces a crear una tupla con tantos valores como constantes necesitemos (o varias tuplas de un solo valor, o una combinación de ambas estrategias). A cada valor le pondremos el nombre de la constante representada.
Para ello primero debemos invocar a la función namedtuple
que necesitará al menos dos parámetros. El nombre del tipo de datos que representará esa tupla (le pondremos Constantes
) y el nombre de, al menos, una constante.
La llamada a namedtuple
nos devuelve otra función que necesitará tantos parámetros como nombres de constantes hayamos indicado, así que la invocaremos indicando los valores de dichas constantes.
Y ya podemos usar nuestra tupla con nombres con sus constantes (en este caso solo una):
from collections import namedtuple
constantes = namedtuple('Constantes', 'PI')(3.141592) # creamos la tupla con nombres
print(constantes.PI) # accedemos al valor de nuestra constante
constantes.PI = 100 # intentamos alterar su valor
Y nuevamente tendremos un error al intentar alterar el valor de PI
de este tipo: AttributeError: can't set attribute
.
Pero... de nuevo el problema de siempre. namedtuple
nos ha creado una clase Constantes
que contiene un atributo de clase llamado PI
. Ese atributo es una referencia a la función _tuplegetter
definida en el módulo collections
.
¿Qué pasa si alteramos el valor de dicho atributo de clase? Lo podemos hacer así: constantes.__class__.PI = 100
. Que a partir de entonces perdemos el acceso al valor definido en nuestra tupla en favor del nuevo valor asignado a PI
.
Otra solución que tampoco es perfecta.
Día que estás fuera, consejo sobre Python que te pierdes.
Antes de suscribirte consulta aquí la
Finalidad de recogida y tratamiento de datos personales: enviarte boletín informativo de Python y comunicaciones comerciales.
Legitimación: tu consentimiento.
Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (GetResponse).
Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control.
Más información: política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal.
¿Vemos otra? ¡Vamos!
Constantes con @dataclass
El decorador @dataclass
nos permite crear clases de tipo registro que contienen solo datos principalmente. Lo interesante es que añade a nuestra clase todos los métodos (métodos dunder, que comienzan y acaban con __
) de gestión de los objetos a partir de los atributos de clase definidos, de forma que la velocidad de desarrollo se incrementa. Puede leer sobre las clases de datos en el PEP 557 y en la documentación oficial.
Además, el decorador permite un parámetro denominado frozen
que permite hacer los objetos de dicha clase inmutables, es decir, que no se pueda alterar el valor de sus atributos. Dicho de otra manera, logramos tener objetos cuyos valores son constantes.
Vamos a ver cómo usaríamos el decorador para definir nuestra constante PI
:
from dataclasses import dataclass
@dataclass(frozen=True)
class Constantes:
PI = 3.141592
constantes = Constantes() # creamos nuestro objeto de constantes
print(constantes.PI) # accedemos al valor de la constante
constantes.PI = 100 # intentamos alterar la constante
Y tendremos un resultado similar a:
3.141592 Traceback (most recent call last): ... dataclasses.FrozenInstanceError: cannot assign to field 'PI'
Bueno, creo que a estas altura, si te has leído el resto de alternativas, te puedes ir imaginando lo que pasa. Haz tú mismo la prueba de intentar alterar el atributo de clase PI
y obtendrás los resultados de las anteriores aproximaciones.
Hay más opciones, pero voy a parar aquí. No tiene sentido seguir.
Conclusiones
La conclusión aquí es evidente: no existen las constantes estrictas en Python y tampoco se pueden simular a la perfección. Cada alternativa para hacerlo tiene sus problemas.
Hay que entender que, conceptualmente, sí que existen las constantes en Python. Es nuestro deber ser fiel a la filosofía de Python en la que aquellas variables definidas con mayúsculas pasan a ser constantes y no pueden ser modificadas.
También debemos instruir a aquellos desarrolladores con los que trabajemos para que entiendan la manera de trabajar con Python (al menos en lo que a constantes se refiere).
Si es necesario, documentaremos el código para evitar problemas y posibles modificaciones de estas constantes.
Y nada más, si te ha parecido de utilidad esta entrada, o si has aprendido algo, considera suscribirte a la lista de correo de Código Pitón. Te llegarán regalitos nada más suscribirte y trucos y consejos de vez en cuando.
¡Feliz programación!
P. S. Por si te quedaste con las dudas de eso del switch de Python: cómo hacer switch en Python.
Te envío todos los días un consejo para que cada día seas mejor en Python.
Siempre sobre Python y programación.
Más de 2500 personas como tú los reciben cada día.
Día que estás fuera, consejo sobre Python que te pierdes.
Antes de suscribirte consulta aquí la
Finalidad de recogida y tratamiento de datos personales: enviarte boletín informativo de Python y comunicaciones comerciales.
Legitimación: tu consentimiento.
Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (GetResponse).
Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control.
Más información: política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal.