Python 3.9: nuevo analizador sintáctico, tipado genérico incorporado, y mejoras en diccionarios

Estamos ad portas de la nueva versión de Python, y desde ya tres características se han hecho notar: operadores de fusión y actualización de diccionarios, incorporación del tipado fuerte de colecciones genéricas, y el uso de un nuevo analizador sintáctico. Además hay cambios en múltiples módulos y como suele ocurrir con cada versión, mejoras en el rendimiento.

Nuevos operadores de manipulación de diccionarios

Los diccionarios se han convertido en una de las estructuras de datos más usadas en todos los lenguajes de programación, e incluso hoy en día se usan en la gran mayoría de API. Dada su simplicidad y lectura amigable han reemplazado al XML en muchos ámbitos. Es por esto que coloco en primer lugar esta novedad de Python 3.9.

Esta versión traerá dos nuevos operadores para diccionarios: el operador Merge (|), y el operador Update (|=). Estos operadores arrojan una excepción lógica TypeError hasta la versión 3.8 cuando los operandos son diccionarios.

Además de la facilidad que conlleva el uso de los nuevos operadores, esto también resuelve el problema de la ineficiencia de los actuales métodos de manipulación de diccionarios (D’Aprano, S. & Bucher, B.). Quienes vivan de diccionarios en Python alguna vez se habrán topado con métodos como update() y ChainMap() para fusionarlos, o incluso habrán recurrido a trucos como el desempaquetamiento. De este último: es algo perverso y oscuro que solo introduce bugs en el código, ya que ignora el tipado de los datos o requiere de condiciones especiales para su correcta operación, que aunque funcione, viola los principios de portabilidad, sin contar con la ineficiencia.

La mejor forma de ejemplificar el uso de los nuevos operadores es con un corto y sencillo ejemplo:

dict1 = {'foo': 'bar'}
dict2 = {'hello': 'world'}
dict3 = {'hola': 'mundo'}



### "Merge" operator ###
dict_merged = dict1 | dict2
print(dict_merged)

# Results:
# Python 3.8: TypeError
# Python 3.9: {'foo': 'bar', 'hello': 'world'}



### "Update" operator ###
dict_merged |= dict3
print(dict_merged)

# Results:
# Python 3.8: TypeError
# Python 3.9: {'foo': 'bar', 'hello': 'world', 'hola': 'mundo'}

La introducción de estos operadores fue una controversia en donde hasta se alegó que iban en contra de la filosofía de Python. Afortunadamente la decisión fue afirmativa, y de seguro muchos desarrolladores se alegrarán y estarán muy agradecidos.

Incorporación del tipado fuerte de colecciones genéricas

Crecí con Visual Basic, ASP 3.0 y PHP 4.3. Una característica en común de estos lenguajes es que no eran tipados, no había que declarar un tipo explícito junto a las variables. Más tarde me enamoraría de C#; la sintaxis tipo C no fue dura ya que era similar a PHP, sin embargo tuve que cambiar el paradigma de la variable multiusos al que estaba acostumbrado, ya que ahora tenía que definir su tipo de manera explícita. Este punto me ayudó a refinar mis buenas prácticas de programación, y también incentivó a programar siguiendo el principio de responsabilidad única.

Ahora bien, en Python hay algunos casos en los que el tipado fuerte no es posible, al menos sin importar ningún módulo específico para esto. Por ejemplo, si quieres declarar una función que retorna un diccionario con tres propiedades de tipo str, int e int. En este caso solo puedes hacerlo al importar el módulo annotations: from __future__ import annotations. Desde la versión 3.9 ahora los tipos de colección genéricos vienen incorporados, o sea que ya no se requiere de una importación adicional. Los tipos más usados que se incorporan son tuple, list, dict, set, frozenset, type, entre otros.

De nuevo, explico esta característica con un ejemplo:

def hello(name: str, birthYear: int, currentYear: int) -> dict[str, int, bool]:
    age = currentYear - birthYear
    return {"name": name, "age": age, "adult": age >= 18}

print(hello("Julian", 1950, 2020))

# Results:
# Python 3.8: TypeError: 'type' object is not subscriptable
# Python 3.9: {'name': 'Julian', 'age': 70, 'adult': True}

Para que este fragmento de código funcione en python 3.8 es necesario importar annotations como lo expliqué recientemente.

Nuevo analizador sintáctico

Los idiomas al rededor del mundo siguen unas reglas sintácticas: dirección de la escritura, caracteres, construcción de las oraciones, orden de los diferentes componentes dentro de una premisa, etc. Algunos idiomas son más flexibles y ricos en expresiones que otros, y otros pueden expresar ideas en menos caracteres.

Los lenguajes de programación se basan en esta teoría. Python es un lenguaje per se, por lo tanto debe seguir ciertas reglas para que el algoritmo tenga algún significado. Estas reglas están definidas por el analizador sintáctico –el intérprete–, y es el que se encarga de transliterar nuestros algoritmos al lenguaje binario. Hasta Python 3.8, se usa un analizador sintáctico de tipo LL. La regla fundamental de este tipo de analizador es que la dirección de la escritura es de izquierda a derecha, o sea, las reglas empiezan siempre por el término que se encuentra al principio de la línea de código.

El analizador sintáctico de Python desde la versión 3.9 es de tipo PEG. La gramática fundamental de este intérprete se basa en el análisis sintáctico de las expresiones: el proceso se centra en el reconocimiento de patrones dentro de los algoritmos. El proceso es parecido a las expresiones regulares, pero es más robusto y por cierto requiere de más recursos. PEG también cumple con prioridades, lo que resuelve algunos problemas de ambigüedad frente a analizadores de tipo LL.

La razón principal por la que este cambio se hizo en Python es debido a la limitación de los analizadores LL sobre los PEG, y Python, aunque era interpretado por LL, hoy en día es más natural y lógico ser interpretado por PEG. La consecuencia de este cambio es transparente en los altos niveles de abstracción, por lo que no será notorio, y apenas se percibe una leve mejora en el rendimiento. Estos cambios impactan en mayor medida a niveles más bajos. Por ejemplo, uno de los módulos que está directamente relacionado con este cambio es ast, uno de los módulos que trabaja al más bajo nivel, donde los desarrolladores ya no exploran.

Hay más cambios

La nueva entrega de Python trae cientos de cambios y mejoras en módulos, retrocompatibilidad y características auxiliares que facilitarán la vida del desarrollador. Esta es una oportunidad para migrar los códigos que aun corren sobre Python 2.7, pero de esta versión moribunda ya hablaré luego.

Referencias

  • D’Aprano, S. & Bucher, B. (2019). Add Union Operators To dict. Python Enhancement Proposals #584. Tomado de https://www.python.org/dev/peps/pep-0584/
  • Langa, Ł. (2019). Type Hinting Generics In Standard Collections. Python Enhancement Proposals #585. Tomado de https://www.python.org/dev/peps/pep-0585/
  • Rossum, Galindo, & Nikolaou (2020). New PEG parser for CPython. Python Enhancement Proposals #617. Tomado de https://www.python.org/dev/peps/pep-0617/

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

%d