22

Is it possible to have a namedtuple inside another namedtuple?

For example:

from collections import namedtuple

Position = namedtuple('Position', 'x y')
Token = namedtuple('Token', ['key', 'value', Position])

which gives a "ValueError: Type names and field names must be valid identifiers"

Also, I am curious if there is a more Pythonic approach to build such a nested container?

Yannis
  • 1,682
  • 7
  • 27
  • 45

2 Answers2

40

You are mixing up two concepts - structure of namedtuple, and values assigned to them. Structure requires list of unique names. Values may be anything, including another namedtuple.

from collections import namedtuple

Position = namedtuple('Position', 'x y')
Token = namedtuple('Token', ['key', 'value', 'position'])

t = Token('ABC', 'DEF', Position(1, 2))
assert t.position.x == 1
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • 3
    Silly me! Thanks for the explanation and the accompanied code. So, is that nested namedtuple considered a good practice? Do you know of any alternatives? – Yannis Apr 03 '16 at 11:59
  • 5
    It's perfectly fine data structure with another data structure nested inside it. If it's correct for problem you're solving, why not? You just have to keep in mind that it's a data structure, not a class. For complex objects you're usually better with creating a class with appropriate methods. – Łukasz Rogalski Apr 03 '16 at 12:01
1

Here a general function to transform a nested dictionary to a nested namedtuple

from collections import namedtuple

def dict2namedtuple(name, d):
    values, keys = [], []
    for k in d:
        keys.append(k)

        v = d[k]
        if isinstance(v, dict):
            values.append(dict2namedtuple(k, v))
        else:
            values.append(v)

    T = namedtuple(name, keys)
    return T(*values)


def namedtuple2dict(nt):
    d = {}
    for k in nt._fields:
        v = getattr(nt, k)
        try:
            d[k] = namedtuple2dict(v)
        except AttributeError:
            d[k] = v
    return d


test_dict = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}}
nt = dict2namedtuple('test', d=test_dict)
dc = namedtuple2dict(nt)
assert dc == test_dict
print('namedtuple', nt)
print('dict', dc)

EDIT: I added a function for the inverse problem namedtuple2dict. In my experience namedtuple._asidct() works fine, but @Chev_603 mentioned some problems.

scleronomic
  • 4,392
  • 1
  • 13
  • 43