Tags

, ,

This is the second post in a series for those who have never used Python but have used a programming language before. These posts are an introduction to this delightful and popular programming language.

The first post introduced Python’s most basic data types: int, float, str, bool, and None. In this post we’ll meet the list-like data types.

Almost all programming languages support numbers and strings of text (aka strings). Nearly all have separate numeric data types for integer and floating-point numbers (Python has the int type and the float type).

Note a key difference between the numeric types and strings. Numbers have a single value, but strings are strings of characters (hence the name). Strings have as many values as they have characters (from zero to however many) — they have a length. Also, the characters in a string have an order — “barfoo” is not the same string as “foobar” even though they have the same six characters.

Generically, strings are lists, and Python treats them as such — the str type is a list data type.

We’ll come back to that later. What’s important here is that Python features a variety of list-like data types, each with different capabilities. They are the topic of this post.


Firstly, there is the actual list type:

001| ### Lists (part 1)…
002| 
003| items = list()
004| items = []
005| 
006| items = [1, 2, 3, 4, 5, 6]
007| items = list([1, 2, 3, 4, 5, 6])
008| 
009| items:list = [“a”, 2, [], 3.1415]
010| 

Line #3 uses the list constructor to create an empty list and assign it to the variable named items. Line #4 also creates an empty list but uses a list literal — Python uses square brackets to indicate lists. In both cases, items gets an empty list with length zero.

Lines #6 and #7 both create a list with six items, the digits one through six. Line #7 uses the list constructor, which takes a list-like object and creates a new list from it. The example here is redundant but there are other use cases where the list constructor turns something list-like to an actual list.

Note that in Python, many things can be list-like without being actual list objects. All five of the Python types discussed here are list-like.

Line #9 shows a list type hint. It also illustrates how lists can contain any type of item. That said, most use cases involve lists where the items are all the same type.

There is a great deal more to the list type and list-like objects. They are a central Python data type, and we’ll revisit them more than once. Let’s move on to some other list-like types.


The tuple type is very similar to the list type:

001| ### Tuples (part 1)…
002| 
003| tup = tuple()
004| tup = ()
005| 
006| tup = (42,)
007| tup = (“Hello”,)
008| 
009| tup = (‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’)
010| tup = ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’
011| 
012| items = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’]
013| tup = tuple(items)
014| 
015| tup:tuple = (‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’)
016| 
017| x,y,z = 2.1, 4.2, 6.3
018| 
019| args = 2.1, 4.2, 6.3
020| x,y,z = args
021| 
022| tmp = (1,2,3, *args, x,y,z)
023| 

Line #3 uses the tuple constructor to create an empty (zero-length) tuple. Line #4 does the same thing using a tuple literal. Python uses parenthesis to indicate a tuple, but it’s a bit more complicated. An empty set of parentheses (as in line#4) does indicate an empty tuple literal.

But consider cases like (42), ("Hello"), or ((2*x)+y). These are just parenthesized values; they are not tuples. Lines #6 and #7 show how to create a single-value tuple: there must be a comma after the value. Python uses commas to separate values, but commas indicate a tuple in expression contexts. Note that Python allows trailing (or “hanging”) commas in lists:

001| # Trailing commas are ignored…
002| 
003| lst = [21, 42, 63, 84, ]
004| 
005| tup = (9, 8, 8, 6, 5, )
006| 

In most cases, it just ignores them, but they are necessary for single-value tuples.

Lines #9 and #10 are identical because Python sees comma-separated lists as tuples — the parentheses aren’t required. Lines #12 and #13 work together to create the same six-item list. Line #15 also creates the same list but includes a type hint.

Note that both the list and tuple constructors take only one argument, and that argument must be a list-like object. This generates an error:

001| # Illegal! These both cause an Exception…
002| 
003| lst = list(1, 2, 3, 4, 5)
004| 
005| tup = tuple(1, 2, 3, 4, 5)
006| 

Python has some syntactical sugar involving tuples. In line #17, the three comma-separated values on the right side of the equals-sign comprise a tuple (because of the commas). The left side of the equals-size is also a tuple — three comma-separated variable names. Because there are three items on either side, Python unpacks the right-side tuple into the left-side tuple assigning the three values to the three variables in order. This is a handy way to initialize closely related variables.

Lines #19 and #20 do the same thing, but in two steps using the variable args to store the three values. What’s important to recognize here is that we can pack comma-separated values into a tuple and also unpack them into comma-separated variables. This can mislead new Python coders into thinking the list and tuple constructors take a comma-separated list, but as mentioned above, they do not. Arguments passed to functions are also comma-separated, but Python treats them as multiple arguments, not items in a tuple.

