from collections import namedtuple as namedtuple
def as_namedtuple(*fields_and_global_default, **defaults):
"""A class decorator factory joining the class with a namedtuple.
If any of the expected arguments are not passed to the class, they
are set to the specific default value or global default value (if any).
"""
num_args = len(fields_and_global_default)
if num_args > 2 or num_args < 1:
raise TypeError("as_namedtuple() takes at 1 or 2 positional-only "
"arguments, {} given".format(num_args))
else:
fields, *global_default_arg = fields_and_global_default
if isinstance(fields, str):
fields = fields.replace(',', ' ').split()
for field in defaults:
if field not in fields:
raise ValueError("got default for a non-existant field ({!r})"
.format(field))
# XXX unnecessary if namedtuple() got support for defaults.
@classmethod
def with_defaults(cls, *args, **kwargs):
"""Return an instance with defaults populated as necessary."""
# XXX or dynamically build this method with appropriate signature
for field, arg in zip(fields, args):
if field in kwargs:
raise TypeError("with_defaults() got multiple values for "
"keyword argument {!r}".format(field))
kwargs[field] = arg
for field, default in defaults.items():
if field not in kwargs:
kwargs[field] = default
if global_default_arg:
default = global_default_arg[0]
for field in fields:
if field not in kwargs:
kwargs[field] = default
return cls(**kwargs)
def decorator(cls):
"""Return a new nametuple-based subclass of cls."""
# Using super() (i.e. the MRO) makes this work correctly.
bases = (namedtuple(cls.__name__, fields), cls)
namespace = {'__doc__': cls.__doc__, '__slots__': (),
'with_defaults': with_defaults}
return type(cls.__name__, bases, namespace)
return decorator
Diff to Previous Revision
--- revision 2 2013-02-14 18:48:41
+++ revision 3 2013-02-14 19:58:13
@@ -45,7 +45,7 @@
def decorator(cls):
"""Return a new nametuple-based subclass of cls."""
# Using super() (i.e. the MRO) makes this work correctly.
- bases = (cls, namedtuple(cls.__name__, fields))
+ bases = (namedtuple(cls.__name__, fields), cls)
namespace = {'__doc__': cls.__doc__, '__slots__': (),
'with_defaults': with_defaults}
return type(cls.__name__, bases, namespace)