Capítulo 1. Fundamentos de Python para DevOps

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

DevOps, la combinación del desarrollo de software con las operaciones de tecnología de la información, ha sido un campo candente durante la última década. Se han roto las fronteras tradicionales entre el desarrollo, la implementación, el mantenimiento y la garantía de calidad del software, lo que ha permitido la creación de equipos más integrados. Python ha sido un lenguaje popular tanto en las operaciones informáticas tradicionales como en DevOps debido a su combinación de flexibilidad, potencia y facilidad de uso.

El lenguaje de programación Python se hizo público a principios de los años 90 para su uso en la administración de sistemas. Ha tenido un gran éxito en este ámbito y ha conseguido una amplia adopción. Python es un lenguaje de programación de propósito general que se utiliza en casi todos los ámbitos. Las industrias de efectos visuales y del cine lo adoptaron. Más recientemente, se ha convertido en el lenguaje de facto de la ciencia de datos y el aprendizaje automático (AM), y se ha utilizado en sectores que van desde la aviación a la bioinformática. Python cuenta con un amplio arsenal de herramientas para cubrir las necesidades más diversas de sus usuarios. Aprender toda la Biblioteca Estándar de Python (las capacidades que vienen con cualquier instalación de Python) sería una tarea desalentadora. Intentar aprender todos los paquetes de terceros que animan el ecosistema Python sería una empresa inmensa. La buena noticia es que no necesitas hacer esas cosas. Puedes convertirte en un poderoso profesional de DevOps aprendiendo sólo un pequeño subconjunto de Python.

En este capítulo, nos basamos en nuestras décadas de experiencia en Python DevOps para enseñar sólo los elementos del lenguaje que necesitas. Éstas son las partes de Python DevOps que se utilizan a diario. Forman la caja de herramientas esencial para hacer las cosas. Una vez que domines estos conceptos básicos, podrás añadir herramientas más complicadas, como verás en capítulos posteriores.

Instalar y ejecutar Python

Si quieres probar el código de este resumen, necesitas tener instalado Python 3.7 o posterior (la última versión es la 3.8.0 en el momento de escribir esto) y acceso a una shell. En macOS X, Windows y la mayoría de las distribuciones de Linux, puedes abrir la aplicación terminal para acceder a una shell. Para ver qué versión de Python estás utilizando, abre un intérprete de comandos y escribe python --version:

$ python --version
Python 3.8.0

Los instaladores de Python pueden descargarse directamente del sitio web Python.org. También puedes utilizar un gestor de paquetes como Apt, RPM, MacPorts, Homebrew, Chocolatey o muchos otros.

La cáscara de Python

La forma más sencilla de ejecutar Python es utilizar el intérprete interactivo incorporado. Sólo tienes que escribir python en un intérprete de comandos. Entonces podrás ejecutar interactivamente sentencias Python. Escribe exit() para salir del intérprete.

