DEV Community

Cover image for Tuple and Sequence operations
Sundeep
Sundeep

Posted on • Originally published at learnbyexample.github.io

Tuple and Sequence operations

This chapter will discuss the tuple data type and some of the common sequence operations. Data types like str, range, list and tuple fall under Sequence types. Binary Sequence Types aren't discussed in this book. Some of the operations behave differently or do not apply for certain types, see docs.python: Common Sequence Operations for details.

See also docs.python: collections for specialized container data types.

Sequences and iterables

Quoting from docs.python glossary: sequence:

An iterable which supports efficient element access using integer indices via the __getitem__() special method and defines a __len__() method that returns the length of the sequence. Some built-in sequence types are list, str, tuple, and bytes. Note that dict also supports __getitem__() and __len__(), but is considered a mapping rather than a sequence because the lookups use arbitrary immutable keys rather than integers.

Quoting from docs.python glossary: iterable:

An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects...

Initialization

Tuples are declared as a collection of zero or more objects, separated by a comma within () parentheses characters. Each element can be specified as a value by itself or as an expression. The outer parentheses are optional if comma separation is present. Here's some examples:

# can also use: empty_tuple = tuple()
>>> empty_tuple = ()
>>> type(empty_tuple)
<class 'tuple'>

# note the trailing comma, otherwise it will result in a 'str' data type
# same as 'apple', since parentheses are optional here
>>> one_element = ('apple',)

# multiple elements example
>>> dishes = ('Aloo tikki', 'Baati', 'Khichdi', 'Makki roti', 'Poha')

# mixed data type example, uses expressions as well
>>> mixed = (1+2, 'two', (-3, -4), empty_tuple)
>>> mixed
(3, 'two', (-3, -4), ())
Enter fullscreen mode Exit fullscreen mode

You can use the tuple() built-in function to convert from an iterable data type to tuple. Here's some examples:

>>> chars = tuple('hello')
>>> chars
('h', 'e', 'l', 'l', 'o')

>>> tuple(range(3, 10, 3))
(3, 6, 9)
Enter fullscreen mode Exit fullscreen mode

Tuples are immutable, but individual elements can be either mutable or immutable. As an exercise, given chars = tuple('hello'), see what's the output of the expression chars[0] and the statement chars[0] = 'H'.

Slicing

