Python is an extremely versatile and fun language to learn. However, due to the vast amount of tools it provides, it can be difficult to navigate through what’s actually useful to know and what’s just nice to know.
In this tutorial, we will focus on three things that, in my opinion, are incredibly useful to know in Python. We will first discuss what unpacking operators are (* and *) and how to use them to both unpack and pack iterable objects. Then we will move on to args and **kwargs. And lastly, we will review, in detail, what iteration, iterables, and iterators are and why it’s incredibly beneficial to know what they are.
There will also be a short bonus tip at the end. So let’s get started!
1. Unpacking Operators (* and **)
Let’s say we have a list:
num_list = [1,2,3,4,5]
And we define a function that takes in 5 arguments and returns their sum:
def num_sum(num1,num2,num3,num4,num5):
return num1 + num2 + num3 + num4 + num5
And we want to find the sum of all the elements in num_list. Well, we can accomplish this by passing in all the elements of num_list to the function num_sum. Since num_list has five elements in it, the num_sum function contains five parameters, one for each element in num_list.
One way to do this would be to pass the elements by using their index as follows:
num_sum(num_list[0], num_list[1], num_list[2], num_list[3], num_list[4])
# 15
However, there is a much easier way to do this, and that’s by using the operator. The operator is an unpacking operator that will unpack the values from any iterable object, such as lists, tuples, strings, etc…
For example, if we want to unpack num_list and pass in the 5 elements as separate arguments for the num_sum function, we could do so as follows:
num_sum(*num_list)
# 15
And that’s it! The asterisk, *, or unpacking operator, unpacks num_list, and passes the values, or elements, of num_list as separate arguments to the num_sum function.
Note: For this to work, the number of elements in num_list must match the number of parameters in the num_sum function. If they don’t match, we would get a TypeError.
Using Built-In Functions
We can also use the asterisk, *, or unpacking operator, with built-in functions in python, such as print:
print(*num_list)
# 1 2 3 4 5
Unpacking Multiple Lists
Let’s say we have another list:
num_list_2 = [6,7,8,9,10]
And we want to print all the elements in both num_list and num_list_2. We can use the unpacking operator, *, to accomplish this as follows:
print(*num_list, *num_list_2)
# 1 2 3 4 5 6 7 8 9 10
Both num_list and num_list_2 are unpacked. Then, all the elements are passed in to print as separate arguments.
Merging Multiple Lists
We can also create a new list that contains all the elements from num_list and num_list_2:
new_list = [*num_list, *num_list_2]
# [1,2,3,4,5,6,7,8,9,10]
_num_list and num_list_2 are unpacked, resulting in their elements constituting the elements of the newly made list, new_list._
Note: We could have simply added num_list and num_list_2 to create new_list. However, this was just to portray the functionality of the unpacking operator.
Other Uses of * Operator
Let’s say that we have a string assigned to the variable name:
name = 'Michael'
And we want to break this name up into 3 parts, with the first letter being assigned to a variable, the last letter being assigned to another variable, and everything in the middle assigned to a third variable. We can do so as follows:
first, *middle, last = name
And that’s it! Since name is a string, and strings are iterable objects, we can unpack them. The values on the right side of the assignment operator will be assigned to the variables on the left depending on their relative position in the iterable object. As such, the first letter of ‘Michael’ is assigned to the variable first, which would be ‘M’ in this case. The last letter, ‘l’, is assigned to the variable last. And the variable middle will contain all the letters between ‘M’ and ‘l’ in the form of a list: [‘i’, ‘c’, ‘h’, ‘a’, ‘e’].
*Note: The first and last variables above are called mandatory variables, as they must be assigned concrete values. The middle variable, due to using the or unpacking operator, can have any number of values, including zero. If there are not enough values to unpack for the mandatory variables, we will get a ValueError.**
For example, if we used the following assignment instead:
first, *middle, last = 'ma'
Then the variable first will be assigned ‘m’, the variable last will be assigned ‘a’, and the variable middle will just be an empty list since there are no other values to assign to it.
Unpacking Dictionaries
What happens when we try to use the * operator with a dictionary?
num_dict = {'a': 1, 'b': 2, 'c': 3}
print(*num_dict)
# a b c
Notice how it printed the keys of the dictionary and not the values? To unpack a dictionary, we need to use the ** unpacking operator. However, since each value is associated with a specific key, the function that we pass these arguments to must have parameters with the same names as the keys of the dictionary being unpacked.
For example:
def dict_sum(a,b,c):
return a+b+c
This dict_sum function has three parameters: a, b, and c. These three parameters are named the same as the keys of num_dict. Therefore, once we pass in the unpacked dictionary using the ** operator, it’ll assign in the values of the keys according to the corresponding parameter names:
dict_sum(**num_dict)
# 6
Thus, the values, or arguments, for the a, b, and c parameters in dict_sum will be 1, 2, and 3, respectively. And the sum of these three values is 6.
Merging Dictionaries
Just like with lists, the ** operator can be used to merge two or more dictionaries:
num_dict = {'a': 1, 'b': 2, 'c': 3}
num_dict_2 = {'d': 4, 'e': 5, 'f': 6}
new_dict = {**num_dict, **num_dict_2}
# {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
2. args and kwargs
We can also use the * operator to pack multiple values into a single variable.
For example:
*names, = 'Michael', 'John', 'Nancy'
# names
['Michael', 'John', 'Nancy']
The reason for using a trailing comma after *names is because the left side of the assignment must be a tuple or list. Therefore, the names variable now contains all the names on the right side in the form of a list.
Note: This is what we do when we define functions that can receive a varying number of arguments! That is the concept of *args and **kwargs!
*args
For example, let’s say we have a function, names_tuple, that takes in names as arguments and returns them back. However, the number of names that we pass in to this function can vary. Well, we can’t just choose a number of parameters that this function would have since the number of positional arguments can change with each calling of the function. We can instead use the * operator to pack the arguments passed in into a tuple as follows:
def names_tuple(*args):
return args
names_tuple('Michael', 'John', 'Nancy')
# ('Michael', 'John', 'Nancy')
names_tuple('Jennifer', 'Nancy')
# ('Jennifer', 'Nancy')
No matter what number of positional arguments we pass in when we call the names_tuple function, the *args* argument will pack the positional arguments into a tuple, similar to the names** assignment above.
**kwargs
To pass in a varying number of keyword or named arguments, we use the ** operator when defining a function. The ** unpacking operator will pack the varying number of named arguments we pass in into a dictionary.
def names_dict(**kwargs):
return kwargs
names_dict(Jane = 'Doe')
# {'Jane': 'Doe'}
names_dict(Jane = 'Doe', John = 'Smith')
# {'Jane': 'Doe', 'John': 'Smith'}
Note: When using the * operator to create a parameter that receives a varying number of positional arguments when defining a function, it is common to use the parameter name args (and kwargs to receive a varying number of keyword or named arguments). However, any names can be chosen for these parameters.
For a more thorough overview of *args and **kwargs:
3. Iteration, Iterables and Iterators
Knowing what iterables and iterators are is extremely useful when Programming in Python. Let’s quickly review the differences between iteration, iterables, and iterators, how to identify iterables and iterators, and the benefits of being able to do so.
What is an Iterable?
Broadly speaking, an iterable is something that can be looped over. The process of looping over something, or taking each item of it, one after another, is iteration.
When we use a for loop to loop over a list, the process of looping over this list is iteration (or we are iterating over this list), and the list is the iterable. Tuples, dictionaries, strings, files, and generators are also iterables, as they can also be iterated over.
For example, we can use a for loop to loop over or iterate over a list as follows:
num_list = [1,2,3,4,5]
for num in num_list:
print(num)
What makes something an iterable?
To be an iterable, an object will have an iter() method. Thus, if an object has an iter() method, then it is an iterable, and thus we can use a for loop to loop over it.
Technically, if an object has a getitem() method it can also be an iterable. However, we will focus on the iter() method here as that is the newer protocol for iteration in python.
We can use the dir() function on _num_list_ to prove that it has the iter() method:

