The power of * and ** in python
These two operators have a special place in python and can be used for many things other than simple multiplication and exponential operation.
Let's first understand some basic concepts of python -
Iterable Unpacking
Any iterable in python, a list or a tuple is said to have a pack of values. We can unpack the values inside an iterable into individual variables. Let's better understand this -
a, b, c, d = [1, '2', {1,2}, 2.3]
Here every variable is assigned the corresponding element from the list on RHS. 'c' gets the value {1,2}. We can also unpack an iterable which is stored in a variable similarly.
v = 'python' a, b, c, d, e, f = v
Note: in the case of unordered iterables like a set, the sequence of values unpacked into different variables is not guaranteed.
Extended Unpacking
* operator
Moving a step further, we always don't wish to unpack a single index value of the iterable in a single variable. What we want to do now resembles the slicing of a list.
v = 'python' a, *b = v
Here, the first index value of iterable 'v' gets assigned to 'a' i.e. p and the rest of the values of the iterable are assigned to 'b' as a list ['y', 't', 'h', 'o', 'n'].
Therefore, using * operator we can do extended unpacking of an iterable. Like in normal unpacking in the case of unordered iterables we cannot guarantee the sequence of elements in the list assigned.
** operator
When working with dictionaries * operator is only able to unpack the keys of the dictionary.
v = {1: 'a', 2: 'b', 3: 'c'} a, *b = v
Here, 'a' will be assigned 1 and b will be assigned [2,3].
To successfully unpack dictionaries we use the ** operator. Consider an example where we need to combine 3 dictionaries into a single dictionary.
utput: {1, 2, 3} {1: 'a', 2: 'b', 3: 'c'}
*args
You might have come across this many times inside the function parameters. It is used to exhaust positional arguments that are passed to any function. For example -
def func(a,b,*c): print(a,b,c) func(10, 20, 30 ,40, 50 ,60, 70)
Output: 10 20 (30, 40, 50, 60, 70)
So 'c' takes up all the additional arguments that were passed to the function. This is pretty useful to have. Remember we can pass multiple arguments to the print statement like print('hello', 'how', 'are', 'you') and it will print all these separated by the default separated i.e. space.
The official documentation of python declares print as -
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
Seeing this declaration also makes me highlight another important point -
def func(a,b,*c,d): print(a,b,c,d) func(10, 20, 30 ,40, 50 ,60, 70, 80)
What would be the output of the above code snippet?
It will throw an error stating missing required keyword-only argument 'd'. Any argument passed into the function preceded by parameter name and assignment operator is called keyword argument like 'd' in func(10, 20, 30, 40, 50, 60, 70, d=80).
We need to pass only keyword-only arguments once extended unpacking has been used.
**kwargs
Keyword arguments can be captured using the ** operator inside the function definition.
def func(a,b=1,*args,e,**kwargs): print(a,b,args,e,kwargs) func(10, 20, 30, 40, 50, e=60, f=70, g=80) Output: 10 20 (30, 40, 50) 60 {'f': 70, 'g': 80}
Breaking the pieces of the puzzle -
- a - positional argument, mandatory, can be named argument
- b - positional argument, not mandatory, can be named argument
- *args - catches all following positional arguments. Remember, no additional positional arguments without names allowed after this
- e - keyword argument, mandatory, should be named argument
- **kwargs - catches all following named arguments. No arguments follow this.
To sum up, we learned how * and ** can be used at many more places than just simple multiplication and exponential operation.