One or more elements can be retrieved from a sequence using the slicing notation (this wouldn't work for an iterable like dict or set). It works similarly to the start/stop/step logic seen with the range() function. The default step is 1. Default value for start and stop depends on whether the step is positive or negative.

>>> primes = (2, 3, 5, 7, 11)

# index starts with 0
>>> primes[0]
2

# start=2 and stop=4, default step=1
# note that the element at index 4 (stop value) isn't part of the output
>>> primes[2:4]
(5, 7)
# default start=0
>>> primes[:3]
(2, 3, 5)
# default stop=len(seq)
>>> primes[3:]
(7, 11)

# copy of the sequence, same as primes[::1]
>>> primes[:]
(2, 3, 5, 7, 11)
Enter fullscreen mode Exit fullscreen mode

You can use negative index to get elements from the end of the sequence. seq[-n] is equivalent to seq[len(seq) - n].

>>> primes = (2, 3, 5, 7, 11)

# len(primes) - 1 = 4, so this is same as primes[4]
>>> primes[-1]
11

# seq[-n:] will give the last n elements
>>> primes[-1:]
(11,)
>>> primes[-2:]
(7, 11)
Enter fullscreen mode Exit fullscreen mode

Here's some examples with different step values.

>>> primes = (2, 3, 5, 7, 11)

# same as primes[0:5:2]
>>> primes[::2]
(2, 5, 11)

# retrieve elements in reverse direction
# note that the element at index 1 (stop value) isn't part of the output
>>> primes[3:1:-1]
(7, 5)
# reversed sequence
# would help you with the palindrome exercise from Control structures chapter
>>> primes[::-1]
(11, 7, 5, 3, 2)
Enter fullscreen mode Exit fullscreen mode

As an exercise, given primes = (2, 3, 5, 7, 11),

  • what happens if you use primes[5] or primes[-6]?
  • what happens if you use primes[:5] or primes[-6:]?
  • is it possible to get the same output as primes[::-1] by using an explicit number for stop value? If not, why not?

Sequence unpacking

You can map the individual elements of an iterable to multiple variables. This is known as sequence unpacking and it is handy in many situations.

>>> details = ('2018-10-25', 'car', 2346)
>>> purchase_date, vehicle, qty = details
>>> purchase_date
'2018-10-25'
>>> vehicle
'car'
>>> qty
2346
Enter fullscreen mode Exit fullscreen mode

Here's how you can easily swap variable values.

>>> num1 = 3.14
>>> num2 = 42
>>> num3 = -100

# RHS is a single tuple data type (recall that parentheses are optional)
>>> num1, num2, num3 = num3, num1, num2
>>> print(f'{num1 = }; {num2 = }; {num3 = }')
num1 = -100; num2 = 3.14; num3 = 42
Enter fullscreen mode Exit fullscreen mode

Unpacking isn't limited to single value assignments. You can use a * prefix to assign all the remaining values, if any is left, to a list variable.

>>> values = ('first', 6.2, -3, 500, 'last')

>>> x, *y = values
>>> x
'first'
>>> y
[6.2, -3, 500, 'last']

>>> a, *b, c = values
>>> a
'first'
>>> b
[6.2, -3, 500]
>>> c
'last'
Enter fullscreen mode Exit fullscreen mode

As an exercise, what do you think will happen for these cases, given nums = (1, 2):

  • a, b, c = nums
  • a, *b, c = nums
  • *a, *b = nums

Returning multiple values

Tuples are also the preferred way to return multiple values from a function. Here's some examples:

>>> def min_max(iter):
...     return min(iter), max(iter)
... 
>>> min_max('visualization')
('a', 'z')
>>> small, big = min_max((-3, 10, -42, 53.2))
>>> small
-42
>>> big
53.2
Enter fullscreen mode Exit fullscreen mode

The min_max(iter) user-defined function above returns both the minimum and maximum values of a given iterable input. min() and max() are built-in functions. You can either save the output as a tuple or unpack into multiple variables. You'll see built-in functions that return tuple as output later in this chapter.

The use of both min() and max() in the above example is for illustration purpose only. As an exercise, write a custom logic that iterates only once over the input sequence and calculates both minimum/maximum simultaneously.

Iteration

You have already seen examples with for loop that iterates over a sequence data type. Here's a refresher:

>>> nums = (3, 6, 9)
>>> for n in nums:
...     print(f'square of {n} is {n ** 2}')
... 
square of 3 is 9
square of 6 is 36
square of 9 is 81
Enter fullscreen mode Exit fullscreen mode

In the above example, you get one element per each iteration. If you need the index of the elements as well, you can use the enumerate() built-in function. You'll get a tuple value per each iteration, containing index (starting with 0 by default) and the value at that index. Here's some examples:

>>> nums = (42, 3.14, -2)

>>> for t in enumerate(nums):
...     print(t)
... 
(0, 42)
(1, 3.14)
(2, -2)

>>> for idx, val in enumerate(nums):
...     print(f'{idx}: {val:>5}')
... 
0:    42
1:  3.14
2:    -2
Enter fullscreen mode Exit fullscreen mode

The enumerate() built-in function has a start=0 default valued argument. As an exercise, change the above snippet to start the index from 1 instead of 0.

Arbitrary number of arguments

As seen before, the print() function can accept zero or more values separated by a comma. Here's a portion of the documentation as a refresher:

print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Enter fullscreen mode Exit fullscreen mode

You can write your own functions to accept arbitrary number of arguments as well. The syntax is similar to the sequence unpacking examples seen earlier in the chapter. A * prefix to an argument name will allow it to accept zero or more values. Such an argument will be packed as a tuple data type and it should always be specified after positional arguments (if any). Idiomatically, args is used as the tuple variable name. Here's an example:

>>> def many(a, *args):
...     print(f'{a = }; {args = }')
... 
>>> many()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: many() missing 1 required positional argument: 'a'
>>> many(1)
a = 1; args = ()
>>> many(1, 'two', 3)
a = 1; args = ('two', 3)
Enter fullscreen mode Exit fullscreen mode

Here's a more practical example:

>>> def sum_nums(*args):
...     total = 0
...     for n in args:
...         total += n
...     return total
... 
>>> sum_nums()
0
>>> sum_nums(3, -8)
-5
>>> sum_nums(1, 2, 3, 4, 5)
15
Enter fullscreen mode Exit fullscreen mode

As an exercise,

  • add a default valued argument initial which should be used to initialize total instead of 0 in the above sum_nums() function. For example, sum_nums(3, -8) should give -5 and sum_nums(1, 2, 3, 4, 5, initial=5) should give 20.
  • what would happen if you call the above function like sum_nums(initial=5, 2)?
  • what would happen if you have nums = (1, 2) and call the above function like sum_nums(*nums, total=3)?
  • in what ways does this function differ from the sum() built-in function?

See also docs.python: Arbitrary Argument Lists.

Section Arbitrary keyword arguments will discuss how to define functions that accept arbitrary number of keyword arguments.

zip

Use zip() to iterate over two or more iterables simultaneously. Every iteration, you'll get a tuple with an item from each of the iterables. Iteration will stop when any of the input iterables is exhausted, use itertools.zip_longest() if you want to go on until the longest iterable is exhausted.

Here's an example:

>>> odd = (1, 3, 5)
>>> even = (2, 4, 6)
>>> for i, j in zip(odd, even):
...     print(i + j)
... 
3
7
11
Enter fullscreen mode Exit fullscreen mode

As an exercise, write a function that returns the sum of product of corresponding elements of two sequences (for example, the result should be 44 for (1, 3, 5) and (2, 4, 6)).

Tuple methods

While this book won't discuss Object-Oriented Programming (OOP) in any detail, you'll still see plenty examples for using them. You've already seen a few examples with modules. See Practical Python Programming and Fluent Python if you want to learn about Python OOP in depth. See also docs.python: Data model.

Data types in Python are all internally implemented as classes. You can use the dir() built-in function to get a list of valid attributes for an object.

# you can also use tuple objects such as 'odd' and 'even' declared earlier
>>> dir(tuple)
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__',
 '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
 '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__',
 '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__',
 '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']