_Notice how the list object, num_list, has an_ iter() method, which makes it an iterable.
To know if an object is an iterable, we check if it has an iter() method
Methods that have double leading and trailing underscores are hence called dunder methods (also known as magic methods or special methods). These methods are intended to be invoked indirectly, as most python operators have an associated magic method.
What does the iter() method do?
An iter() method will return an iterator object. To loop over an iterable, such as a list, we don’t usually do that manually. For example, a for loop first calls the iter() method on our object which returns an iterator object. The for loop then loops over this iterator by calling the next() method. All of this is done in the background.
So this brings us to iterators.
What is an Iterator?
A list is an iterable. But it is not an iterator. If we run the iter() method on our list, it will return an iterator. An iterator is an object with a state that remembers where it is during iteration. Iterators also know how to get their next value. They get their next value with the next() method.
To know if an object is an iterator, we check if it has a next() method
We can first call the iter() function on num_list (the iter() function calls the iter() method on the object passed in), which returns an iterator, and then we will use the dir() function on the iterator to view its attributes and methods:

Our iterator, _num_list_iterator_, has a next() method. A list object is not an iterator because it does not have a next() method. Thus calling the next() function (which would call the next() method on an object if it has one) on a list would result in a TypeError, since our list object is not an iterator and thus does not have a next() method. However, a list object is an iterable because it has an iter() method, which when called, returns an iterator that will have a next() method.
_Note how num_listiterator also has an iter() method. As a result, iterators are also iterables, or self-iterables, since calling the iter() method on an iterator returns self.
What does the next() method do?
Calling the _next() method on an iterator will return the next value in the iteration. Thus, calling the next_() method on the iterator we created from num_list, or num_list_iterator, will return the first value in our iterator. Calling the _next_() method on that same iterator will then return the second value in our iterator (since iterators have a state which remembers where it is during iteration).
Once we’ve went through all the values of our iterator, calling the next() method again on our iterator will result in a StopIteration error, since our iterator has now been exhausted. Once an iterator is exhausted, we must make a new iterator via the iter() method of the iterable.
Note: Iterators can only go forward. Meaning we can only get the next value in an iterator via the next() method. To start from scratch, we would have to create a new iterator object by calling the iter() method on the iterable we would like to iterate over.
Why is this useful to know?
Knowing whether an object is an iterable (or can be iterated over) can be very helpful while coding in python. For example, knowing if an object is an iterable would tell us if an object can be used in a for loop. Or if the documentation for a Python function states that it takes in an iterable object as an argument, now we know what they mean!
In the above example, we used a for loop to loop over num_list, which was a list of integers starting at 1 and ending at 5, or [1,2,3,4,5]. Well, instead of creating num_list as we did above and looping through it, we could check to see if we can use the range() function to create a range object that we can loop over using a for loop, giving us the same sequence of numbers to iterate over in less code:

