Search code examples
pythondictionarykeyword-argument

What is the benefit of using kwargs (or args) over a simple dict?


I'd like to get idea why should I use kwargs or args over passing in a simple dict (or tuple in case of args)?

I wrote a very simple code snippet to check what exactly happens and I can't find any pros to use kwargs over a dict. If anyone could tell me why should I use those I'd be happy. Now as I can see it just more pythonic but don't makes any difference. Also if you use a simple dict then it's more readable because all the languages can do that but not the kwargs way.

def test_normal(input: dict):
    for element in input.items():
        print('Type: {}, raw: {}'.format(type(input), input))
        print('key: {}, value: {}'.format(element[0], element[1]))

def test_kwargs(**kwargs):
    for element in kwargs.items():
        print('Type: {}, raw: {}'.format(type(kwargs), kwargs))
        print('key: {}, value: {}'.format(element[0], element[1]))

test_normal(dict(name='Joseph'))
test_kwargs(name='Joseph')
Type: <class 'dict'>, raw: {'name': 'Joseph'}
key: name, value: Joseph
Type: <class 'dict'>, raw: {'name': 'Joseph'}
key: name, value: Joseph

Solution

  • These are different things and both have their use-cases. Just a rule of thumb: If it looks like a function parameter, it should be a function parameter.

    There are several neat use-cases for *args and **kwargs. One of which is passing through parameters that you don't care about at this point:

    Say you have class Base and class A inherited from Base:

    class Base:
        def __init__(self, x, y, z):
            self.x = x
            self.y = y
            self.z = z
    
    class A(Base):
        def __init__(self, n, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.n = n
    

    As you see class A does not care about Base's __init__ parameters, so it just passes everything (except n that it needs) forward. So if you were to change Base's __init__ you would not need to change A.

    But when you create A object you would pass parameters normally:

    a = A(5, 3, y=6, z=42)
    

    A similar idea is when you implement a decorator which you'd like to use on a function with any kind and number of arguments:

    def say_hello_first(fn):
        def wrapper(*args, *kwargs):
            print('Hello')
            return fn(*args, **kwargs)
        return wrapper
    
    @say_hello_first
    def foo(x):
        print(x)
    
    @say_hello_first
    def bar(a, b, c=3.14):
        print((a + b) * c)
    

    then:

    >>> foo(42)
    Hello
    42
    
    >>> bar(1, 2, c=3)
    Hello
    9