Skip to content Skip to sidebar Skip to footer

Creating A Namedtuple Object Using Only A Subset Of Arguments Passed

I am pulling rows from a MySQL database as dictionaries (using SSDictCursor) and doing some processing, using the following approach: from collections import namedtuple class Foo(

Solution 1:

First, you have to override __new__ in order to customize namedtuple creation, because a namedtuple's __new__ method checks its arguments before you even get to __init__.

Second, if your goal is to accept and filter keyword arguments, you need to take **kwargs and filter and pass that through, not just *args.

So, putting it together:

classFoo(namedtuple('Foo', ['id', 'name', 'age'])):
    __slots__ = ()

    def__new__(cls, *args, **kwargs):
        kwargs = {k: v for k, v in kwargs.items() if k in cls._fields}
        returnsuper(Foo, cls).__new__(cls, *args, **kwargs)

You could replace that dict comprehension with itemgetter, but every time I use itemgetter with multiple keys, nobody understands what it means, so I've reluctantly stopped using it.


You can also override __init__ if you have a reason to do so, because it will be called as soon as __new__ returns a Foo instance.

But you don't need to just for this, because the namedtuple's __init__ doesn't take any arguments or do anything; the values have already been set in __new__ (just as with tuple, and other immutable types). It looks like with CPython 2.7, you actually cansuper(Foo, self).__init__(*args, **kwargs) and it'll just be ignored, but with PyPy 1.9 and CPython 3.3, you get a TypeError. At any rate, there's no reason to pass them, and nothing saying it should work, so don't do it even in CPython 2.7.

Note that you __init__ will get the unfiltered kwargs. If you want to change that, you could mutate kwargs in-place in __new__, instead of making a new dictionary. But I believe that still isn't guaranteed to do anything; it just makes it implementation-defined whether you get the filtered args or unfiltered, instead of guaranteeing the unfiltered.


So, can you wrap this up? Sure!

defLenientNamedTuple(name, fields):
    classWrapper(namedtuple(name, fields)):
        __slots__ = ()
        def__new__(cls, *args, **kwargs):
            args = args[:len(fields)]
            kwargs = {k: v for k, v in kwargs.items() if k in fields}
            returnsuper(Wrapper, cls).__new__(cls, *args, **kwargs)
    return Wrapper

Note that this has the advantage of not having to use the quasi-private/semi-documented _fields class attribute, because we already have fields as a parameter.

Also, while we're at it, I added a line to toss away any excess positional arguments, as suggested in a comment.


Now you just use it as you'd use namedtuple, and it automatically ignores any excess arguments:

classFoo(LenientNamedTuple('Foo', ['id', 'name', 'age'])):
    passprint(Foo(id=1, name=2, age=3, spam=4))

    print(Foo(1, 2, 3, 4, 5))     print(Foo(1, age=3, name=2, eggs=4))


I've uploaded a test, replacing the dict comprehension with dict() on a genexpr for 2.6 compatibility (2.6 is the earliest version with namedtuple), but without the args truncating. It works with positional, keyword, and mixed args, including out-of-order keywords, in CPython 2.6.7, 2.7.2, 2.7.5, 3.2.3, 3.3.0, and 3.3.1, PyPy 1.9.0 and 2.0b1, and Jython 2.7b.

Solution 2:

A namedtuple type has an attribute _fields which is a tuple of the names of the fields in the object. You could use this to dig out the required fields from the database record.

Solution 3:

These answers all seem overly-complex. Do you really want new classes and overloading instead of just writing a line of code or a helper function to instantiate a standard datatype the way you want?

Foo = namedtuple('Foo', ['id', 'name', 'age'], defaults=(None,) * 3)
Bar = namedtuple('Bar', ['id', 'address', 'city', 'state'], defaults=(None,) * 4)

poo = {'id': 1, 'age': 'Orz', 'city': 'Tucson', 'weight': True}
ooh = {'id': 2, 'name': 'Spathi', 'state': 'Iowa', 'children': '25'}
>>> Foo(*[poo[f] if f in poo elseNonefor f in Foo._fields])
Foo(id=1, name=None, age='Orz')

Ta-daa!

Or make a little helper.

# nt should have defaultsdefnt_from_kwargs(nt, **kwargs):
    return nt(**dict(i for i in kwargs.items() if i[0] in nt._fields))
>>> nt_from_kwargs(Foo, id=1, age='Orz', city='Tucson', weight=True)
Foo(id=1, name=None, age='Orz')

>>> nt_from_kwargs(Bar, **poo)
Bar(id=1, address=None, city='Tucson', state=None)

>>> nt_from_kwargs(Bar, **{**poo, **ooh})
Bar(id=2, address=None, city='Tucson', state='Iowa')

And everybody likes dictionaries.

defnt_from_dict(nt, d):
    return nt(*[d[k] if k in d elseNonefor k in nt._fields])
>>> nt_from_dict(Foo, poo)
Foo(id=1, name=None, age='Orz')

>>> nt_from_dict(Bar, poo)
Bar(id=1, address=None, city='Tucson', state=None)

>>> nt_from_dict(Bar, {**poo, **ooh})
Bar(id=2, address=None, city='Tucson', state='Iowa')

Post a Comment for "Creating A Namedtuple Object Using Only A Subset Of Arguments Passed"