Palabras reservadas en Python
Python tiene un conjunto de palabras reservadas que no podemos utilizar para nombrar variables ni funciones, ya que las reserva internamente para su funcionamiento.
Por ejemplo, no podemos llamar a una función True
, y si intentamos hacerlo, tendremos un SyntaxError
. Esto es lógico ya que Python usa internamente True
para representar el tipo booleano.
def True():
pass
# SyntaxError: invalid syntax
Análogamente, no podemos llamar a una variable is
ya que se trata del operador de identidad.
is = 4
# SyntaxError: invalid syntax
Resulta lógico que no se nos permita realizar esto, ya que de ser posible, podríamos romper el lenguaje. Algo muy importante a tener en cuenta es que palabras como list
no están reservadas, y esto es algo que puede generar problemas. El siguiente código crea una lista usando la función estándar de Python list()
.
a = list("letras")
print(a)
# ['l', 'e', 't', 'r', 'a', 's']
Sin embargo, y aunque pueda parece extraño, podemos crear una función con ese nombre. Al hacer esto, nos estamos cargando la función list()
de Python, y por lo tanto al intentar hacer la llamada anterior falla, ya que nuestra función en este caso no acepta argumentos. Mucho cuidado con esto.
def list():
print("Funcion list")
a = list("letras")
# TypeError: list() takes 0 positional arguments but 1 was given
Pero volviendo a las palabras reservadas, Python nos ofrece una forma de acceder a estas palabras programmatically, es decir, a través de código. Aquí tenemos un listado con todas las palabras reservadas.
import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert',
# 'async', 'await', 'break', 'class', 'continue',
# 'def', 'del', 'elif', 'else', 'except', 'finally',
# 'for', 'from', 'global', 'if', 'import', 'in', 'is',
# 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise',
# 'return', 'try', 'while', 'with', 'yield']
Vistas ya las palabras reservadas de Python, a continuación explicaremos para que sirve cada una de ellas y las pondremos en contexto.
Condicionales: if, elif, else
El uso del if y los condicionales en general son la base de cualquier lenguaje de programación. Son usados para alterar la línea de ejecución del programa en función de determinadas condiciones.
En el siguiente ejemplo podemos ver su uso. De los tres bloques, sólo se ejecutará uno de ellos, el cual cumpla la condición establecida sobre lenguaje
.
lenguaje = "Python"
if lenguaje == "Python":
print("Estoy de acuerdo, Python es el mejor")
elif lenguaje == "Java":
print("No me gusta, Java no mola")
else:
print("Ningún otro lenguaje supera a Python")
# Salida: Estoy de acuerdo, Python es el mejor
Bucles: while, for, break, continue
El while y for permiten crear bucles que ejecutan una sección de código repetidas veces. Por otro lado el continue y el break permiten realizar alteraciones sobre el bucle.
El while
ejecuta su sección de código mientras se cumpla una determinada condición.
x = 0
while x < 3:
print(x)
x += 1
# Salida: 0, 1, 2
El for
permite iterar clases iterables, ejecutando la sección de código tantas veces como elementos tiene el iterable.
for i in range(3):
print(i)
# Salida: 0, 1, 2
El continue
salta hasta el final del bloque, dejando sin ejecutar lo restante, pero continúa en la siguiente iteración.
for i in range(3):
if i == 1:
continue
print(i)
# Salida: 0, 2
Por último, el break
rompe la ejecución del bucle, saliendo del mismo.
x = 0
while True:
print(x)
if x == 2:
break
x += 1
# Salida: 0, 1, 2
Valores: False, True, None
False
, True
y None
son valores que pueden ser asignados a variables, siendo los dos primeros booleanos y el último algo parecido al null
de otros lenguajes de programación.
Si realizamos una comparación usando el operador relacional ==
se nos devolverá True
o False
.
x = (5 == 1)
print(x)
# Salida: False
También podemos asignar nosotros True
a una variable.
x = True
if x:
print("Python!")
# Salida: Python!
Por otro lado None
se devuelve por defecto cuando una función no cuenta con un return
.
def mi_funcion():
pass
print(mi_funcion())
# Salida: None
Operadores lógicos: and, or, not
El and
, or
y not
son operadores lógicos que actúan sobre valores booleanos. El primero es verdadero si y solo si todos los operandos son verdaderos. El segundo devuelve verdadero si al menos un elemento es verdadero. Y por último, el not
invierte verdadero por falso y viceversa.
print(True and False) # False
print(True or False) # True
print(not True) # False
Funciones: def, return, lambda, pass, yield
Todas ellas relacionadas con las funciones. El uso de def
nos permite crear una función.
def funcion_suma(a, b):
print("La suma es", a + b)
funcion_suma(3, 5)
# Salida: La suma es 8
Si queremos que la función devuelva uno o varios valores, podemos usar return
.
def funcion_suma(a, b):
return a + b
suma = funcion_suma(3, 5)
print("La suma es", suma)
# Salida: La suma es 8
El uso de lambda
nos permite crear funciones lambda, una especie de funciones “para vagos”. Dichas funciones no tienen un nombre per se, salvo asignado explícitamente.
print("La suma es", (lambda a, b: a + b)(3, 5))
# Salida: La suma es 8
Por otro lado, podemos usar pass
cuando no queramos definir la función, es decir si la queremos dejar en blanco por el momento. Nótese que también puede ser usado en clases, estructuras de control, etc.
def funcion_suma(a, b):
pass
Por último, yield
está asociado a los generadores y las corrutinas, un concepto un tanto avanzado pero muy interesante. En el siguiente generador vemos como se generan tres valores, obteniendo uno cada vez que iteramos el generador.
def generador():
n = 1
yield n
n += 1
yield n
n += 1
yield n
for i in generador():
print(i)
# Salida: 1, 2, 3
Los generadores pueden ser usados para generar secuencias infinitas de valores, sin que tengan que ser almacenados a priori, siendo creados bajo demanda. Este es una utilidad muy importante trabajando con listas muy grandes, cuyo almacenamiento en memoria sería muy costoso.
Clases: class
El uso de class
nos permite crear clases. Las clases son el núcleo de la programación orientada objetos, y son una especie de estructura de datos que agrupa un conjunto de funciones (métodos) y variables (atributos).
class MiClase:
def __init__(self):
print("Creando objeto de MiClase")
objeto = MiClase()
# Salida: Creando objeto de MiClase
Excepciones: assert, try, except, finally, raise
Las palabras clave assert
, try
, except
, finally
y raise
están relacionadas con las excepciones, y nos permiten tratar el qué hacer cuando las cosas no salen como esperamos. El siguiente código intenta hacer un cast de cadena a entero, manejando un posible error.
- Si
x="10"
el casteo se realiza sin problemas, ya que es posible representar esa cadena como un entero. Sin embargo hay que estar preparados siempre para lo peor. - Si
x="a"
no se podría hacerint()
y tendríamos un error. Si no manejamos este error, el programa se pararía, y esto no es algo deseable. El uso detry
,except
yfinally
nos permite controlar dicho error y actuar en consecuencia sin que el programa se pare.
x = "10"
valor = None
try:
valor = int(x)
except Exception as e:
print("Hubo un error:", e)
finally:
print("El valor es", valor)
# Salida: El valor es 10
Variables: global, nonlocal
El uso de global
permite realizar lo siguiente, y de no usarlo tendríamos un UnboundLocalError
. Aunque puede resultar muy útil, mucho cuidado con las variables globales.
a = 0
def suma_uno():
global a
a = a + 1
suma_uno()
print(a)
# Salida: 1
El uso de nonlocal
es útil cuando tenemos funciones anidadas. En el siguiente ejemplo podemos ver como cuando funcion_b
modifica x
, también afecta a la x
de la funcion_a
, ya que la hemos declarado como nonlocal
. Te invitamos a que elimines el nonlocal
y veas el comportamiento.
def funcion_a():
x = 10
def funcion_b():
nonlocal x
x = 20
print("funcion_b", x)
funcion_b()
print("funcion_a", x)
funcion_a()
# Salida:
# funcion_b 20
# funcion_a 20
Al usar nonlocal
, el valor de x
se modifica en el ámbito de funcion_a
desde funcion_b
.
Sin nonlocal
, Python trataría x
dentro de funcion_b
como una variable local a esa función, y el valor de x
en funcion_a
no se modificaría.
Módulos: from, import
El uso de from
e import
nos permite importar módulos o librerías, tanto estándar de Python como externas o definidas por nosotros. En ejemplos como este es donde podemos ver que la sintaxis de Python se asemeja bastante al lenguaje natural: de collections importa namedtuple.
from collections import namedtuple
Pertenencia e Identidad: in, is
El uso de in
nos permite saber si un determinado elemento está en una clase iterable, devolviendo True
en el caso de que sea cierto.
lista = ["a", "b", "c"]
print("a" in lista)
# Salida: True
El uso de is
nos permite saber si dos variables apuntan en realidad al mismo objeto. Por debajo se usa la función id()
y es importante notar que la igualdad ==
no implica que is
sea True
.
a = [1, 2]
b = [1, 2]
c = a
print(a is b) # False
print(a is c) # True
Eliminar variables: del
El uso de del
nos permite eliminar una variable del scope, pudiendo resultar útil cuando trabajamos con variables que almacenan gran cantidad de datos. Es una manera explícita de indicar que ya no queremos una variable, pero no olvidemos que Python tiene garbage collector.
a = 10
del a
print(a)
# Salida: NameError: name 'a' is not defined
Context Managers: with, as
El uso de with
y as
es muy utilizado a la hora de manejar ficheros, pero en realidad pertenecen a los context managers o gestores de contexto, un concepto algo avanzado.
with open('fichero.txt', 'r') as file:
print(file.read())
Concurrencia: async, await
El uso de async
y await
nos permite ejecutar procesos de manera concurrente en vez de secuencial. Imaginemos un proceso()
que tarda 10 segundos en ejecutarse, ya que realiza una petición a una base de datos que lo bloquea durante ese tiempo. Sin esta herramienta, si quisiéramos ejecutar 3 veces el proceso tardaríamos 30 segundos, ya que por defecto se ejecutan de manera secuencial, hasta que uno no acaba no pasamos al siguiente.
Sin embargo, creando una función async
y usando await
, podemos paralelizar la ejecución de los procesos, aprovechando el tiempo “muerto” mientras se retorna al await
. En el siguiente ejemplo podemos ver como se tarda unos 10 segundos en ejecutar los 3 procesos.
import asyncio
async def proceso(id_proceso):
print("Empieza proceso:", id_proceso)
await asyncio.sleep(10)
print("Acaba proceso:", id_proceso)
async def main():
await asyncio.gather(proceso(1), proceso(2), proceso(3))
asyncio.run(main())
# Salida:
# Empieza proceso: 1
# Empieza proceso: 2
# Empieza proceso: 3
# Acaba proceso: 1
# Acaba proceso: 2
# Acaba proceso: 3