Lastly, line #22 shows a handy unpacking trick. The leading asterisk on the name args tells Python to unpack it and insert the values at that point. The variable tmp ends up with a tuple: (1, 2, 3, 2.1, 4.2, 6.3, 2.1, 4.2, 6.3).


Given a list or tuple, specific items are addressed by their index using square brackets (in a manner similar to many other languages):

001| ### Lists & Tuples (part 2)…
002| 
003| lst = [21, 42, “63”, ’84’, 3.14159, [1,2,3], “99”]
004| 
005| print(‘first:’ , lst[0])
006| print(‘last:’  , lst[1])
007| print(‘head:’  , lst[:3])
008| print(‘tail:’  , lst[3:])
009| print(‘evens:’ , lst[0::2])
010| print(‘revers:’, lst[::1])
011| print()
012| 
013| tup = (21, 42, “63”, ’84’, 3.14159, [1,2,3], “99”)
014| 
015| print(‘first:’ , tup[0])
016| print(‘last:’  , tup[1])
017| print(‘head:’  , tup[:3])
018| print(‘tail:’  , tup[3:])
019| print(‘evens:’ , tup[0::2])
020| print(‘revers:’, tup[::1])
021| print()
022| 

Note that Python uses zero-based indexing, so indexes run from zero to length-1 (which drives some coders crazy but has its advantages). In Python, a numeric index is data type called a slice.

The syntax is:

object [ start : stop : step ]

Where object is any list-like object. At least one of start, stop, or step is required, or at least one colon. Colons separate the fields and are necessary if a later field is provided but an earlier one isn’t. A single value with no colons is assumed to be the start value and to index a single item.

Note that the item indexed by the stop value is not included (so object[2:2] returns an empty list). A handy feature of start:stop indexes is that the length of the slice is just stop-start.

So, the expression object[0:3] returns a three-item list comprised of the first three items (indexes 0, 1, and 2 but not index 3). Because the default value for start is zero, object[:3] returns the same thing. The default stop value is the list length, so object[:] is the same as just object — the entire list. Note that this default for stop only applies if there is at least one colon.

Remember: A single value with no colons returns just the one item and does not return a list. So, object[5] returns the sixth object in the list (because zero-indexing) but object[5:6] returns a one-item list containing the sixth object.

