args and kwargs in Python : The Mystery of Asterisks in Python

by | Nov 17, 2022 | Uncategorized | 0 comments

Questions addressed in this post:

  • How can I define functions in Python and what are parameters?
  • What is Advanced Parameter Handling For Functions?
  • How can I use args(*) and kwargs(**) in Python?

What you will learn:

  • Define a function that takes parameters.
  • Args(* Arguments) and kwargs(** Keyword Arguments) and how to use them.
  • Set default values for function parameters.
  • Explain how we can use args(*) and kwargs(*) for Advanced Parameter Handling For Functions and Decorators

Ok so let’s dive in the code. Also read the code comments for better understanding:

args and kwargs in python – Mystery of Asterisks in Python

A) When Asterisk used in multiplication and power operations

2 * 3
6
2 ** 3

8

# # Initialize the zero-valued list with 100 length
zeros_list = [0] * 10
print(zeros_list)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

B) args and kwargs in python
Functions and their arguments in Python
1) Anatomy of a Function
def fahr_to_celsius(temp):
    return ((temp - 32) * (5/9))
conv_temp = fahr_to_celsius(273)
print(conv_temp)
133.88888888888889
print('freezing point of water:', fahr_to_celsius(32), 'C')
print('boiling point of water:', fahr_to_celsius(212), 'C')
freezing point of water: 0.0 C
boiling point of water: 100.0 C

2) Parameter Handling

def adder(a,b):
    sum_amt = a + b
    return sum_amt
amt = adder(2,4)
print(amt)
6
amt = adder(2,4,7)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-ca38331a5c84> in <module>()
----> 1 amt = adder(2,4,7)

TypeError: adder() takes 2 positional arguments but 3 were given
ERROR :
TypeError: adder() takes 2 positional arguments but 3 were given
def adder_revised(a,b,c):
    sum_amt = a + b + c
    return sum_amt
amt = adder_revised(2,4,7)
print(amt)
13
# The line below will generate error and is for the demonstration purpose
amt = adder_revised(2,4)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-98-5bc59d53d483> in <module>()
      1 # The line below will generate error and is for the demonstration purpose
----> 2 amt = adder_revised(2,4)

TypeError: adder_revised() missing 1 required positional argument: 'c'

ERROR:

TypeError: adder_revised() missing 1 required positional argument: ‘c’

3) Concept of Advanced Parameters and Functions

import datetime
now = datetime.datetime.now()
print('Datetime Now is : ', now)
Datetime Now is :  2019-03-30 11:03:08.381734
Functions with Keyword Arguments
# Arguments with Keyword
day1 = datetime.timedelta(days=1)
delta_day = now - day1
print('Datetime Now is : ', now)
print('Delta Day is    : ', delta_day)
Datetime Now is :  2019-03-30 11:03:08.381734
Delta Day is    :  2019-03-29 11:03:08.381734

Same function different keyword argument
now = datetime.datetime.now()
print('Datetime Now is : ', now)
day1 = datetime.timedelta(hours=4)
delta_day = now - day1
print('Delta Day is    : ', delta_day)
Datetime Now is :  2019-03-30 11:06:50.388904
Delta Day is    :  2019-03-30 07:06:50.388904

Same function with Multiple Keyword Argument

now = datetime.datetime.now()
print('Datetime Now is : ', now)
day1 = datetime.timedelta(days=1,hours=4)
delta_day = now - day1
print('Delta Day is    : ', delta_day)
Datetime Now is :  2019-03-30 11:08:03.531606
Delta Day is    :  2019-03-29 07:08:03.531606

Optional Arguments

Lets revamp our adder function with keyword Argument
def new_adder(a,b,c=0):
    sum_amt = a + b + c
    return sum_amt
amt = new_adder(2,4)
print(amt)
6
amt = new_adder(2,4,7)
print(amt)
13
amt = new_adder(2,4,7,10)
print(amt)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-665b09d0593c> in <module>()
----> 1 amt = new_adder(2,4,7,10)
      2 print(amt)

TypeError: new_adder() takes from 2 to 3 positional arguments but 4 were given
TypeError: new_adder() takes from 2 to 3 positional arguments but 4 were given

4) Unlimited Number of Positional Argument Values

def smart_adder(*args):
    amt = 0
    for n in args:
        amt += n
    print("Sum:",amt)
    return amt
smart_adder(3,5)
smart_adder(4,5,6,7)
smart_adder(1,2,3,5,6)
smart_adder(2,3,5,6,8,9,7)
Sum: 8
Sum: 22
Sum: 17
Sum: 40
def smart_adder(*num):
    amt = 0
    for n in num:
        amt += n
    print("Sum:",amt)
    return amt
