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 

Additional parameters are ignored. But if I have namedtuple , I will get an `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 to safely pass extra arguments?

+5
source share
3 answers

The following session in the interpreter shows one possible solution to the 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 have a simpler solution, you can find the following program 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() 
+7
source

It's not beautiful:

 p = Person(*(dict(name='joe', age=25)[k] for k in Person._fields)) 
+6
source

You can bypass the constructor for the Person class to ignore arguments that are not defined as Person namedtuple fields:

 from collections import namedtuple Person = namedtuple('Person', 'name') def make_person(*args, **kwargs): person_args = {} # process positional args if len(args) > len(Person._fields): msg = "Person() takes %d positional arguments but %d were given" % (len(Person._fields), len(args)) raise TypeError(msg) for arg_name, arg_value in zip(Person._fields, args): person_args[arg_name] = arg_value # process keyword args for arg_name, arg_value in kwargs.items(): try: i = Person._fields.index(arg_name) except ValueError: pass # ignore arguments not defined as Person fields else: if arg_name in person_args: msg = "make_person() got multiple values for argument " + repr(arg_name) raise TypeError(msg) person_args[arg_name] = arg_value if len(person_args) != len(Person._fields): msg = "Person() requires additional arguments: " msg += ", ".join([repr(x) for x in Person._fields if x not in person_args]) raise TypeError(msg) return Person(*[person_args[x] for x in Person._fields]) 

Given the foregoing:

 >>> make_person('a') Person(name='a') >>> make_person('a', b='b') Person(name='a') >>> make_person('a', name='b') TypeError: make_person() got multiple values for argument 'name' >>> make_person(b='b') TypeError: Person() requires additional arguments: 'name' >>> make_person(1, 2) TypeError: Person() takes 1 positional arguments but 2 were given 
+2
source

Source: https://habr.com/ru/post/1244231/


All Articles