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
.
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)