Why doesn't rename seem to work for me with these invalid key names in my dictionary?

PyDev console: starting.
Python 3.13.2 (tags/v3.13.2:4f8bb39, Feb 4 2025, 15:23:48) [MSC v.1942 64 bit (AMD64)] on win32
from collections import namedtuple
def tuplify_rename():
data = {‘name’: ‘Bob’, ‘age’:44, ‘city’:‘rome’, ‘zip-code’: ‘10001’, ‘for’: ‘reserved_word’}
Person = namedtuple(‘Person’, [‘name’, ‘age’, ‘city’, ‘zip-code’, ‘for’], rename=True)
return Person(**data)
print(tuplify_rename())
Traceback (most recent call last):
File “”, line 1, in
File “”, line 4, in tuplify_rename
TypeError: Person.new() got an unexpected keyword argument ‘zip-code’

A namedtuple is not like a dict. The fieldnames must be valid as attribute names.

For example, the fieldname 'name' becomes the attribute name, so if p is a Person, then p.name is that person’s name.

zip-code is not valid as an attribute name. You could not say p.zip-code for that person’s zipcode; that would be interpreted as p.zip - code, i.e. the person’s zip(?) minus the value of code.

1 Like

The namedtuple maps invalid field names to sequential field names starting with _.
So if I had instantiated the namedtuple positionally it works fine:
print(Person(‘Bob’, 44, ‘rome’, 10001, ‘reserved word’))
Person(name=‘Bob’, age=44, city=‘rome’, _3=10001, _4=‘reserved word’)

I guess I was expecting the namedtuple to retain the mapping of zip-code to _3 and ‘for’ to _4
so that when I instantiated it with a map (containing the invalid names) it would be able to figure out that zip-code should be mapped to _3 and ‘for’ to _4.

But I guess not…

Looks like renaming happens once during class creation, and original field names are not kept. As far as namedtuple is concerned it is just a regular class with the following fields:

In [38]: Person._fields
Out[38]: ('name', 'age', 'city', '_3', '_4')

I ended up writing a sort of extension to handle maps. Seems to work ok (newbie to Python, so guessing code is not very pythonic)

from collections import namedtuple

def named_tuple_extension(tuple_name, *args, **kwargs):
    """
    Create a named tuple with the given name and arguments.
    """
    Struct = namedtuple(tuple_name, *args, **kwargs)
    Struct.__new__.__defaults__ = (None,) * len(Struct._fields)

    field_dict = {}
    struct_fields = Struct._fields
    i = 0
    for tuple_fields in args:
        for arg in tuple_fields:
            field_dict[arg] = struct_fields[i]
            i += 1

    class InnerStruct(Struct):
        def __new__(self, *_args, **_kwargs):
            return self.create(self, *_args, **_kwargs)
        def __init__(*_args, **_kwargs):
            Struct(_args)
        def create(self, *_args, **_kwargs):
            if len(_kwargs) > 0 and isinstance(_kwargs, dict):
                return get_map_struct(tuple_name, *_args, **_kwargs)
            else:
                return Struct(*_args)

    return InnerStruct

def get_map_struct(tuple_name, *args, **_kwargs):
    _kwargs['field_mapping'] = {}
    _Struct = namedtuple(tuple_name, _kwargs, rename=True)
    _Struct.__new__.__defaults__ = (None,) * len(_Struct._fields)

    def get_value(self, attribute_name):
        translated_key = self._asdict().get('field_mapping', None).get(attribute_name, attribute_name)
        return getattr(self, translated_key, None)

    _Struct.get_value = get_value
    _field_dict = _kwargs['field_mapping']
    _struct_fields = _Struct._fields
    for i, arg in enumerate(_kwargs):
        _field_dict[arg] = _struct_fields[i]
    map_aliased_args = {}
    for arg in _kwargs:
        alias_key = _field_dict.get(arg)
        map_aliased_args[alias_key] = _kwargs[arg]
    return _Struct(**map_aliased_args)

data = {'1ws': 3, 'name': 'Joe', '2ws': '2ws', 'age': 32, 'city': 'New York', 'zip-code': '10001', 'for': 'reserved_word'}

Person = named_tuple_extension('Person', ['name', 'age', 'city', 'zip-code', 'for'], rename=True)
person_from_map = Person(**data)
print(f'zip by invalid key {person_from_map.get_value('zip-code')}')
print(f'zip by valid key {person_from_map.get_value('_5')}')
print(person_from_map.city)

person_from_values = Person("Dave", 45, "Chicago", "60601", "reserved_word")
print(person_from_values)
print(person_from_values.age)