Colecciones en Python: namedtuple()

Colecciones en Python: namedtuple()

Tabla de Contenido

Note

En este artículo, conocerás la función namedtuple del módulo collections de la biblioteca estándar de Python.

¿Qué es namedtuple()?

namedtuple() es una función que retorna una nueva clase, la cual permite acceder a los valores de una tupla tanto por índice como a través de un nombre definido al crear la tupla.

Primero, examinaremos la firma de la función para entender cómo funciona y, posteriormente, analizaremos por qué y cuándo es beneficioso usarla.

def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None):

Esta firma revela cinco argumentos importantes:

  • typename: el nombre que se asignará a la nueva clase creada.
  • field_names: ya sea un string o un iterable de strings que representan los nombres de los campos de la tupla. El orden de estos strings determina cómo se accederán los valores.
  • rename: un valor booleano que, si se activa, permite que la función reemplace nombres duplicados o conflictivos automáticamente.
  • defaults: permite definir valores predeterminados para los campos de la tupla, asignando valores desde el final hacia el inicio de la lista de campos.
  • module: un string que asocia la nueva tupla a un módulo específico.

Note

Los argumentos definidos después de * son argumentos que deben pasarse como keyword arguments (es decir, como nombre=valor), no de manera posicional.

Ejemplos de uso

Crear una namedtuple

Primero, veamos cómo crear una nueva tupla sencilla:

from collections import namedtuple

Coordinates = namedtuple("Coordinates", ["x", "y", "z"])

point_1 = Coordinates(1, 2, 3)
print(point_1)
print('x:', point_1.x, 'y:', point_1.y, 'z:', point_1.z)
print('x:', point_1[0], 'y:', point_1[1], 'z:', point_1[2])

El resultado de este código será:

$ python main.py
Coordinates(x=1, y=2, z=3)
x: 1 y: 2 z: 3
x: 1 y: 2 z: 3

Como observamos, creamos una nueva clase llamada Coordinates con campos x, y, y z. Luego instanciamos point_1 y accedimos a sus valores tanto por nombre como por índice.

Note

Al declarar point_1, los valores se asignan en el mismo orden en que se especifican en la tupla: primero x, después y, y finalmente z.

Crear una namedtuple con valores por defecto

Definiendo valores por defecto, que deben ser iterables, podemos especificar los valores que se asignarán a los campos si no se proporcionan al crear la instancia:

from collections import namedtuple

UserData = namedtuple(
    "UserData",
    ["name", "age", "email", "country"],
    defaults=("example@mail.com", "CO")
)

david = UserData( "David", 28, )
juan = UserData("Juan", 24, "juan@mail.com")

print(david)
print(juan)

El resultado de este código será:

UserData(name='David', age=28, email='example@mail.com', country='CO')
UserData(name='Juan', age=24, email='juan@mail.com', country='CO')

Los valores por defecto se asignan de derecha a izquierda a los campos que no tienen valores asignados al crear la instancia.

Pero debemos de tener en cuenta que si no pasamos todos los argumentos que no están definidos por defecto, la función lanzará un error.

unknown = UserData()
print(unknown)
Traceback (most recent call last):
  File "/home/dacacode/dacadev/personal-blog/tempCodeRunnerFile.python", line 9, in <module>
    unknown = UserData()
              ^^^^^^^^^^
TypeError: UserData.__new__() missing 2 required positional arguments: 'name' and 'age'

Controlar el renombramiento de los campos

Activando el argumento rename como True, la función automáticamente renombra campos duplicados o que sean palabras reservadas del lenguaje. Primero vamos a crear una tupla normal con nombres de campos que pueden entrar en conflicto.

from collections import namedtuple

dynamic_fields = ['name', 'def', 'arg', 'name']
EspecialInfo = namedtuple("EspecialInfo", dynamic_fields)

print(EspecialInfo)

Al ejecutar el código vas a obtener un error como el siguiente:

Traceback (most recent call last):
  File "/home/dacacode/dacadev/personal-blog/tempCodeRunnerFile.python", line 4, in <module>
    EspecialInfo = namedtuple("EspecialInfo", dynamic_fields)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dacacode/.pyenv/versions/3.11.5/lib/python3.11/collections/__init__.py", line 398, in namedtuple
    raise ValueError('Type names and field names cannot be a '..)
ValueError: Type names and field names cannot be a keyword: 'def'

Pero si habilitamos la opcion de rename el código funcionará, ya que la función se encarga de procesar esos casos.

from collections import namedtuple

dynamic_fields = ['name', 'def', 'arg', 'name']
EspecialInfoV2 = namedtuple("EspecialInfoV2", dynamic_fields, rename=True)

print(EspecialInfoV2)
data = EspecialInfoV2('David', 'print', 'Hello, World!', 'David')
print(data)

Ahora si obtendremos un resultado positivo:

<class '__main__.EspecialInfoV2'>
EspecialInfoV2(name='David', _1='print', arg='Hello, World!', _3='David')

En donde podemos ver que los atributos que son keywords y duplicados han sido renombrados por valores posicionales, en este caso _1 y _3.

Las namedtuples son Clases

Por último, pero no menos importante, recuerda que el resultado de la función namedtuple es una clase que hereda de tuple, lo que significa que podemos usarla como cualquier otra clase. Por ejemplo, podemos usar la nueva clase como padre de otra para extender su funcionalidad:

from collections import namedtuple

Coordinates = namedtuple("Coordinates", ["x", "y", "z"])

class Point(Coordinates):
    
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y, self.z + other.z)

point_1 = Point(1, 2, 3)
point_2 = Point(4, 5, 6)

print(point_1)
print(point_2)
print(point_1 + point_2)

El resultado de este código será:

Point(x=1, y=2, z=3)
Point(x=4, y=5, z=6)
Point(x=5, y=7, z=9)

¿Por qué usar namedtuples?

Una de las principales razones para usar las namedtuple es la mejora de la legibilidad del código, ya que al momento los atributos para acceder a sus elementos es más fácil de entender que si lo hacemos por índice.

from collections import namedtuple

UserInfo = namedtuple('UserInfo', ['name', 'age', 'email'])

# Mejora la legibilidad
user = UserInfo('David', 28, 'mail@mail.com')

user.name # David
user.age # 30
user.email # mail@mail.com

# Debes tener en tu mente el orden de los campos, no es lo ideal!
userRaw = {'David', 28, 'mail@mail.com'}
user[0] # David
user[1] # 30
user[2] # mail@mail.com

Otros beneficios que encontramos es en la eficiencia del código, ya que al utilizar tuplas (o nametuples) encontramos que:

  • Las tuplas tienen mayor rendimiento al momento de ser creadas, debido a que son objetos inmutables y su alocación en memoria es mucho ams eficiente que las clasicas lsitas o diccionarios.
  • Son ideales para procesar información que debe mantenerse constante durante todo su tiempo de vida, ya que las tuplas son inmutables.
  • El tiempo de acceso a los elementos de la tupla es más rápido que las listas o los diccionarios.

Note

Esto no significa que las tuplas sean mejores que otras estructuras de datos, esto depende de cada caso en particular y debes analizarlo antes de tomar una decisión.

Info

Para más información sobre namedtuple, visita la documentación oficial en este enlace

Etiquetas :