>>> even = (2, 4, 6)
# same as: len(even)
>>> even.__len__()
3
Enter fullscreen mode Exit fullscreen mode

The non-dunder names (last two items) in the above listing will be discussed in this section. But first, a refresher on the in membership operator is shown below.

>>> num = 5
>>> num in (10, 21, 33)
False

>>> num = 21
>>> num in (10, 21, 33)
True
Enter fullscreen mode Exit fullscreen mode

The count() method returns the number of times a value is present in the tuple object.

>>> nums = (1, 4, 6, 22, 3, 5, 2, 1, 51, 3, 1)
>>> nums.count(3)
2
>>> nums.count(31)
0
Enter fullscreen mode Exit fullscreen mode

The index() method will give the index of the first occurrence of a value. It will raise ValueError if the value isn't present, which you can avoid by using the in operator first. Or, you can use the try-except statement to handle the exception as needed.

>>> nums = (1, 4, 6, 22, 3, 5, 2, 1, 51, 3, 1)

>>> nums.index(3)
4

>>> n = 31
>>> nums.index(n)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: tuple.index(x): x not in tuple
>>> if n in nums:
...     print(nums.index(n))
... else:
...     print(f'{n} not present in "nums" tuple')
... 
31 not present in "nums" tuple
Enter fullscreen mode Exit fullscreen mode

The list and str sequence types have many more methods and they will be discussed separately in later chapters.

Latest comments (0)