namedtuple — Subclase de tupla con campos con nombre

La tuple estándar usa índices numéricos para acceder a sus miembros.

collections_tuple.py
bob = ('Bob', 30, 'male')
print('Representation:', bob)

jane = ('Jane', 29, 'female')
print('\nField by index:', jane[0])

print('\nFields by index:')
for p in [bob, jane]:
    print('{} is a {} year old {}'.format(*p))

Esto hace a las tuples contenedores convenientes para usos simples.

$ python3 collections_tuple.py

Representation: ('Bob', 30, 'male')

Field by index: Jane

Fields by index:
Bob is a 30 year old male
Jane is a 29 year old female

Por el contrario, recordar qué índice se debe usar para cada valor puede conducir a errores, especialmente si la tuple tiene muchos campos y está# construida lejos de donde se usa. Una namedtuple asigna nombres, así como el índice numérico, a cada miembro.

Definiendo

Las instancias namedtuple son igual de eficientes en la memoria que las tuplas normales porque no tienen diccionarios por instancia. Cada tipo de namedtuple está representada por su propia clase, que es creada usando la función de fábrica namedtuple(). Los argumentos son nombre de la nueva clase y una cadena que contiene los nombres de los elementos.

collections_namedtuple_person.py
import collections

Person = collections.namedtuple('Person', 'name age')

bob = Person(name='Bob', age=30)
print('\nRepresentation:', bob)

jane = Person(name='Jane', age=29)
print('\nField by name:', jane.name)

print('\nFields by index:')
for p in [bob, jane]:
    print('{} is {} years old'.format(*p))

Como lo ilustra el ejemplo, es posible acceder a los campos de la namedtuple por nombre usando notación punteada (obj.attr) así como usando los índices posicionales de las tuplas estándar.

$ python3 collections_namedtuple_person.py


Representation: Person(name='Bob', age=30)

Field by name: Jane

Fields by index:
Bob is 30 years old
Jane is 29 years old

Al igual que una tuple normal, una namedtuple es inmutable. Esta restricción permite que las instancias tuple tengan un valor hash consistente, que hace posible usarlos como claves en diccionarios y para ser incluidos en conjuntos.

collections_namedtuple_immutable.py
import collections

Person = collections.namedtuple('Person', 'name age')

pat = Person(name='Pat', age=12)
print('\nRepresentation:', pat)

pat.age = 21

Intentar cambiar un valor a través de su atributo nombrado da como resultado un AttributeError.

$ python3 collections_namedtuple_immutable.py


Representation: Person(name='Pat', age=12)
Traceback (most recent call last):
  File "collections_namedtuple_immutable.py", line 17, in
<module>
    pat.age = 21
AttributeError: can't set attribute

Nombre de campo inválidos

Los nombres de campo no son válidos si se repiten o entran en conflicto con palabras clave Python.

collections_namedtuple_bad_fields.py
import collections

try:
    collections.namedtuple('Person', 'name class age')
except ValueError as err:
    print(err)

try:
    collections.namedtuple('Person', 'name age age')
except ValueError as err:
    print(err)

A medida que se analizan los nombres de los campos, los valores no válidos causan excepciones ValueError.

$ python3 collections_namedtuple_bad_fields.py

Type names and field names cannot be a keyword: 'class'
Encountered duplicate field name: 'age'

En situaciones donde se crea una namedtuple basada en valores fuera del control del programa (como para representar las filas devueltas por una consulta de base de datos, donde el esquema no se conoce con atelación), la opción rename debe establecerse en True así que los campos inválidos son renombrados

collections_namedtuple_rename.py
import collections

with_class = collections.namedtuple(
    'Person', 'name class age',
    rename=True)
print(with_class._fields)

two_ages = collections.namedtuple(
    'Person', 'name age age',
    rename=True)
print(two_ages._fields)

Los nuevos nombres para campos renombrados dependen de su índice en la tupla, entonces el campo con el nombre class se convierte en _1 y el campo duplicado age se cambia a _2.

$ python3 collections_namedtuple_rename.py

('name', '_1', 'age')
('name', 'age', '_2')

Atributos especiales

namedtuple proporciona varios atributos y métodos útiles para trabajar con subclases e instancias. Todos estas propiedades incorporadas tienen nombres con un guion bajo (_), que por convención en la mayoría de los programas de Python indica un atributo privado. Para namedtuple, sin embargo, el prefijo está destinado a proteger el nombre de una colisión con nombres de atributos proporcionados por el usuario.

Los nombres de los campos pasados a namedtuple para definir la nueva clase se guardan en el atributo _fields.

collections_namedtuple_fields.py
import collections

Person = collections.namedtuple('Person', 'name age')

bob = Person(name='Bob', age=30)
print('Representation:', bob)
print('Fields:', bob._fields)

Aunque el argumento es una cadena única separada por espacios, el valor almacenado es la secuencia de nombres individuales.

$ python3 collections_namedtuple_fields.py

Representation: Person(name='Bob', age=30)
Fields: ('name', 'age')

Las instancias namedtuple se pueden convertir a instancias OrderedDict usando _asdict().

collections_namedtuple_asdict.py
import collections

Person = collections.namedtuple('Person', 'name age')

bob = Person(name='Bob', age=30)
print('Representation:', bob)
print('As Dictionary:', bob._asdict())

Las llaves del OrderedDict están en el mismo orden que los campos para la namedtuple.

$ python3 collections_namedtuple_asdict.py

Representation: Person(name='Bob', age=30)
As Dictionary: OrderedDict([('name', 'Bob'), ('age', 30)])

El método _replace() crea una nueva instancia, reemplazando los valores de algunos campos en el proceso.

collections_namedtuple_replace.py
import collections

Person = collections.namedtuple('Person', 'name age')

bob = Person(name='Bob', age=30)
print('\nBefore:', bob)
bob2 = bob._replace(name='Robert')
print('After:', bob2)
print('Same?:', bob is bob2)

Aunque el nombre implica que está modificando el objeto existente, porque las namedtuple son inmutables, el método en realidad devuelve un nuevo objeto.

$ python3 collections_namedtuple_replace.py


Before: Person(name='Bob', age=30)
After: Person(name='Robert', age=30)
Same?: False