$ python
Python 3.8.0 (default, Sep 23 2018, 09:47:03)
[Clang 9.0.0 (clang-900.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 1 + 2
3
>>> exit()

Scripts en Python

El código Python se ejecuta desde un archivo con extensión .py:

# This is my first Python script
print('Hello world!')

Guarda este código en un archivo llamado hola.py. Para invocar el script, en un intérprete de comandos ejecuta python seguido del nombre del archivo:

$ python hello.py
Hello world!

Los scripts de Python son la forma en que se ejecuta la mayor parte del código Python de producción.

IPython

Además del intérprete de comandos interactivo incorporado, hay varios intérpretes de comandos interactivos de terceros que ejecutan código Python. Una de las más populares es IPython. IPython ofrece introspección (la capacidad de obtener dinámicamente información sobre objetos), resaltado de sintaxis, comandos mágicos especiales (que trataremos más adelante en este capítulo) y muchas más funciones, lo que hace que sea un placer utilizarlo para explorar Python. Para instalar IPython, utiliza el gestor de paquetes de Python, pip:

$ pip install ipython

Ejecutarlo es similar a ejecutar el intérprete de comandos interactivo incorporado descrito en la sección anterior:

$ ipython
Python 3.8.0 (default, Sep 23 2018, 09:47:03)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.5.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: print('Hello')
Hello

In [2]: exit()

Cuadernos Jupyter

El proyecto Jupyter, derivado del proyecto iPython, permite crear documentos que contengan texto, código y visualizaciones. Estos documentos son potentes herramientas para combinar código en ejecución, resultados y texto formateado. Jupyter permite la entrega de documentación junto con el código. Ha alcanzado una gran popularidad, especialmente en el mundo de la ciencia de datos. A continuación te explicamos cómo instalar y ejecutar los cuadernos Jupyter:

$ pip install jupyter
$ jupyter notebook

Este comando abre una pestaña del navegador web que muestra el directorio de trabajo actual. Desde aquí, puedes abrir las libretas existentes en el proyecto actual o crear otras nuevas.

Programación procedimental

Si has estado en contacto con la programación, probablemente habrás oído términos como programación orientada a objetos (POO) y programación funcional. Se trata de diferentes paradigmas arquitectónicos utilizados para organizar programas. Uno de los paradigmas más básicos, la programación procedimental, es un excelente punto de partida. La programación procedimental es la emisión de instrucciones a un ordenador en una secuencia ordenada:

>>> i = 3
>>> j = i +1
>>> i + j
7

Como puedes ver en este ejemplo, hay tres sentencias que se ejecutan en orden desde la primera línea hasta la última. Cada sentencia utiliza el estado producido por las anteriores. En este caso, la primera sentencia asigna el valor 3 a una variable llamada i. En la segunda sentencia, el valor de esta variable se utiliza para asignar un valor a una variable llamada j, y en la tercera sentencia, se suman los valores de ambas variables. No te preocupes todavía por los detalles de estas sentencias; fíjate en que se ejecutan en orden y se basan en el estado creado por las sentencias anteriores.

Variables

Una variable es un nombre que apunta a algún valor. En el ejemplo anterior, las variables son i y j. Las variables en Python pueden asignarse a nuevos valores:

>>> dog_name = 'spot'
>>> dog_name
'spot'
>>> dog_name = 'rex'
>>> dog_name
'rex'
>>> dog_name = 't-' + dog_name
>>> dog_name
't-rex'
>>>

Las variables de Python utilizan tipado dinámico. En la práctica, esto significa que pueden reasignarse a valores de distintos tipos o clases:

>>> big = 'large'
>>> big
'large'
>>> big = 1000*1000
>>> big
1000000
>>> big = {}
>>> big
{}
>>>

Aquí la misma variable se asigna a una cadena, a un número y a un diccionario. Las variables pueden reasignarse a valores de cualquier tipo.

Matemáticas básicas

Las operaciones matemáticas básicas como la suma, la resta, la multiplicación y la división se pueden realizar utilizando los operadores matemáticos incorporados:

>>> 1 + 1
2
>>> 3 - 4
1
>>> 2*5
10
>>> 2/3
0.6666666666666666

Ten en cuenta que el símbolo // es para la división de enteros. El símbolo ** crea un exponente, y % es el operador de módulo:

>>> 5/2
2.5
>>> 5//2
2
>>> 3**2
9
>>> 5%2
1

Comentarios

Los comentarios son texto ignorado por el intérprete de Python. Son útiles para documentar el código y pueden ser aprovechados por algunos servicios para proporcionar documentación independiente. Los comentarios de una sola línea se delimitan anteponiendo #. Un comentario de una sola línea puede empezar al principio de una línea o en cualquier punto posterior. Todo lo que sigue a # forma parte del comentario hasta que se produce un nuevo salto de línea:

 # This is a comment
 1 + 1 # This comment follows a statement

Los comentarios multilínea se encierran a su vez en bloques que comienzan y terminan con """ o ''':

"""
This statement is a block comment.
It can run for multiple lines
"""

'''
This statement is also a block comment
'''

Funciones incorporadas

Las funciones son sentencias agrupadas como una unidad. Invocas una función escribiendo el nombre de la función, seguido de un paréntesis. Si la función tiene argumentos, éstos aparecen dentro de los paréntesis. Python tiene muchas funciones incorporadas. Dos de las funciones incorporadas más utilizadas son print y range.

Imprimir

La función print produce una salida que el usuario de un programa puede ver. Es menos relevante en entornos interactivos, pero es una herramienta fundamental a la hora de escribir scripts en Python. En el ejemplo anterior, el argumento de la función print se escribe como salida cuando se ejecuta el script:

# This is my first Python script
print("Hello world!")

$ python hello.py
Hello world!

print se puede utilizar para ver el valor de una variable o para dar información sobre el estado de un programa. print generalmente da salida al flujo de salida estándar y es visible como salida del programa en un intérprete de comandos.

Gama

Aunque range es una función incorporada, técnicamente no es una función en absoluto. Es un tipo que representa una secuencia de números. Al llamar al constructor range(), se devuelve un objeto que representa una secuencia de números. Los objetos Rango cuentan a través de una secuencia de números. La función range admite hasta tres argumentos enteros. Si sólo aparece un argumento, la secuencia estará representada por los números desde cero hasta ese número, pero sin incluirlo. Si aparece un segundo argumento, éste representa el punto de partida, en lugar de la opción por defecto de empezar desde 0. El tercer argumento se puede utilizar para especificar la distancia entre pasos, y por defecto es 1.

>>> range(10)
range(0, 10)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
>>> list(range(5, 10, 3))
[5, 8]
>>>

range mantiene una huella de memoria pequeña, incluso en secuencias largas, ya que sólo almacena los valores de inicio, parada y paso. La función range puede iterar a través de secuencias largas de números sin limitaciones de rendimiento.

Control de ejecución

Python tiene muchas construcciones para controlar el flujo de ejecución de sentencias. Puedes agrupar sentencias que desees ejecutar juntas como un bloque de código. Estos bloques pueden ejecutarse varias veces utilizando los bucles for y while o ejecutarse sólo en determinadas condiciones utilizando las sentencias if, los bucles while o los bloques try-except. Utilizar estas construcciones es el primer paso para aprovechar el poder de la programación. Los distintos lenguajes delimitan los bloques de código utilizando diferentes convenciones. Muchos lenguajes con sintaxis similar al lenguaje C (un lenguaje muy influyente utilizado en la escritura de Unix) utilizan llaves alrededor de un grupo de sentencias para definir un bloque. En Python, se utiliza la indentación para indicar un bloque. Las sentencias se agrupan por indentación en bloques que se ejecutan como una unidad.

Nota

Al intérprete de Python no le importa si utilizas tabuladores o espacios para la indentación, siempre que seas coherente. Sin embargo, la guía de estilo de Python, PEP-8, recomienda utilizar cuatro espacios en blanco para cada nivel de indentación.

if/elif/else

if/elif/else son formas comunes de bifurcarse entre decisiones en el código. Un bloque directamente después de una sentencia se ejecuta si esa sentencia se evalúa como : if True

>>> i = 45
>>> if i == 45:
...     print('i is 45')
...
...
i is 45
>>>

Aquí utilizamos el operador ==, que devuelve True si los elementos son iguales y False si no lo son. Opcionalmente, este bloque puede seguir a una sentencia elif o else con un bloque de acompañamiento. En el caso de una sentencia elif, este bloque sólo se ejecuta si elif se evalúa como True:

>>> i = 35
>>> if i == 45:
...     print('i is 45')
... elif i == 35:
...     print('i is 35')
...
...
i is 35
>>>

Múltiples bucles elif pueden agregarse juntos. Si estás familiarizado con las sentencias switch de otros lenguajes, esto simula el mismo comportamiento de elegir entre varias opciones. Añadir una sentencia else al final ejecuta un bloque si ninguna de las otras condiciones se evalúa como True:

>>> i = 0
>>> if i == 45:
...     print('i is 45')
... elif i == 35:
...     print('i is 35')
... elif i > 10:
...     print('i is greater than 10')
... elif i%3 == 0:
...     print('i is a multiple of 3')
... else:
...     print('I don't know much about i...')
...
...
i is a multiple of 3
>>>

Puedes anidar sentencias if, creando bloques que contengan sentencias if que sólo se ejecuten si una sentencia if externa es True:

>>> cat = 'spot'
>>> if 's' in cat:
...     print("Found an 's' in a cat")
...     if cat == 'Sheba':
...         print("I found Sheba")
...     else:
...         print("Some other cat")
... else:
...     print(" a cat without 's'")
...
...
Found an 's' in a cat
Some other cat
>>>

para Bucles

for Los bucles te permiten repetir un bloque de sentencias (un bloque de código) una vez por cada miembro de una secuencia (grupo ordenado de elementos). A medida que iteras por la secuencia, el bloque de código puede acceder al elemento actual. Uno de los usos más comunes de los bucles es iterar a través de un objeto para realizar una tarea un número determinado de veces: range

>>> for i in range(10):
...     x = i*2
...     print(x)
...
...
0
2
4
6
8
10
12
14
16
18
>>>

En este ejemplo, nuestro bloque de código es el siguiente:

...     x = i*2
...     print(x)

Repetimos este código 10 veces, cada vez asignando la variable i al siguiente número de la secuencia de enteros del 0-9. Los bucles for pueden utilizarse para iterar a través de cualquiera de los tipos de secuencia de Python. Los verás más adelante en este capítulo.

continúa

La sentencia continue se salta un paso en un bucle, saltando al siguiente elemento de la secuencia:

>>> for i in range(6):
...     if i == 3:
...         continue
...     print(i)
...
...
0
1
2
4
5
>>>

Bucles while

while los bucles repiten un bloque mientras una condición se evalúe como : True

>>> count = 0
>>> while count < 3:
...     print(f"The count is {count}")
...     count += 1
...
...
The count is 0
The count is 1
The count is 2
>>>

Es esencial definir una forma de que tu bucle termine. De lo contrario, te quedarás atrapado en el bucle hasta que tu programa se bloquee. Una forma de hacerlo es definir tu sentencia condicional de modo que al final se evalúe como False. Un patrón alternativo utiliza la sentencia break para salir de un bucle mediante una condicional anidada:

>>> count = 0
>>> while True:
...     print(f"The count is {count}")
...     if count > 5:
...         break
...     count += 1
...
...
The count is 0
The count is 1
The count is 2
The count is 3
The count is 4
The count is 5
The count is 6
>>>

Manejo de excepciones

Las excepciones son un tipo de error que hace que tu programa se bloquee si no se maneja (atrapa). Atraparlas con un bloque try-except permite que el programa continúe. Estos bloques se crean indentando el bloque en el que podría producirse la excepción, poniendo una sentencia try antes de él y una sentencia except después, seguidas de un bloque de código que debería ejecutarse cuando se produzca el error:

>>> thinkers = ['Plato', 'PlayDo', 'Gumby']
>>> while True:
...     try:
...         thinker = thinkers.pop()
...         print(thinker)
...     except IndexError as e:
...         print("We tried to pop too many thinkers")
...         print(e)
...         break
...
...
...
Gumby
PlayDo
Plato
We tried to pop too many thinkers
pop from empty list
>>>

Hay muchas excepciones incorporadas, como IOError, KeyError, y ImportError. Muchos paquetes de terceros también definen sus propias clases de excepciones. Indican que algo ha ido muy mal, por lo que sólo merece la pena atraparlas si estás seguro de que el problema no será fatal para tu software. Puedes especificar explícitamente qué tipo de excepción atraparás. Lo ideal es que atrapes el tipo de excepción exacto (en nuestro ejemplo, se trataba de la excepción IndexError).

Objetos incorporados

En esta visión general, no trataremos la programación orientada a objetos. Sin embargo, el lenguaje Python viene con bastantes clases incorporadas.

¿Qué es un objeto?

En la programación orientada a objetos, los datos o estado y la funcionalidad aparecen juntos. Los conceptos esenciales que hay que entender al trabajar con objetos son la instanciación de clases (crear objetos a partir de clases) y la sintaxis de puntos (la sintaxis para acceder a los atributos y métodos de un objeto). Una clase define atributos y métodos compartidos por sus objetos. Piensa en ella como el dibujo técnico de un modelo de coche. La clase puede instanciarse para crear una instancia. La instancia, u objeto, es un único coche construido a partir de esos dibujos.

>>> # Define a class for fancy defining fancy cars
>>> class FancyCar():
...     pass
...
>>> type(FancyCar)
<class 'type'>
>>> # Instantiate a fancy car
>>> my_car = FancyCar()
>>> type(my_car)
<class '__main__.FancyCar'>

No necesitas preocuparte por crear tus propias clases en este punto. Sólo tienes que entender que cada objeto es una instanciación de una clase.

Métodos y atributos de los objetos

Los objetos almacenan datos en atributos. Estos atributos son variables adjuntas al objeto o a la clase del objeto. Los objetos definen la funcionalidad en métodos de objeto (métodos definidos para todos los objetos de una clase) y métodos de clase (métodos adjuntos a una clase y compartidos por todos los objetos de la clase), que son funciones adjuntas al objeto.

Nota

En la documentación de Python, las funciones adjuntas a objetos y clases se denominan métodos.

Estas funciones tienen acceso a los atributos del objeto y pueden modificar y utilizar sus datos. Para llamar a un método de un objeto o acceder a uno de sus atributos, utilizamos la sintaxis de puntos :

>>> # Define a class for fancy defining fancy cars
>>> class FancyCar():
...     # Add a class variable
...     wheels = 4
...     # Add a method
...     def driveFast(self):
...         print("Driving so fast")
...
...
...
>>> # Instantiate a fancy car
>>> my_car = FancyCar()
>>> # Access the class attribute
>>> my_car.wheels
4
>>> # Invoke the method
>>> my_car.driveFast()
Driving so fast
>>>

Así que aquí nuestra clase FancyCar define un método llamado driveFast y un atributo wheels. Cuando instancias una instancia de FancyCar llamada my_car, puedes acceder al atributo e invocar al método utilizando la sintaxis de puntos.

Secuencias

Las secuencias son una familia de tipos incorporados, que incluyen los tipos lista, tupla, rango, cadena y binario. Las secuencias representan colecciones ordenadas y finitas de elementos.

Secuencia de operaciones

Hay muchas operaciones que funcionan en todos los tipos de secuencias. Aquí cubrimos algunas de las operaciones más utilizadas.

Puedes utilizar los operadores in y not in para comprobar si un elemento existe o no en una secuencia:

>>> 2 in [1,2,3]
True
>>> 'a' not in 'cat'
False
>>> 10 in range(12)
True
>>> 10 not in range(2, 4)
True

Puedes hacer referencia al contenido de una secuencia utilizando su número de índice. Para acceder al elemento en algún índice, utiliza corchetes con el número de índice como argumento. El primer elemento indexado está en la posición 0, el segundo en la 1, y así sucesivamente hasta el número uno menos que el número de elementos:

>>> my_sequence = 'Bill Cheatham'
>>> my_sequence[0]
'B'
>>> my_sequence[2]
'l'
>>> my_sequence[12]
'm'

La indexación puede aparecer desde el final de una secuencia, en lugar de desde el principio, utilizando números negativos. El último elemento tiene el índice -1, el penúltimo tiene el índice -2, y así sucesivamente:

>>> my_sequence = "Bill Cheatham"
>>> my_sequence[1]
'm'
>>> my_sequence[2]
'a'
>>> my_sequence[13]
'B'

El índice de un elemento es el resultado del método index. Por defecto, devuelve el índice de la primera aparición del elemento, pero los argumentos opcionales pueden definir un subrango en el que buscar:

>>> my_sequence = "Bill Cheatham"
>>> my_sequence.index('C')
5
>>> my_sequence.index('a')
8
>>> my_sequence.index('a',9, 12)
11
>>> my_sequence[11]
'a'
>>>

Puedes producir una nueva secuencia a partir de una secuencia utilizando el troceado. Una rebanada aparece invocando una secuencia con corchetes que contienen argumentos opcionales start, stop y step :

my_sequence[start:stop:step]

start es el índice del primer elemento a utilizar en la nueva secuencia, stop el primer índice más allá de ese punto, y step, la distancia entre elementos. Todos estos argumentos son opcionales y se sustituyen por valores por defecto si se omiten. Esta sentencia produce una copia de la secuencia original. El valor por defecto para start es 0, para stop es la longitud de la secuencia, y para step es 1. Ten en cuenta que si el paso no aparece, también se puede omitir el : correspondiente:

>>> my_sequence = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> my_sequence[2:5]
['c', 'd', 'e']
>>> my_sequence[:5]
['a', 'b', 'c', 'd', 'e']
>>> my_sequence[3:]
['d', 'e', 'f', 'g']
>>>

Los números negativos pueden utilizarse para indexar hacia atrás:

>>> my_sequence[6:]
['b', 'c', 'd', 'e', 'f', 'g']
>>> my_sequence[3:1]
['d', 'e', 'f']
>>>

Las secuencias comparten muchas operaciones para obtener información sobre ellas y su contenido. len devuelve la longitud de la secuencia, min el miembro más pequeño, max el más grande y count el número de un elemento concreto. min y max sólo funcionan en secuencias con elementos que sean comparables. Recuerda que funcionan con cualquier tipo de secuencia:

>>> my_sequence = [0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4]
>>> len(my_sequence)
12
>>> min(my_sequence)
0
>>> max(my_sequence)
4
>>> my_sequence.count(1)
3
>>>

Listas

Las listas, una de las estructuras de datos de Python más utilizadas, representan una colección ordenada de elementos de cualquier tipo. El uso de corchetes indica una sintaxis de lista.

La función list() puede utilizarse para crear una lista vacía o una lista basada en otro objeto iterable finito (como otra secuencia):

>>> list()
[]
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list("Henry Miller")
['H', 'e', 'n', 'r', 'y', ' ', 'M', 'i', 'l', 'l', 'e', 'r']
>>>

Las listas creadas utilizando directamente corchetes son la forma más habitual. En este caso, los elementos de la lista deben enumerarse explícitamente. Recuerda que los elementos de una lista pueden ser de distintos tipos:

>>> empty = []
>>> empty
[]
>>> nine = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> nine
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> mixed = [0, 'a', empty, 'WheelHoss']
>>> mixed
[0, 'a', [], 'WheelHoss']
>>>

La forma más eficaz de añadir un único elemento a una lista es append el elemento al final de la lista. Un método menos eficaz, insert, te permite insertar un elemento en la posición de índice que elijas:

>>> pies = ['cherry', 'apple']
>>> pies
['cherry', 'apple']
>>> pies.append('rhubarb')
>>> pies
['cherry', 'apple', 'rhubarb']
>>> pies.insert(1, 'cream')
>>> pies
['cherry', 'cream', 'apple', 'rhubarb']
>>>

El contenido de una lista puede añadirse a otra utilizando el método extend:

>>> pies
['cherry', 'cream', 'apple', 'rhubarb']
>>> desserts = ['cookies', 'paste']
>>> desserts
['cookies', 'paste']
>>> desserts.extend(pies)
>>> desserts
['cookies', 'paste', 'cherry', 'cream', 'apple', 'rhubarb']
>>>

La forma más eficaz y habitual de eliminar el último elemento de una lista y devolver su valor es pop. Se puede proporcionar un argumento de índice a este método, eliminando y devolviendo el elemento en ese índice. Esta técnica es menos eficaz, ya que hay que volver a indexar la lista:

>>> pies
['cherry', 'cream', 'apple', 'rhubarb']
>>> pies.pop()
'rhubarb'
>>> pies
['cherry', 'cream', 'apple']
>>> pies.pop(1)
'cream'
>>> pies
['cherry', 'apple']

También existe un método remove, que elimina la primera aparición de un elemento.

>>> pies.remove('apple')
>>> pies
['cherry']
>>>

Una de las funciones más potentes e idiomáticas de Python, las comprensiones de listas, te permiten utilizar la funcionalidad de un bucle for en una sola línea. Veamos un ejemplo sencillo, empezando con un bucle for que eleva al cuadrado todos los números del 0 al 9 y los añade a una lista:

>>> squares = []
>>> for i in range(10):
...     squared = i*i
...     squares.append(squared)
...
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>

Para sustituirlo por una comprensión de lista, hacemos lo siguiente:

>>> squares = [i*i for i in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>

Ten en cuenta que la funcionalidad del bloque interno se pone en primer lugar, seguida de la declaración for. También puedes añadir condicionales a las comprensiones de listas, filtrando los resultados:

>>> squares = [i*i for i in range(10) if i%2==0]
>>> squares
[0, 4, 16, 36, 64]
>>>

Otras técnicas para la comprensión de listas son el anidamiento y el uso de múltiples variables, pero la forma más directa que se muestra aquí es la más común.

Cuerdas

El tipo de secuencia cadena es una colección de caracteres ordenados rodeados de comillas. A partir de Python 3, las cadenas utilizan por defecto la codificación UTF-8.

Puedes crear cadenas utilizando el método constructor de cadenas str(), o encerrando directamente el texto entre comillas:

>>> str()
''
>>> "some new string!"
'some new string!'
>>> 'or with single quotes'
'or with single quotes'

El constructor de cadenas puede utilizarse para crear cadenas a partir de otros objetos:

>>> my_list = list()
>>> str(my_list)
'[]'

Puedes crear cadenas de varias líneas utilizando comillas triples alrededor del contenido:

>>> multi_line = """This is a
... multi-line string,
... which includes linebreaks.
... """
>>> print(multi_line)
This is a
multi-line string,
which includes linebreaks.
>>>

Además de los métodos que comparten todas las secuencias, las cadenas tienen bastantes métodos propios de su clase.

Es relativamente habitual que el texto del usuario tenga espacios en blanco al final o al principio. Si alguien escribe "sí" en un formulario en lugar de "sí", normalmente querrás tratarlos igual. Las cadenas de Python tienen un método strip para este caso. Devuelve una cadena con los espacios en blanco eliminados del principio y del final. También hay métodos para eliminar los espacios en blanco sólo de la parte derecha o izquierda de la cadena:

>>> input = "  I want more  "
>>> input.strip()
'I want more'
>>> input.rstrip()
'  I want more'
>>> input.lstrip()
'I want more  '

Por otro lado, si quieres añadir relleno a una cadena, puedes utilizar los métodos ljust o rjust. Cualquiera de ellos rellena con espacios en blanco por defecto, o toma un argumento de carácter :

>>> output = 'Barry'
>>> output.ljust(10)
'Barry     '
>>> output.rjust(10, '*')
'*****Barry'

A veces quieres dividir una cadena en una lista de subcadenas. Tal vez tengas una frase que quieras convertir en una lista de palabras, o una cadena de palabras separadas por comas. El método split descompone una cadena en una lista de cadenas. Por defecto, utiliza espacios en blanco como token para hacer las rupturas. Se puede utilizar un argumento opcional para añadir otro carácter en el que pueda romperse la división:

>>> text = "Mary had a little lamb"
>>> text.split()
['Mary', 'had', 'a', 'little', 'lamb']
>>> url = "gt.motomomo.io/v2/api/asset/143"
>>> url.split('/')
['gt.motomomo.io', 'v2', 'api', 'asset', '143']

Puedes crear fácilmente una nueva cadena a partir de una secuencia de cadenas y join convertirlas en una sola cadena. Este método inserta una cadena como separador entre una lista de otras cadenas:

>>> items = ['cow', 'milk', 'bread', 'butter']
>>> " and ".join(items)
'cow and milk and bread and butter'

Cambiar las mayúsculas y minúsculas de un texto es algo habitual, tanto si se trata de uniformizar las mayúsculas y minúsculas para la comparación como de cambiarlas en preparación para el consumo del usuario. Las cadenas de Python tienen varios métodos para hacer de esto un proceso fácil:

>>> name = "bill monroe"
>>> name.capitalize()
'Bill monroe'
>>> name.upper()
'BILL MONROE'
>>> name.title()
'Bill Monroe'
>>> name.swapcase()
'BILL MONROE'
>>> name = "BILL MONROE"
>>> name.lower()
'bill monroe'

Python también proporciona métodos para comprender el contenido de una cadena. Ya sea para comprobar el caso del texto, o para ver si representa un número, hay bastantes métodos incorporados para interrogar. Aquí tienes algunos de los métodos más utilizados:

>>> "William".startswith('W')
True
>>> "William".startswith('Bill')
False
>>> "Molly".endswith('olly')
True
>>> "abc123".isalnum()
True
>>> "abc123".isalpha()
False
>>> "abc".isalnum()
True
>>> "123".isnumeric()
True
>>> "Sandy".istitle()
True
>>> "Sandy".islower()
False
>>> "SANDY".isupper()
True

Puedes insertar contenido en una cadena y controlar su formato en tiempo de ejecución. Tu programa puede utilizar los valores de variables u otro contenido calculado en cadenas. Este enfoque se utiliza tanto para crear texto consumido por el usuario como para escribir registros de software.

La forma más antigua de formatear cadenas en Python procede de la función printf del lenguaje C. Puedes utilizar el operador de módulo, %, para insertar valores formateados en una cadena. Esta técnica se aplica a la forma string % values, donde los valores pueden ser una única no-tupla o una tupla de múltiples valores. La propia cadena debe tener un especificador de conversión para cada valor. El especificador de conversión, como mínimo, comienza con un % y va seguido de un carácter que representa el tipo de valor insertado:

>>> "%s + %s = %s" % (1, 2, "Three")
'1 + 2 = Three'
>>>

Los argumentos de formato adicionales incluyen el especificador de conversión. Por ejemplo, puedes controlar el número de posiciones que imprime un flotante, %f:

>>> "%.3f" % 1.234567
'1.235'

Este mecanismo de formateo de cadenas fue el dominante en Python durante años, y lo encuentras en el código heredado. Este enfoque ofrece algunas características atractivas, como compartir sintaxis con otros lenguajes. También tiene algunos escollos. En particular, debido al uso de una secuencia para contener los argumentos, son frecuentes los errores relacionados con la visualización de los objetos tuple y dict. Recomendamos adoptar opciones de formato más modernas, como el método de cadena format, las cadenas de plantilla y las cadenas f, tanto para evitar estos errores como para aumentar la sencillez y legibilidad de tu código.

Python 3 introdujo una nueva forma de dar formato a las cadenas utilizando el método de cadena format. Esta forma de formatear también se ha trasladado a Python 2. Esta especificación utiliza llaves en la cadena para indicar campos de sustitución, en lugar de los especificadores de conversión basados en módulos del formato antiguo. Los valores de inserción se convierten en argumentos del método de cadena format. El orden de los argumentos determina su orden de colocación en la cadena de destino:

>>> '{} comes before {}'.format('first', 'second')
'first comes before second'
>>>

Puedes especificar números de índice entre paréntesis para insertar valores en un orden distinto al de la lista de argumentos. También puedes repetir un valor especificando el mismo número de índice en varios campos de sustitución:

>>> '{1} comes after {0}, but {1} comes before {2}'.format('first',
                                                           'second',
                                                           'third')
'second comes after first, but second comes before third'
>>>

Una característica aún más potente es que los valores de inserción se pueden especificar por nombre:

>>> '''{country} is an island.
... {country} is off of the coast of
... {continent} in the {ocean}'''.format(ocean='Indian Ocean',
...                                      continent='Africa',
...                                      country='Madagascar')
'Madagascar is an island.
Madagascar is off of the coast of
Africa in the Indian Ocean'

Aquí funciona un dict para suministrar los valores clave de los campos de sustitución basados en nombres:

>>> values = {'first': 'Bill', 'last': 'Bailey'}
>>> "Won't you come home {first} {last}?".format(**values)
"Won't you come home Bill Bailey?"

También puedes especificar argumentos de especificación de formato. Aquí añaden relleno izquierdo y derecho utilizando > y <. En el segundo ejemplo, especificamos un carácter para utilizar en el relleno:

>>> text = "|{0:>22}||{0:<22}|"
>>> text.format('O','O')
'|                     O||O                     |'
>>> text = "|{0:<>22}||{0:><22}|"
>>> text.format('O','O')
'|<<<<<<<<<<<<<<<<<<<<<O||O>>>>>>>>>>>>>>>>>>>>>|'

Las especificaciones de formato se realizan utilizando el minilenguaje de especificación de formato. Nuestro tema también utiliza otro tipo de lenguaje llamado cadenas f.

Las cadenas f de Python utilizan el mismo lenguaje de formateo que el método format, pero ofrecen un mecanismo más sencillo e intuitivo para utilizarlas. Las cadenas f se anteponen con f o F antes de la primera comilla. Al igual que la cadena format descrita anteriormente, las cadenas f utilizan llaves para delimitar los campos de sustitución. Sin embargo, en una cadena f, el contenido del campo de sustitución es una expresión. Este enfoque significa que puede referirse a variables definidas en el ámbito actual o implicar cálculos:

>>> a = 1
>>> b = 2
>>> f"a is {a}, b is {b}. Adding them results in {a + b}"
'a is 1, b is 2. Adding them results in 3'

Al igual que en las cadenas format, las especificaciones de formato en las cadenas f ocurren dentro de las llaves después de la expresión del valor y comienzan con un ::

>>> count = 43
>>> f"|{count:5d}"
'|   43'

La expresión del valor puede contener expresiones anidadas, variables de referencia y expresiones en la construcción de la expresión padre:

>>> padding = 10
>>> f"|{count:{padding}d}"
'|        43'
Consejo

Te recomendamos encarecidamente que utilices cadenas f para la mayoría de tus formateos de cadenas. Combinan la potencia del minilenguaje de especificación con una sintaxis sencilla e intuitiva.

Las plantillas de cadenas están diseñadas para ofrecer un mecanismo sencillo de sustitución de cadenas. Estos métodos incorporados funcionan para tareas como la internacionalización, donde son necesarias sustituciones simples de palabras. Utilizan $ como carácter de sustitución, con llaves opcionales alrededor. Los caracteres que siguen directamente a $ identifican el valor que se va a insertar. Cuando se ejecuta el método substitute de la plantilla de cadenas, estos nombres se utilizan para asignar valores.

Nota

Los tipos y funciones incorporados están disponibles siempre que ejecutes código Python, pero para acceder al mundo más amplio de funcionalidades disponibles en el ecosistema Python, necesitas utilizar la sentencia import. Este enfoque te permite añadir funcionalidad de la Biblioteca Estándar de Python o de servicios de terceros a tu entorno. Puedes importar selectivamente partes de un paquete utilizando la palabra clave from:

>>> from string import Template
>>> greeting = Template("$hello Mark Anthony")
>>> greeting.substitute(hello="Bonjour")
'Bonjour Mark Anthony'
>>> greeting.substitute(hello="Zdravstvuyte")
'Zdravstvuyte Mark Anthony'
>>> greeting.substitute(hello="Nǐn hǎo")
'Nǐn hǎo Mark Anthony'

Dicts

Aparte de las cadenas y las listas, los dictos pueden ser las clases incorporadas a Python más utilizadas. Un dict es un mapeo de claves a valores. La búsqueda de un valor concreto mediante una clave es muy eficaz y rápida. Las claves pueden ser cadenas, números, objetos personalizados o cualquier otro tipo no mutable.

Nota

Un objeto mutable es aquel cuyo contenido puede cambiar en su lugar. Las listas son un ejemplo primario; el contenido de la lista puede cambiar sin que cambie la identidad de la lista. Las cadenas no son mutables. Creas una cadena nueva cada vez que cambias el contenido de una existente.

Los dicts se representan como pares clave/valor separados por comas y rodeados de llaves. Los pares clave/valor constan de una clave, dos puntos (:) y, a continuación, un valor.

Puedes crear un objeto dict utilizando el constructor dict(). Sin argumentos, crea un dict vacío. También acepta una secuencia de pares clave/valor como argumento:

>>> map = dict()
>>> type(map)
<class 'dict'>
>>> map
{}
>>> kv_list = [['key-1', 'value-1'], ['key-2', 'value-2']]
>>> dict(kv_list)
{'key-1': 'value-1', 'key-2': 'value-2'}

También puedes crear un dict directamente utilizando llaves:

>>> map = {'key-1': 'value-1', 'key-2': 'value-2'}
>>> map
{'key-1': 'value-1', 'key-2': 'value-2'}

Puedes acceder al valor asociado a una tecla utilizando la sintaxis de corchetes:

>>> map['key-1']
'value-1'
>>> map['key-2']
'value-2'

Puedes utilizar la misma sintaxis para establecer un valor. Si la clave no está en el dict, se añade como una nueva entrada. Si ya existe, el valor cambia al nuevo valor:

>>> map
{'key-1': 'value-1', 'key-2': 'value-2'}
>>> map['key-3'] = 'value-3'
>>> map
{'key-1': 'value-1', 'key-2': 'value-2', 'key-3': 'value-3'}
>>> map['key-1'] = 13
>>> map
{'key-1': 13, 'key-2': 'value-2', 'key-3': 'value-3'}

Si intentas acceder a una clave que no se ha definido en un dict, se lanzará una excepción KeyError:

>>> map['key-4']
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    map['key-4']
KeyError: 'key-4'

Puedes comprobar si la clave existe en un dict utilizando la sintaxis in que vimos con las secuencias. En el caso de los dicts, comprueba la existencia de claves:

>>> if 'key-4' in map:
...     print(map['key-4'])
... else:
...     print('key-4 not there')
...
...
key-4 not there

Una solución más intuitiva es utilizar el método get(). Si no has definido una clave en un dict, devuelve un valor por defecto suministrado. Si no has proporcionado un valor por defecto, devuelve None:

>>> map.get('key-4', 'default-value')
'default-value'

Utiliza del para eliminar un par clave-valor de un dict:

>>> del(map['key-1'])
>>> map
{'key-2': 'value-2', 'key-3': 'value-3'}

El método keys() devuelve un objeto dict_keys con las claves del dict. El método values() devuelve un objeto dict_values, y el método items() devuelve pares clave-valor. Este último método es útil para recorrer el contenido de un dict:

>>> map.keys()
dict_keys(['key-1', 'key-2'])
>>> map.values()
dict_values(['value-1', 'value-2'])
>>> for key, value in map.items():
...     print(f"{key}: {value}")
...
...
key-1: value-1
key-2: value-2

Parecidas a las comprensiones de listas, las comprensiones de dict son sentencias de una línea que devuelven un dict iterando a través de una secuencia:

>>> letters = 'abcde'
>>> # mapping individual letters to their upper-case representations
>>> cap_map = {x: x.upper() for x in letters}
>>> cap_map['b']
'B'

Funciones

Ya has visto algunas funciones incorporadas de Python. Ahora pasa a escribir las tuyas propias. Recuerda que una función es un mecanismo para encapsular un bloque de código. Puedes repetir el comportamiento de este bloque en varios puntos sin tener que duplicar el código. Tu código estará mejor organizado, será más comprobable, mantenible y más fácil de entender.

Anatomía de una función

La primera línea de una definición de función empieza con la palabra clave def, seguida del nombre de la función, los parámetros de la función entre paréntesis y, a continuación, :. El resto de la función es un bloque de código y está sangrado:

def <FUNCTION NAME>(<PARAMETERS>):
    <CODE BLOCK>

Si se proporciona primero una cadena que utilice sintaxis multilínea en el bloque sangrado, actúa como documentación. Utilízala para describir lo que hace tu función, cómo funcionan los parámetros y qué se puede esperar que devuelva. Verás que estas docstrings son inestimables para comunicarte con los futuros usuarios de tu código. Varios programas y servicios también los utilizan para crear documentación. Proporcionar docstrings se considera una buena práctica y es muy recomendable:

>>> def my_function():
...    '''This is a doc string.
...
...    It should describe what the function does,
...    what parameters work, and what the
...    function returns.
...    '''

Los argumentos de las funciones aparecen entre paréntesis tras el nombre de la función. Pueden ser posicionales o de palabra clave. Los argumentos posicionales utilizan el orden de los argumentos para asignar valor:

>>> def positioned(first, second):
...     """Assignment based on order."""
...     print(f"first: {first}")
...     print(f"second: {second}")
...
...
>>> positioned(1, 2)
first: 1
second: 2
>>>

Con argumentos de palabra clave, asigna a cada argumento un valor por defecto:

>>> def keywords(first=1, second=2):
...     '''Default values assigned'''
...     print(f"first: {first}")
...     print(f"second: {second}")
...
...

Los valores por defecto se utilizan cuando no se pasan valores durante la invocación de la función. Los parámetros de palabra clave se pueden llamar por su nombre durante la invocación de la función, en cuyo caso no importará el orden:

>>> keywords(0)
first: 0
second: 2
>>> keywords(3,4)
first: 3
second: 4
>>> keywords(second='one', first='two')
first: two
second: one

Al utilizar parámetros de palabra clave, todos los parámetros definidos después de un parámetro de palabra clave deben ser también parámetros de palabra clave. Todas las funciones devuelven un valor. La palabra clave return se utiliza para establecer este valor. Si no se establece desde la definición de una función, ésta devuelve None:

>>> def no_return():
...     '''No return defined'''
...     pass
...
>>> result = no_return()
>>> print(result)
None
>>> def return_one():
...     '''Returns 1'''
...     return 1
...
>>> result = return_one()
>>> print(result)
1

Funciones como objetos

Las funciones son objetos. Se pueden pasar o almacenar en estructuras de datos. Puedes definir dos funciones, ponerlas en una lista e iterar por la lista para invocarlas:

>>> def double(input):
...     '''double input'''
...     return input*2
...
>>> double
<function double at 0x107d34ae8>
>>> type(double)
<class 'function'>
>>> def triple(input):
...     '''Triple input'''
...     return input*3
...
>>> functions = [double, triple]
>>> for function in functions:
...     print(function(3))
...
...
6
9

Funciones anónimas

Cuando necesites crear una función muy limitada, puedes crear una sin nombre (anónima) utilizando la palabra clave lambda. En general, debes limitar su uso a situaciones en las que una función espera una función pequeña como argumento. En este ejemplo, tomas una lista de listas y la ordenas. El mecanismo de ordenación por defecto compara basándose en el primer elemento de cada sublista:

>>> items = [[0, 'a', 2], [5, 'b', 0], [2, 'c', 1]]
>>> sorted(items)
[[0, 'a', 2], [2, 'c', 1], [5, 'b', 0]]

Para ordenar basándote en algo distinto de la primera entrada, puedes definir un método que devuelva la segunda entrada del elemento y pasarlo al parámetro key de la función de ordenación:

>>> def second(item):
...     '''return second entry'''
...     return item[1]
...
>>> sorted(items, key=second)
[[0, 'a', 2], [5, 'b', 0], [2, 'c', 1]]

Con la palabra clave lambda, puedes hacer lo mismo sin la definición completa de la función. Las lambdas funcionan con la palabra clave lambda seguida de un nombre de parámetro, dos puntos y un valor de retorno:

lambda <PARAM>: <RETURN EXPRESSION>

Ordena utilizando lambdas, primero utilizando la segunda entrada y luego utilizando la tercera:

>>> sorted(items, key=lambda item: item[1])
[[0, 'a', 2], [5, 'b', 0], [2, 'c', 1]]
>>> sorted(items, key=lambda item: item[2])
[[5, 'b', 0], [2, 'c', 1], [0, 'a', 2]]

Ten cuidado al utilizar lambdas de forma más general, ya que pueden crear código poco documentado y de lectura confusa si se utilizan en lugar de funciones generales.

Utilizar expresiones regulares

La necesidad de hacer coincidir patrones en cadenas surge una y otra vez. Podrías estar buscando un identificador en un archivo de registro o comprobando la entrada de un usuario en busca de palabras clave o una miríada de otros casos. Ya has visto comparaciones sencillas de patrones utilizando la operación in para secuencias, o los métodos de cadena .endswith y .startswith. Para realizar comparaciones más sofisticadas, necesitas una herramienta más potente. Las expresiones regulares, a menudo denominadas regex, son la respuesta. Las expresiones regulares utilizan una cadena de caracteres para definir patrones de búsqueda. El paquete re de Python ofrece operaciones de expresiones regulares similares a las de Perl. El módulo re utiliza barras invertidas(\) para delimitar los caracteres especiales utilizados en las coincidencias. Para evitar confusiones con las secuencias de escape de las cadenas regulares, se recomienda utilizar cadenas en bruto al definir patrones de expresiones regulares. A las cadenas sin procesar se les antepone una r antes de la primera comilla.

Nota

Las cadenas de Python tienen varias secuencias de escape. Entre las más comunes están el salto de línea \n y el tabulador \t.

Buscando en

Digamos que tienes una lista cc de un correo electrónico como texto y quieres saber más sobre quién está en esta lista:

In [1]: cc_list = '''Ezra Koenig <ekoenig@vpwk.com>,
   ...: Rostam Batmanglij <rostam@vpwk.com>,
   ...: Chris Tomson <ctomson@vpwk.com,
   ...: Bobbi Baio <bbaio@vpwk.com'''

Si quieres saber si un nombre está en este texto, puedes utilizar la sintaxis de pertenencia a la secuencia in:

In [2]: 'Rostam' in cc_list
Out[2]: True

Para obtener un comportamiento similar, puedes utilizar la función re.search, que devuelve un objeto re.Match sólo si hay una coincidencia:

In [3]: import re

In [4]: re.search(r'Rostam', cc_list)
Out[4]: <re.Match object; span=(32, 38), match='Rostam'>

Puedes utilizarlo como condición para comprobar la afiliación:

>>> if re.search(r'Rostam', cc_list):
...     print('Found Rostam')
...
...
Found Rostam

Juegos de caracteres

Hasta ahora re no te ha dado nada que no pudieras conseguir utilizando el operador in. Sin embargo, ¿qué ocurre si buscas a una persona en un texto, pero no recuerdas si se llama Bobbi o Robby?

Con las expresiones regulares, puedes utilizar grupos de caracteres, cualquiera de los cuales podría aparecer en un lugar. Se denominan conjuntos de caracteres. Los caracteres entre los que debe elegirse una coincidencia aparecen entre corchetes en la definición de la expresión regular. Puedes hacer coincidir la B o la R, seguidas de obb, y la i o la y:

In [5]: re.search('[RB]obb[yi]', ',obbi')
Out[5]: <re.Match object; span=(0, 5), match=',obbi'>

Puedes poner caracteres individuales separados por comas en un conjunto de caracteres o utilizar rangos. El rango A-Z incluye todas las letras mayúsculas; el rango 0-9 incluye los dígitos del cero al nueve:

In [6]: re.search(r'Chr[a-z][a-z]', cc_list)
Out [6]: <re.Match object; span=(69, 74), match='Chris'>

El signo + después de un elemento en una expresión regular coincide con uno o más de ese elemento. Un número entre paréntesis coincide con un número exacto de caracteres:

In [7]: re.search(r'[A-Za-z]+', cc_list)
Out [7]: <re.Match object; span=(0, 4), match='Ezra'>
In [8]: re.search(r'[A-Za-z]{6}', cc_list)
Out [8]: <re.Match object; span=(5, 11), match='Koenig'>

Podemos construir una coincidencia utilizando una combinación de conjuntos de caracteres y otros caracteres para hacer una coincidencia ingenua de una dirección de correo electrónico. El carácter . tiene un significado especial. Es un comodín y coincide con cualquier carácter. Para que coincida con el carácter . real, debes escaparlo utilizando una barra invertida:

In [9]: re.search(r'[A-Za-z]+@[a-z]+\.[a-z]+', cc_list)
Out[9]: <re.Match object; span=(13, 29), match='ekoenig@vpwk.com'>

Este ejemplo es sólo una demostración de conjuntos de caracteres. No representa toda la complejidad de una expresión regular lista para la producción de correos electrónicos.

Clases de personajes

Además de conjuntos de caracteres, la página re de Python ofrece clases de caracteres. Se trata de conjuntos de caracteres prefabricados. Algunos de los más utilizados son \w, que equivale a [a-zA-Z0-9_] y \d, que equivale a [0-9]. Puedes utilizar el modificador + para hacer coincidir varios caracteres:

>>> re.search(r'\w+', cc_list)
<re.Match object; span=(0, 4), match='Ezra'>

Y puedes sustituir nuestro comparador de correo electrónico primitivo por \w:

>>> re.search(r'\w+\@\w+\.\w+', cc_list)
<re.Match object; span=(13, 29), match='ekoenig@vpwk.com'>

Grupos

Puedes utilizar paréntesis para definir grupos en una coincidencia. Se puede acceder a estos grupos desde el objeto coincidencia. Están numerados en el orden en que aparecen, siendo el grupo cero la coincidencia completa:

>>> re.search(r'(\w+)\@(\w+)\.(\w+)', cc_list)
<re.Match object; span=(13, 29), match='ekoenig@vpwk.com'>
>>> matched = re.search(r'(\w+)\@(\w+)\.(\w+)', cc_list)
>>> matched.group(0)
'ekoenig@vpwk.com'
>>> matched.group(1)
'ekoenig'
>>> matched.group(2)
'vpwk'
>>> matched.group(3)
'com'

Grupos designados

También puedes proporcionar nombres a los grupos añadiendo ?P<NAME> en la definición del grupo. Así podrás acceder a los grupos por su nombre en lugar de por su número:

>>> matched = re.search(r'(?P<name>\w+)\@(?P<SLD>\w+)\.(?P<TLD>\w+)', cc_list)
>>> matched.group('name')
'ekoenig'
>>> print(f'''name: {matched.group("name")}
... Secondary Level Domain: {matched.group("SLD")}
... Top Level Domain: {matched.group("TLD")}''')
name: ekoenig
Secondary Level Domain: vpwk
Top Level Domain: com

Buscar todo

Hasta ahora, hemos demostrado que sólo devuelve la primera coincidencia encontrada. También podemos utilizar findall para devolver todas las coincidencias como una lista de cadenas:

>>> matched = re.findall(r'\w+\@\w+\.\w+', cc_list)
>>> matched
['ekoenig@vpwk.com', 'rostam@vpwk.com', 'ctomson@vpwk.com', 'cbaio@vpwk.com']
>>> matched = re.findall(r'(\w+)\@(\w+)\.(\w+)', cc_list)
>>> matched
[('ekoenig', 'vpwk', 'com'), ('rostam', 'vpwk', 'com'),
 ('ctomson', 'vpwk', 'com'), ('cbaio', 'vpwk', 'com')]
>>> names = [x[0] for x in matched]
>>> names
['ekoenig', 'rostam', 'ctomson', 'cbaio']

Buscar Iterador

Cuando se trata de textos grandes, como los registros, es útil no procesar el texto de una sola vez. Puedes producir un objeto iterador utilizando el método finditer. Este objeto procesa el texto hasta que encuentra una coincidencia y se detiene. Al pasarlo a la función next devuelve la coincidencia actual y continúa procesando hasta encontrar la siguiente coincidencia. De esta forma, puedes tratar cada coincidencia individualmente sin dedicar recursos a procesar toda la entrada a la vez:

>>> matched = re.finditer(r'\w+\@\w+\.\w+', cc_list)
>>> matched
<callable_iterator object at 0x108e68748>
>>> next(matched)
<re.Match object; span=(13, 29), match='ekoenig@vpwk.com'>
>>> next(matched)
<re.Match object; span=(51, 66), match='rostam@vpwk.com'>
>>> next(matched)
<re.Match object; span=(83, 99), match='ctomson@vpwk.com'>

El objeto iterador, matched, también puede utilizarse en un bucle for:

>>> matched = re.finditer("(?P<name>\w+)\@(?P<SLD>\w+)\.(?P<TLD>\w+)", cc_list)
>>> for m in matched:
...     print(m.groupdict())
...
...
{'name': 'ekoenig', 'SLD': 'vpwk', 'TLD': 'com'}
{'name': 'rostam', 'SLD': 'vpwk', 'TLD': 'com'}
{'name': 'ctomson', 'SLD': 'vpwk', 'TLD': 'com'}
{'name': 'cbaio', 'SLD': 'vpwk', 'TLD': 'com'}

Sustitución

Además de buscar y comparar, las expresiones regulares pueden utilizarse para sustituir parte o la totalidad de una cadena:

>>> re.sub("\d", "#", "The passcode you entered was  09876")
'The passcode you entered was  #####'
>>> users = re.sub("(?P<name>\w+)\@(?P<SLD>\w+)\.(?P<TLD>\w+)",
                   "\g<TLD>.\g<SLD>.\g<name>", cc_list)
>>> print(users)
Ezra Koenig <com.vpwk.ekoenig>,
Rostam Batmanglij <com.vpwk.rostam>,
Chris Tomson <com.vpwk.ctomson,
Chris Baio <com.vpwk.cbaio

Compilando

Todos los ejemplos hasta ahora han llamado directamente a métodos del módulo re. Esto es adecuado para muchos casos, pero si la misma coincidencia se va a producir muchas veces, se puede mejorar el rendimiento compilando la expresión regular en un objeto. Este objeto puede reutilizarse para las coincidencias sin necesidad de recompilarlo:

>>> regex = re.compile(r'\w+\@\w+\.\w+')
>>> regex.search(cc_list)
<re.Match object; span=(13, 29), match='ekoenig@vpwk.com'>

Las expresiones regulares ofrecen muchas más funciones de las que hemos tratado aquí. De hecho, se han escrito muchos libros sobre su uso, pero ahora deberías estar preparado para la mayoría de los casos básicos.

Evaluación perezosa

La evaluación perezosa es la idea de que, especialmente cuando se trata de grandes cantidades de datos, no quieres procesar todos los datos antes de utilizar los resultados. Ya has visto esto con el tipo range, donde la huella de memoria es la misma, incluso para uno que represente un gran grupo de números.

Generadores

Puedes utilizar los generadores de forma similar a los objetos range. Realizan alguna operación sobre los datos en trozos según se les solicite. Detienen su estado entre llamadas. Esto significa que puedes almacenar variables necesarias para calcular la salida, a las que se accede cada vez que se llama al generador.

Para escribir una función generadora, utiliza la palabra clave yield en lugar de una sentencia return. Cada vez que se llama al generador, éste devuelve el valor especificado por yield y luego pausa su estado hasta la siguiente llamada. Escribamos un generador que simplemente cuente, devolviendo cada número subsiguiente:

>>> def count():
...     n = 0
...     while True:
...         n += 1
...         yield n
...
...
>>> counter = count()
>>> counter
<generator object count at 0x10e8509a8>
>>> next(counter)
1
>>> next(counter)
2
>>> next(counter)
3

Ten en cuenta que el generador mantiene un registro de su estado y, por tanto, la variable n en cada llamada al generador refleja el valor establecido previamente. Implementemos un generador Fibonacci:

>>> def fib():
...     first = 0
...     last = 1
...     while True:
...         first, last = last, first + last
...         yield first
...
>>> f = fib()
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3

También podemos iterar utilizando el generador en un bucle for:

>>> f = fib()
>>> for x in f:
...     print(x)
...     if x > 12:
...         break
...
1
1
2
3
5
8
13

Comprensiones del generador

Podemos utilizar las comprensiones de generador para crear generadores de una línea. Se crean utilizando una sintaxis similar a la de las comprensiones de lista, pero se utilizan paréntesis en lugar de corchetes:

>>> list_o_nums = [x for x in range(100)]
>>> gen_o_nums = (x for x in range(100))
>>> list_o_nums
[0, 1, 2, 3, ...  97, 98, 99]
>>> gen_o_nums
<generator object <genexpr> at 0x10ea14408>

Incluso con este pequeño ejemplo, podemos ver la diferencia de memoria utilizada al utilizar el método sys.getsizeof, que devuelve el tamaño de un objeto, en bytes:

>>> import sys
>>> sys.getsizeof(list_o_nums)
912
>>> sys.getsizeof(gen_o_nums)
120

Más funciones de IPython

Ya has visto algunas de las características de IPython al principio del capítulo. Ahora vamos a ver algunas características más avanzadas, como la ejecución de comandos shell desde dentro del intérprete IPython y el uso de funciones mágicas.

Utilizar IPython para ejecutar comandos de Unix Shell

Puedes utilizar IPython para ejecutar comandos de shell. Ésta es una de las razones más convincentes para realizar acciones DevOps en el shell de IPython. Veamos un ejemplo muy sencillo en el que el carácter !, que IPython utiliza para identificar los comandos del shell, se coloca delante del comando ls:

In [3]: var_ls = !ls -l
In [4]: type(var_ls)
Out[4]: IPython.utils.text.SList

La salida del comando se asigna a una variable Python var_ls. El type de esta variable es IPython.utils.text.SList. El tipo SList convierte un comando shell normal en un objeto que tiene tres métodos principales: fields, grep, y sort. Aquí tienes un ejemplo en acción utilizando el comando Unix df. El método sort puede interpretar los espacios en blanco de este comando Unix y luego ordenar la tercera columna por tamaño:

In [6]: df = !df
In [7]: df.sort(3, nums = True)

Veamos a continuación SList y .grep. Aquí tienes un ejemplo que busca qué comandos con kill como parte de su nombre están instalados en el directorio /usr/bin:

In [10]: ls = !ls -l /usr/bin
In [11]: ls.grep("kill")
Out[11]:
['-rwxr-xr-x   1 root   wheel      1621 Aug 20  2018 kill.d',
 '-rwxr-xr-x   1 root   wheel     23984 Mar 20 23:10 killall',
 '-rwxr-xr-x   1 root   wheel     30512 Mar 20 23:10 pkill']

Lo más importante es que IPython es un entorno de ensueño para hackear pequeños scripts de shell.

Utilizar los comandos mágicos de IPython

Si te acostumbras a utilizar IPython, también deberías acostumbrarte a utilizar los comandos mágicos incorporados. Básicamente, son atajos con un gran efecto. Los comandos mágicos se indican precediéndolos de %%. He aquí un ejemplo de cómo escribir Bash en línea dentro de IPython. Ten en cuenta que esto es sólo un pequeño comando, pero podría ser un script Bash entero:

In [13]: %%bash
    ...: uname -a
    ...:
    ...:
Darwin nogibjj.local 18.5.0 Darwin Kernel Version 18.5.0: Mon Mar ...

El %%writefile es bastante complicado porque puedes escribir y probar scripts Python o Bash sobre la marcha, utilizando IPython para ejecutarlos. No es para nada un mal truco de fiesta:

In [16]: %%writefile print_time.py
    ...: #!/usr/bin/env python
    ...: import datetime
    ...: print(datetime.datetime.now().time())
    ...:
    ...:
    ...:
Writing print_time.py

In [17]: cat print_time.py
#!/usr/bin/env python
import datetime
print(datetime.datetime.now().time())

In [18]: !python print_time.py
19:06:00.594914

Otro comando muy útil, %who, te mostrará lo que hay cargado en memoria. Resulta muy útil cuando llevas mucho tiempo trabajando en un terminal:

In [20]: %who
df     ls     var_ls

Ejercicios

  • Escribe una función Python que tome un nombre como argumento e imprima ese nombre.

  • Escribe una función Python que tome una cadena como argumento e imprima si está en mayúsculas o minúsculas.

  • Escribe una comprensión de lista que dé como resultado una lista con todas las letras de la palabra smogtether en mayúsculas.

  • Escribe un generador que alterne entre devolver Pares e Impares.

Get Python para DevOps now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.