Python es uno de los lenguajes que más crecimiento e interés ha despertado en los últimos años. De hecho recientemente ha alcanzado el número 1 como lenguaje más popular en el índice TIOBE. Esto es debido a varios factores como las características del propio lenguaje, su comunidad o su fuerte relación con el mundo de los datos y el machine learning, pero, sin duda, otro factor importante es la continua e incesante evolución que está experimentando el lenguaje en los últimos años incorporando nuevas características, en particular, desde que se dio el salto a Python 3, y que demuestran lo vivo y dinámico del ecosistema Python y el grandísimo trabajo que llevan desarrollando los desarrolladores voluntarios del Python Core Developer Team, que mantienen y evolucionan el lenguaje.

Fuente: Wikipedia.
Fuente: Wikipedia.

En octubre de 2021 ha sido liberada la versión 3.10 y para celebrarlo en este artículo vamos a comentar 10 trucos o características de Python que han sido incorporados en las últimas versiones y son poco conocidos en general.

1 Formatted string literals

Esta característica nos permite definir y formatear strings de una forma nueva, más cómoda y legible. Comenzaremos el string por el prefijo “f” y de esta manera podremos incluir entre llaves variables y expresiones que serán evaluadas en tiempo de ejecución. Esto es una alternativa a la concatenación de strings, el método format() o al uso de “%”:

# Old
name = "John"
full_name = name + " smith"

# New
name = "John"
full_name = f"{name} smith"

# Old 
message.append(" [line %2d]" % lineno)

# New
message.append(f" [line {lineno:2d}]")

# Old
print("Usage: {0} [{1}]".format(sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), file=sys.stderr)

# New
print(f"Usage: {sys.argv[0]} [{'|'.join('--'+opt for opt in valid_opts)}]", file=sys.stderr)

# Old
"{}".format((lambda x: x*2)(3))

# New
f"{(lambda x: x*2)(3)}"

Añadido en Python 3.6, más detalles en el PEP 498.

2 El operador morsa

El operador morsa o “walrus operator” se define con el símbolo (:=) y recibe este nombre por el parecido con una morsa y sus largos colmillos. En Python ha sido introducido para definir expresiones de asignación que en ocasiones nos pueden ayudar a simplificar partes del código y evitar redundancia:

# Old
walrus = True
print(walrus)

# New
print(walrus := True)

# Old
while True:
    my_input = input("Write something: ")
    if my_input == "exit":
        break
    do_something(my_input)

# New
while (my_input := input("Write something: ")) != "exit":
    do_something(my_input)

Añadido en Python 3.8, más detalles en el PEP 572.

3 Guiones bajos en literales numéricos

Esta característica es realmente sencilla, pero puede ser muy útil para facilitar la lectura de grandes números. Ahora podemos añadir el símbolo underscore o guión bajo “_” a la hora de definir un número para facilitar su legibilidad. Esto aplica para números en cualquier base:

>>> 1_000_000_000_000_000
1000000000000000

>>> 0x_FF_FF_FF_FF
4294967295

Además también tenemos la posibilidad de formatear automáticamente los números para que sean representados como strings incluyendo estos guiones bajos.

>>> '{:_}'.format(1000000)
'1_000_000'
>>> '{:_x}'.format(0xFFFFFFFF)
'ffff_ffff'

Añadido en Python 3.6, más detalles en el PEP 515.

4 Structural Pattern Matching

Se trata de una de las características más recientes añadidas al lenguaje y posiblemente la más importante de la versión 3.10, ha despertado cierta discusión en la comunidad e introduce una nueva forma de construir estructuras de tipo“switch”al estilo del lenguaje C sin usar if/elif/elif… consecutivos. Además añade ciertas características adicionales a la hora de hacer el matching que lo hacen muy potente. En este otro artículo del blog profundizamos en las posibilidades del “structural pattern matching”.

# Old
if key == 1:
  do_a()
elif key == 2:
  do_b()
else:
  do_c()

# New
match key:
    case 1:
        do_a()
    case 2:
        do_b()
    case _:
        do_c()

Añadido en Python 3.10, más detalles en el PEP 634.

5 Eliminación de sufijos y prefijos

Una tarea habitual a la hora de trabajar con strings es la limpieza o el tratamiento de los mismos a través de la eliminación de cierto prefijo o sufijo. Tradicionalmente en Python disponíamos de la función strip() dentro de las herramientas para manejo de strings de la biblioteca standard que nos podía ayudar en esta tarea. Sin embargo, strip no realiza la eliminación del prefijo o sufijo exactamente sino de los caracteres que definiéramos como argumento, dando lugar a errores y situaciones confusas si no se entendía bien el comportamiento de strip:

>>> 'Arthur: three!'.strip('Arthur: ')
'ee!'

