Skip to content

Instantly share code, notes, and snippets.

@caruccio
Created September 22, 2012 04:49
Show Gist options
  • Save caruccio/3765157 to your computer and use it in GitHub Desktop.
Save caruccio/3765157 to your computer and use it in GitHub Desktop.
metattuple - recursively creating a namedtuple class from a dict object using a metaclass
''' metatuple - Recursive namedtuple from arbitrary dict
After 2 hours of intensive coding and some tequila sips I found
a "simple" solution to create a namedtuple from any dictionary,
recursivelly creating any necessary namedtuple.
Probably there are tons of easiest ways of doing that, like some
well documented method or standart function in the python library,
but that wouldn't be fun.'''
from collections import namedtuple
def metatuple(name, attrs):
'''Creates a namedtuple from an arbitrary dict,
recursivelly namedtuple()ing any dict it finds on the way.
>>> person = {
... 'name': 'Charlotte',
... 'address': {
... 'street': 'Acacia Avenue',
... 'number': 22,
... },
... }
>>> Person = metatuple('Person', person)
>>> charlotte = Person(**person)
>>> assert charlotte.name == 'Charlotte'
>>> assert charlotte.address.street == 'Acacia Avenue'
>>> assert charlotte.address.number == 22
# ensure dict's random key ordering don't affect correctness
>>> rand_dict = {
... 'a_field0': 0,
... 'b_field1': 1,
... 'c_field2': 2,
... 'd_field3': 3,
... 'e_field4': 4,
... 'f_field5': 5,
... 'g_field6': 6,
... }
>>> RandDict = metatuple('RandDict', rand_dict)
>>> rand = RandDict(**rand_dict)
>>> assert rand.a_field0 == 0
>>> assert rand.b_field1 == 1
>>> assert rand.c_field2 == 2
>>> assert rand.d_field3 == 3
>>> assert rand.e_field4 == 4
>>> assert rand.f_field5 == 5
>>> assert rand.g_field6 == 6
'''
class _meta_cls(type):
'''Metaclass to replace namedtuple().__new__ with a recursive version _meta_new().'''
def __new__(mcs, name, bases, metadict):
def _meta_new(_cls, **kv):
return tuple.__new__(_cls, ([ (metatuple('%s_%s' % (_cls.__name__, k), kv[k].keys())(**kv[k]) if isinstance(kv[k], dict) else kv[k]) for k in _cls._fields]))
metadict['__new__'] = _meta_new
return type.__new__(mcs, bases[0].__name__, bases, metadict)
class _metabase(namedtuple(name, ' '.join(attrs))):
'''Wrapper metaclass for namedtuple'''
__metaclass__ = _meta_cls
return _metabase
if __name__ == "__main__":
import doctest
doctest.testmod()
@floer32
Copy link

floer32 commented Jul 9, 2013

This is very interesting!

I was trying to accomplish a similar end-goal to you but wanted to avoid using metaclasses (for simpler code); this is what I came up with: https://gist.github.com/hangtwenty/5960435

Cheers

@caruccio
Copy link
Author

caruccio commented May 8, 2014

@hangtwenty Funny is that I can't even understand my code anymore :)
Your is much more readable, plus it supports lists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment