Cuando escribes total = 0 o defines una función como def calcular_precio():, estás creando identificadores: los nombres con los que Python reconoce variables, funciones, clases y cualquier otro objeto en tu programa. Parece trivial hasta que Python te lanza un SyntaxError sin razón aparente, o hasta que dos personas del equipo nombran cosas de forma inconsistente y el código se vuelve ilegible.
Un identificador válido en Python sigue tres reglas simples: puede contener letras (incluyendo Unicode, aunque no es recomendable en código colaborativo), dígitos y el guion bajo _, pero nunca puede empezar con un dígito. Así que precio2 es válido, 2precio no lo es. _interno es válido. mi-variable no lo es, porque el guion - es el operador resta, no un carácter de nombre.
Luego están las palabras reservadas (keywords): tokens que el intérprete ya usa para construir la gramática del lenguaje. No puedes usarlas como identificadores porque Python las necesita para saber qué estás intentando hacer. if, for, return, class son ejemplos obvios. Puedes ver la lista completa en cualquier momento:
import keyword print(keyword.kwlist)
Esto importa el módulo keyword de la biblioteca estándar, que expone kwlist, una lista de strings con todas las palabras reservadas de la versión de Python que estás corriendo. Es útil cuando dudas sobre algún nombre.
Hay tres keywords que merecen atención especial: True, False y None. En Python 2 eran simples variables predefinidas, lo que significaba que podías escribir True = 1 y el intérprete lo aceptaba sin quejarse, con consecuencias desastrosas para cualquier código que dependiera de ellas. Desde Python 3 son palabras reservadas reales; el intérprete rechaza cualquier intento de reasignarlas directamente en el parser, antes de que tu código llegue a ejecutarse.
Python es completamente case-sensitive: Variable, variable y VARIABLE son tres identificadores distintos, sin ninguna relación entre sí. Esto no es un detalle menor; es una fuente de bugs silenciosos cuando escribes Total en una función y luego intentas leer total en otra.
El guion bajo _ merece su propio párrafo porque tiene múltiples roles según el contexto. Como nombre de variable suelto, es la convención para “no me importa este valor”. En el REPL interactivo, _ almacena automáticamente el resultado de la última expresión evaluada. Con prefijos y sufijos dobles como __nombre__, señala métodos especiales del modelo de datos (dunder methods). Con un prefijo simple _nombre, es una convención de “esto es interno, no lo uses desde fuera”. Son convenciones, no restricciones del lenguaje, excepto el doble prefijo __nombre dentro de una clase, que activa name mangling real.
Sobre convenciones de nombrado, Python tiene PEP 8 como referencia canónica:
snake_casepara variables, funciones y módulos:calcular_precio_total,nombre_usuarioUPPER_CASEpara constantes a nivel de módulo:MAX_REINTENTOS,URL_BASECamelCase(o PascalCase) para clases:ClienteActivo,ConexionDatabase
import keyword
# ─── constante de módulo ───────────────────────────────────────────────
MAX_INTENTOS = 3
# ─── clase en CamelCase ────────────────────────────────────────────────
class ConexionDatabase:
# prefijo _ → uso interno, no parte de la API pública
_pool_activo = False
def __init__(self, host: str, puerto: int = 5432):
self.host = host # atributo público
self.puerto = puerto
self._intentos = 0 # atributo interno
def conectar(self) -> bool:
while self._intentos < MAX_INTENTOS:
self._intentos += 1
exito = self._intentar_conexion()
if exito:
ConexionDatabase._pool_activo = True
return True
return False
def _intentar_conexion(self) -> bool:
# lógica real iría aquí; retornamos True para el ejemplo
return True
# ─── variable desechable con _ ─────────────────────────────────────────
coordenadas = (40.4168, -3.7038)
latitud, _ = coordenadas # _ indica que longitud no nos interesa
# ─── verificar si un nombre es keyword antes de usarlo dinámicamente ───
candidatos = ["total", "class", "precio_final", "return", "host"]
nombres_validos = [n for n in candidatos if not keyword.iskeyword(n)]
print(nombres_validos) # ['total', 'precio_final', 'host']
Qué hace cada decisión en este código
La constante MAX_INTENTOS en mayúsculas es una señal para cualquier lector: este valor no cambia durante la ejecución. Python no lo impide técnicamente, pero la convención establece un contrato implícito. Si alguien lo modifica, es su responsabilidad.
La clase ConexionDatabase sigue CamelCase porque representa un tipo, una categoría de objetos, no una acción ni un dato. El contraste con conectar() en snake_case es intencional: las funciones describen comportamiento.
_pool_activo y _intentos tienen el prefijo _ para comunicar que son detalles de implementación. Si alguien usa ConexionDatabase desde otro módulo y accede a _intentos, está violando un contrato no escrito. Herramientas como linters y los IDEs más comunes advierten sobre este acceso.
El desempaquetado latitud, _ = coordenadas es idiomático Python: cuando solo necesitas parte de un iterable, _ nombra lo que descartás. El intérprete asigna el valor igual, pero el nombre comunica la intención.
La comprensión de lista al final usa keyword.iskeyword(n), que es la forma correcta de validar nombres dinámicamente, por ejemplo, si estás generando código, leyendo nombres de columnas de una base de datos o construyendo un DSL.
Errores que debes conocer
Error: usar un nombre que parece válido pero colisiona con un builtin, como list, id, type o input. Python no lo prohíbe, pero pierdes acceso al builtin en ese scope y el bug puede aparecer lejos de donde lo causaste.
# ❌ Wrong
list = [1, 2, 3]
resultado = list("hola") # TypeError: 'list' object is not callable
# ✅ Right
numeros = [1, 2, 3]
resultado = list("hola") # ['h', 'o', 'l', 'a']
Renombrar la variable elimina la sombra (shadowing) del builtin; keyword.iskeyword no detecta builtins, pero builtins.__dict__ sí.
Error: asumir que el prefijo _ o __ es una restricción del lenguaje en lugar de una convención, y sorprenderse cuando Python sí permite acceder a _atributo desde fuera.
# ❌ Wrong (asumir que esto falla)
class Caja:
def __init__(self):
self._secreto = 42
c = Caja()
print(c._secreto) # 42 — Python lo permite sin error
# ✅ Right (respetar la convención aunque no sea forzada)
# No accedas a _secreto desde fuera de la clase;
# usa una propiedad pública si necesitas ese valor.
La única restricción real del lenguaje es el doble guion bajo __nombre dentro de una clase, que activa name mangling y cambia el nombre real del atributo a _NombreClase__nombre. El _ simple es solo vocabulario compartido entre desarrolladores.
N° 16