If start or stop are negative, they index from the end of the list (lines #6 and #16 as well as lines #8 and #18).

The step value determines how big the indexing steps are. The default is one, so unless overridden, the slice includes every item. Larger step sizes allow skipping, as in lines #9 and #19 above, which take every other item. If step is negative, the order is reversed. Lines #10 and #20 both return the entire list in reverse order.

When the code above is run, it prints:

first: 21
last: 99
head: [21, 42, '63']
tail: [3.14159, [1, 2, 3], '99']
evens: [21, '63', 3.14159, '99']
revers: ['99', [1, 2, 3], 3.14159, '84', '63', 42, 21]

first: 21
last: 99
head: (21, 42, '63')
tail: (3.14159, [1, 2, 3], '99')
evens: (21, '63', 3.14159, '99')
revers: ('99', [1, 2, 3], 3.14159, '84', '63', 42, 21)

Note that indexing is identical between lists and tuples. The only difference is that indexing a list returns a list and indexing a tuple returns a tuple.

Because strings are list-like, we can index their characters the same way:

001| ### String indexing…
002| 
003| text = ‘Hello, World! How are you?’
004| 
005| print(‘first:’ , text[0])
006| print(‘last:’  , text[1])
007| print(‘head:’  , text[:3])
008| print(‘tail:’  , text[3:])
009| print(‘evens:’ , text[0::2])
010| print(‘revers:’, text[::1])
011| print()
012| 

Which when run prints:

first: H
last: ?
head: Hel
tail: ou?
evens: Hlo ol!Hwaeyu
revers: ?uoy era woH !dlroW ,olleH

There is, however, a crucial distinction between lists and tuples. Like strings, tuples are immutable — they cannot be modified once created. There is no such restriction on lists; we can add and remove items in a variety of ways:

001| ### Lists (part 3)…
002| 
003| lst = []
004| lst.append(1)
005| lst.append(2)
006| lst.append(3)
007| print(lst)
008| 
009| lst.extend([4,5,6])
010| print(lst)
011| 
012| lst.insert(0, 0)
013| lst.insert(1, 99)
014| print(lst)
015| 
016| last = lst.pop()
017| first = lst.pop(0)
018| print(last)
019| print(first)
020| print(lst)
021| 

Line #3 creates an empty list and assigns it to variable lst. Lines #4 through #6 use the append method to add items. Line #6 also adds to the end of the list but expects a list-like object and appends each item separately. If we append a list to a list, the new list item is the whole appended list. Using the extend method allows adding items in a list individually.

The insert method uses an index to place a new item anywhere within the list (except at the very end; use append for that).

The pop method is the opposite of insert; it removes an item. By default, it removes the last item. (Which makes it easy to implement a quick and dirty LIFO stack with a list. Use the append method to “push” values and the pop method to retrieve them.)

There’s a lot more to lists, but here’s a look ahead in an example showing some features of lists:

001| ### Lists (part 4)…
002| 
003| nums = (21,42, “63”, ’84’, 2.71828, 3.14159, 86, ’99’)
004| 
005| print(‘Nums:’)
006| for num in nums:
007|     print(repr(num))
008| print()
009| 
010| nbrs = []
011| for num in nums:
012|     nbrs.append(int(num))
013| 
014| print(‘Nbrs:’)
015| for nbr in nbrs:
016|     print(repr(nbr))
017| print()
018| 
019| nbrs = [int(num) for num in nums]
020| 

Line #3 creates a tuple with eight items. The items all look like numbers, but some are string literals while others are numeric. Of the numeric literals, some are integers and some are floating-point.

Lines #6 & #7 (and lines #11 & #12 and lines #15 & #16) demonstrate Python for loops. We’ll examine Python loops in more detail later. For now, the basic syntax is:

for name in list:
    ...
    # loop statements
    ...

The for and in are keywords, the name is for the loop variable — the one that changes each time through the loop — and list is any list-like object. In the code above, the list-like objects are nums (a tuple object) and nbrs (an actual list object).

The for loop in lines #6 & #7 prints each item in nums. Lines #15 & #16 print each item in nbrs. In the print function, I used the built-in repr function to display the “under the hood” or “debug” version of the value. For strings, that means printing the quotes.

When run, this prints:

Nums:
21
42
'63'
'84'
2.71828
3.14159
86
'99'

Nbrs:
21
42
63
84
2
3
86
99

Note the quotes in the values from the first loop (Python uses single-quote characters when displaying strings). The example above supposes a use case where we’re receiving a list of mixed numeric characters (integers, floating-point, and strings) but we need a list of integers.

We create one by starting with an empty list called nbrs (line #10). The for loop iterates through the values and uses the int type constructor to convert each one to an integer, which it appends to nbrs.

Line #19 demonstrates a trick called a list comprehension. It does the same thing as lines #10 through #12. I’ll only introduce it here, but it provides a flavor of the power of lists in Python. List comprehensions and related topics are worth a post on their own. [If you want to jump ahead, see Simple Tricks #2 and Simple Tricks #3.]


Above we saw that how to access the items in lists, tuples, or strings with numeric indexes (slices). Any ordered list has implicit a set of numeric indexes to its items. In contrast, associative lists are collections of key/value pairs where unique keys — often strings — index values.

Python has the dict type (short for dictionary):

001| ### Dictionaries…
002| 
003| bag = dict()
004| bag = {}
005| 
006| bag = dict(x=2.1, y=4.2, z=6.3, sep=“,”)
007| bag = {“x”:2.1, “y”:4.2, “z”:6.3, “sep”:“,”}
008| 
009| bag:dict = {“x”:2.1, “y”:4.2, “z”:6.3, “sep”:“,”}
010| 

Line #3 uses the dict constructor to create a default object (an empty dictionary) and assign it to the variable named bag. Line #4 does the same thing by assigning an empty dictionary literal. Python uses curly braces to indicate dictionaries.

Line #6 shows one way to create a populated dictionary. The dict type constructor takes any number of keyword arguments and creates a dictionary from them. Line #7 creates the same dictionary using literal values.

Note key differences! Line #6 uses the syntax name=value, whereas line #7 uses key:value. An obvious difference is the equals-sign versus the colon. Less obvious is that the names for the first one must be valid Python variable names and are unquoted, but the keys in the second one can be any immutable data type (in this case they are all strings).

Strings are immutable and are commonly used as keys. But imagine a dictionary of months that uses keys made of tuples consisting of year and month:

001| # Dictionary with year-month keys…
002| 
003| winter = {
004|     (2025,10): [0, 0, 0, 0],
005|     (2025,11): [0, 0, 0, 0],
006|     (2025,12): [0, 0, 0, 0],
007|     (2026, 1): [0, 0, 0, 0],
008|     (2026, 2): [0, 0, 0, 0],
009|     (2026, 3): [0, 0, 0, 0],
010| }
011| 
012| # Make a key for January, 2026…
013| jan = (2026, 1)
014| 
015| # Get month of January, 2026…
016| january = winter[jan]
017| 
018| # Set values…
019| january[0] =  3   # Set high-temp
020| january[1] = +43   # Set low-temp
021| january[2] = 10.5  # Set snow-fall
022| january[3] = 15.3  # Set snow-depth
023| 
024| print(winter[jan])
025| 

Lines #3 through #10 create a dictionary (a dict object) called winter. It has six items, each indexed by a tuple consisting of (year, month). Each month has a four-item list as its value. The list items are all set to zero. We’ll use them as: low-temp, high-temp, snow-fall, and snow-depth for each month.

Line #13 creates a two-item tuple, (2026,1), and assigns it to the variable jan. We’ll use it as a key into the winter dictionary. We use it on line #16 to assign that month (January) to the variable january, which now is bound to the four-item list for that month.

Lines #19 through #22 assign numeric values to each item in the list. Lastly, line #24 prints the month of January:

[-3, 43, 10.5, 15.3]

Lastly, note that in a list context a dict returns its set of keys:

001| # Treating a dict as a list…
002| 
003| bag = {“x”:2.1, “y”:4.2, “z”:6.3, “sep”:“,”}
004| 
005| keys = list(bag)
006| print(keys)
007| 

When run, this prints:

['x', 'y', 'z', 'sep']

This turns out to be a handy feature. Python dictionaries are a powerful data type, and we’ll explore them in detail later. There is one last data type to cover today.


It’s not uncommon to have a list of items where some items may occur multiple times, but what’s needed is a list of unique items — each item appearing only once. The hard way is to compare each list item to each other list item and remove any duplicates.

We might consider using Python dictionaries because their keys are unique:

001| # Dictionary keys are unique…
002| 
003| colors = dict(red=1, green=2, blue=4)
004| print(colors)
005| 
006| colors[‘red’] = 8
007| 
008| colors[‘yellow’] = 0
009| colors[‘yellow’] = 1
010| 
011| print(colors)
012| 

Line #3 creates a new dict object assigned to the name colors. It has three members, indexed by the strings “red”, “green”, and “blue” and having the values 1, 2, and 4, respectively.

Line #6 sets the member indexed by “red” to the value 8. This overwrites the previous value of 1. Line #8 adds a new member indexed by the string “yellow” and having the value 0. Line #9 overwrites this new member and sets its value to 1.

When run, this prints:

{'red': 1, 'green': 2, 'blue': 4}
{'red': 8, 'green': 2, 'blue': 4, 'yellow': 1}

So, it’s possible to create a list of unique items using a dict. For each item in the list, use it to index and set a value in a dictionary:

001| # Find unique items in list…
002| 
003| stuff = [21, 42, ‘A’, 21, 63, ‘a’, 42, ‘A’, 63, 84, 99]
004| 
005| temp = {}
006| 
007| for item in stuff:
008|     temp[item] = True
009| 
010| unique = list(temp)
011| print(unique)
012| 

And then take the list of keys, which are guaranteed to be unique.

When run, this prints:

[21, 42, 'A', 63, 'a', 84, 99]

Which works fine, except Python makes it even easier with the set type:

001| ### Sets…
002| 
003| one = set()
004| one = {42}
005| 
006| one = {21, 42, 21, 42, 63, 63, 84, 42}
007| 
008| one:set = {21, 42, 21, 42, 63, 63, 84, 42}
009| 
010| ### Frozen Sets…
011| two = frozenset(one)
012| 

As its name implies, it’s a set of unique items. The frozenset type is also a set but is an immutable set. You can add and remove items from a set but not from a frozenset.

Now we can just write:

001| # Find unique items in list…
002| 
003| stuff = [21, 42, ‘A’, 21, 63, ‘a’, 42, ‘A’, 63, 84, 99]
004| 
005| unique = set(stuff)
006| print(unique)
007| 

Which when run prints:

{99, 42, 'A', 'a', 84, 21, 63}

Note that dict objects are guaranteed to keep the order in which items are added whereas set objects are not.

In my experience, in contrast to all the data types discussed so far, one doesn’t use the set type that often, but it is handy for unique lists.


That’s plenty for this time. Next time we’ll take a deeper dive into Python strings and lists. Then we’ll look into code structure: the if-else structure and the for and while looping structures. After that we’ll get into Python functions.

Lots to discuss in the coming weeks!