smart_adder(3,5)
smart_adder(4,5,6,7)
smart_adder(1,2,3,5,6)
smart_adder(2,3,5,6,8,9,7)
Sum: 8
Sum: 22
Sum: 17
Sum: 40
40

In Python, we can pass a variable number of arguments to a function using special symbols. There are two special symbols: *args (Non Keyword Arguments) **kwargs (Keyword Arguments) We use *args and **kwargs as an argument when we are unsure about the number of arguments to pass in the functions.

You would use *args when you're not sure how many arguments might be passed to your function, i.e. it allows you pass an arbitrary number of arguments to your function.

Keyword Arguments with variable number of keys
def intro(**data):
    print("\nData type of argument:",type(data))

    for key, value in data.items():
        print("{} is {}".format(key,value))

intro(Firstname="Ali", Lastname="Mohammed", Phone=1234567890)
intro(Firstname="Ali", Lastname="Raza", Email="alirazabhayani@gmail.com", Country="Pakistan", Phone=9876543210)
Data type of argument: <class 'dict'>
Firstname is Ali
Lastname is Mohammed
Phone is 1234567890

Data type of argument: <class 'dict'>
Firstname is Ali
Lastname is Raza
Email is alirazabhayani@gmail.com
Country is Pakistan
Phone is 9876543210

Ordering Arguments When ordering arguments within a function or function call, arguments need to occur in a particular order: Formal positional arguments *args Keyword arguments **kwargs

def func(required_arg, *args, **kwargs):
    # required_arg is a positional-only parameter.
    print(required_arg)

    # args is a tuple of positional arguments,
    # because the parameter name has * prepended.
    if args: # If args is not empty.
        print(args)

    # kwargs is a dictionary of keyword arguments,
    # because the parameter name has ** prepended.
    if kwargs: # If kwargs is not empty.
        print(kwargs)
# Expected to generate an error as no arguments passed
func()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-67-ae433acc38da> in <module>()
      1 # Expected to generate an error as no arguments passed
----> 2 func()

TypeError: func() missing 1 required positional argument: 'required_arg'
func("This is a required argument")
This is a required argument
func("This is a required argument", 1, 2, '3')
This is a required argument
(1, 2, '3')
func("This is a required argument", 1, 2, '3', keyword1=4, keyword2="foo")
This is a required argument
(1, 2, '3')
{'keyword1': 4, 'keyword2': 'foo'}

Whats the point of this all?

Why should we even bother ourselves with the Advanced Function Parameter?

One place where the use of *args and **kwargs is quite useful is for subclassing

class Foo(object):
    def __init__(self, value1, value2):
        # do something with the values
        print(value1, value2)

class MyFoo(Foo):
    def __init__(self, *args, **kwargs):
        # do something else, don't care about the args
        print('myfoo')
        super(MyFoo, self).__init__(*args, **kwargs)

This way we can extend the behaviour of the Foo class, without having to know too much about Foo. This can be quite convenient if you are programming to an API which might change. MyFoo just passes all arguments to the Foo class

DECORATORS:

Decorators are very powerful and useful tool in Python since it allows programmers to modify the behavior of function or class. Decorators allow us to wrap another function in order to extend the behavior of wrapped function, without permanently modifying it.

In Decorators, functions are taken as the argument into another function and then called inside the wrapper function.

def pass_thru(func_to_decorate):
    def new_func(*original_args, **original_kwargs):
        print("Function has been decorated.  Congratulations.")
        # Do whatever else you want here
        new_args = []
        for name in original_args:
            new_args.append(name.title())
            print('Coverting %s to %s ' %(name,name.title()))
        return func_to_decorate(*new_args, **original_kwargs)
    return new_func
@pass_thru
def print_args(*args):
    print('Entered Function now')
    for arg in args:
        print(arg)
print_args('ALI', 'RaZa', 'BhaYanI')
Function has been decorated.  Congratulations.
Coverting ALI to Ali 
Coverting RaZa to Raza 
Coverting BhaYanI to Bhayani 
Entered Function now
Ali
Raza
Bhayani
@pass_thru
def print_2args(a,b):
    print('Entered Function now')
    print('Final Inputs I got : ', a,b)
print_2args('ALI', 'RaZa')
Function has been decorated.  Congratulations.
Coverting ALI to Ali 
Coverting RaZa to Raza 
Entered Function now
Final Inputs I got :  Ali Raza
@pass_thru
def print_3args(a,b,c):
    print('Entred Function now')
    print('Final Inputs I got : ',a,b,c)
print_3args('ALI', 'RaZa', 'BhaYanI')
Function has been decorated.  Congratulations.
Coverting ALI to Ali 
Coverting RaZa to Raza 
Coverting BhaYanI to Bhayani 
Entred Function now
Final Inputs I got :  Ali Raza Bhayani