Creating A Namedtuple Object Using Only A Subset Of Arguments Passed
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"