pythonpython-2.7keyword-argumentnamedtuple

Make namedtuple accept kwargs


If I have a class like:

class Person(object):
    def __init__(self, name, **kwargs):
        self.name = name

p = Person(name='joe', age=25) # age is ignored

Extra params are ignored. But if I have a namedtuple, I'll get `unexpected keyword argument:

from collections import namedtuple 

Person = namedtuple('Person', 'name')
p = Person(name='joe', age=25)

# Traceback (most recent call last):
#   File "python", line 1, in <module>
# TypeError: __new__() got an unexpected keyword argument 'age'

How can I make namedtuple accept kwargs so I can pass extra arguments safely?


Solution

  • The following session in the interpreter shows one possible solution to fixing your problem:

    Python 3.5.0 (v3.5.0:374f501f4567, Sep 13 2015, 02:27:37) [MSC v.1900 64 bit (AMD64)] on win32
    Type "copyright", "credits" or "license()" for more information.
    >>> import collections
    >>> class Person(collections.namedtuple('base', 'name')):
        __slots__ = ()
        def __new__(cls, *args, **kwargs):
            for key in tuple(kwargs):
                if key not in cls._fields:
                    del kwargs[key]
            return super().__new__(cls, *args, **kwargs)
    
    
    >>> p = Person(name='joe', age=25)
    >>> p
    Person(name='joe')
    >>> 
    

    Alternative:

    Since you rather have a simpler solution, you might find the next program more to your liking:

    #! /usr/bin/env python3
    import collections
    
    
    def main():
        Person = namedtuple('Person', 'name')
        p = Person(name='joe', age=25)
        print(p)
    
    
    def namedtuple(typename, field_names, verbose=False, rename=False):
        base = collections.namedtuple('Base', field_names, verbose, rename)
        return type(typename, (base,), {
            '__slots__': (),
            '__new__': lambda cls, *args, **kwargs: base.__new__(cls, *args, **{
                key: value for key, value in kwargs.items()
                if key in base._fields})})
    
    
    if __name__ == '__main__':
        main()