The range object is an iterable since it has the _iter() method! Therefore, it can be iterated over using a for loop, since the for loop **will first create the iterator object via the iter_() method, and then iterate over that iterator object by calling the next__() method until the iterator returns a StopIteration error, which means the iterator has been exhausted (we don’t see the error)**.
for num in range(1,6):
print(num)
# 1
2
3
4
5
Bonus – Ternary Operators
Let’s start with the following if/else scenario:
num1, num2 = 5, 10
min = None
if num1 > num2:
min = num2
else:
min = num1
print(min)
# 5
We first set num1 equal to 5, and num2 equal to 10 (note how we assigned multiple values to multiple variables on line one). We then have an if/else statement. If num1 is greater than num2, then the min variable is assigned to num2. Otherwise, min is assigned to num1. Obviously we’re not accounting for num1 and num2 being equal.
We can shorten the code significantly by using ternary operators as follows:
x if C else y
num1, num2 = 5, 10
min = num2 if num1 > num2 else num1
And that’s it! C is our condition (num1 > num2), which is evaluated first. If it evaluates to True, then x is evaluated and its value will be returned (and assigned to the variable min). Otherwise, y is evaluated and its value is returned (and assigned to the variable min).
If you enjoy reading stories like these and want to support me as a writer, consider signing up to become a Medium member. It’s $5 a month, giving you unlimited access to stories on Medium. If you sign up using my link, I’ll earn a small commission.
Conclusion
In this tutorial, we gained some extremely valuable tools and concepts for programming in Python. We first learned how to use * and * operators to unpack iterable objects and saw many examples of them being used. We also saw how to pack iterable objects using those same operators, and how that relates to the concept of args and **kwargs. We then reviewed the differences between iteration, iterables, and iterators and how that relates to the iter() and next() methods. We then saw the benefits of knowing those distinctions and how it can translate to shorter, more concise Python code. Lastly, we learned how ternary operators are used and how they can substantially shorten our code while still being quite intuitive and easy to read.