Era necesario implementar nuestra propia función para eliminar sufijos y prefijos. Afortunadamente, con la introducción de los métodosremoveprefix() y removesuffix() ya disponemos de dos funciones de la librería standard que realizan estas tareas:

>>> 'Arthur: three!'.removeprefix('Arthur: ')
'three!'

Añadido en Python 3.9, más detalles en el PEP 616.

6 Operador para mezcla de diccionarios

Esta característica es personalmente una de las que más agradezco y que más uso. En mi experiencia, es muy habitual trabajar con diccionarios y tener que mezclar varios de ellos es un escenario típico. Gracias a la inclusión del operador union “|” y el union-in-place “|=” ahora podemos mezclar dos o más diccionarios de una forma elegante, limpia y trivial:

# Old 1
c = {**a, **b}

# Old 2
c = a.copy()
for key, value in b.items():
    c[key] = value

# New
c = a | b

# Old
a.update({33: "new_item"})

# New
a |= {33: "new_item"}

Añadido en Python 3.9, más detalles en el PEP 584.

7 Dataclasses

Este nuevo concepto añadido a la biblioteca estándar nos es muy útil a la hora de escribir clases que encapsulan datos y nos brindará una serie de propiedades muy útiles a la hora de trabajar con los objetos que instancian estas dataclasses.

El mecanismo principal de definición de dataclasses es a través del nuevo decorador @dataclass que podremos añadir en la definición de cualquiera de nuestras clases:

@dataclass
class Car:
    name: str
    power: int
    consumption: float
    trunk_capacity: int

Al decorar la clase lo que hace Python internamente es darnos la implementación de los métodos internos (init, repr, eq, ne, lt, le, gt, ge). Esto nos permite de forma muy rápida, sencilla y ahorrándonos muchas líneas de código trabajar con estos objetos pudiendo construirlos, compararlos, ordenarlos y representarlos de forma muy simple.

>>> scenic = Car('Renault Scenic', 160, 7.1, 506)
>>> touran = Car('Volkswagen Touran', 150, 6.7, 743)
>>> scenic
Car(name='Renault Scenic', power=160, consumption=7.1, trunk_capacity=506)
>>> scenic.power
160
>>> scenic == touran
False

Añadido en Python 3.7, más detalles en el PEP 557.

8 El operador de multiplicación de matrices

Este operador representado por el símbolo “@” permite multiplicar matrices de una forma más limpia y sencilla que usando los métodos de multiplicación correspondientes, especialmente interesante si necesitamos hacer varias multiplicaciones consecutivas:

import numpy as np
import tensorflow as tf

N = np.random.rand(5, 5)
T = tf.random.uniform((5, 5))

# Old
NNN = N.dot(N).dot(N)
TTT = tf.matmul(tf.matmul(T, T), T)

# New
NNN = N @ N @ N
TTT = T @ T @ T

Añadido en Python 3.5, más detalles en el PEP 465.

9 Cercanía matemática

Esta curiosa función de la biblioteca matemática de Python recibe como argumento dos valores numéricos y nos indica si son “cercanos” o no.

>>> math.isclose(1, 2)
False

>>> math.isclose(1, 1.0000000001)
True

¿Pero con qué criterio Python determina si dos números son cercanos o no, ya que realmente esto dependerá de nuestro contexto? En realidad lo que hace internamente esta función es aplicar la siguiente fórmula:

abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

Donde rel_tol y abs_tol son la tolerancia relativa y la tolerancia absoluta respectivamente. Estos valores los podemos pasar a la función como argumento opcional según nuestro contexto, aunque por defecto están fijados a rel_tol=1e-09 y abs_tol=0.0.

Añadido en Python 3.5, más detalles en el PEP 485.

10 Positional-Only Arguments

Esta nueva característica nos permite especificar argumentos en nuestras funciones Python que sean exclusivamente posicionales. Esto puede ser útil especialmente a la hora de construir bibliotecas o APIs.

El funcionamiento es a través del símbolo “/” que puede ser añadido al definir nuestra función en la lista de argumentos, de tal forma que los parámetros que preceden el símbolo “/” deben de ser obligatoriamente posicionales y los que vayan a continuación podrán ser parámetros nombrados:

def name(positional_only_parameters, /, positional_or_keyword_parameters, *, keyword_only_parameters):

Veamos un ejemplo:

>>> def pos_only_arg(arg, /):
...     print(arg)

>>> pos_only_arg(1)
1

>>> pos_only_arg(arg=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'

Añadido en Python 3.8, más detalles en el PEP 570.

Son todos los que están, pero no están todos los que son

¿Qué te han parecido estos trucos, los conocías? ¿Crees que son útiles o complican el aprendizaje del lenguaje? Deja un comentario con tu opinión y dinos qué otros “trucos” o funcionalidades conoces de las últimas versiones de Python y cuáles son tus favoritas.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.