Python for Youngsters

(And Anyone Else Who Wants to Learn Programming)

Gregory Terzian
Python for Youngsters
9 min readMar 23, 2023

--

Part 2: Lists, Sets, Dictionaries, and Iteration

Introduction to the Second Part

As in the first part, all code examples below can be copied and pasted into the Python interactive shell. Let’s get started.

2.1 Lists

We have encountered the list in the previous chapter. Its name gives a good description of what it is: a list of items. You can add items to, and remove items from, a list.

a_list = []
a_list.append('one')
a_list.append('two')
a_list.append('three')
print(a_list)

a_list.pop()
print(a_list)

The list is a class, and append and pop are two of its methods. Python gives us the convenient literal notation to create an instance — as it does with strings and all other classes that come built into the language.

You can access items in the list based on their order; starting at 0 for the first item.

a_list[0]

To find out how many items are in the list, you can call the built-in “len” function, passing the list as the first and only argument.

len(a_list)

Why is “len” a function, and not a method of the list? The short answer is: it’s a quirk of Python. In fact, the list does have a method giving you its current length.

a_list.__len__()

Remember the __init__ method we saw in the previous chapter? Any method whose name is surrounded by underscores is a special method within Python: these methods allow any class to plug into the workings of the language itself. The __init__ method allows you to add data to an instance before the instance is made accessible to other code. The __len__ method allows an instance to be passed as the argument to the len function, so that it can give its length. We can add such a method to our combiner of words.

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing
self.words = []

def __len__(self):
return len(self.words)

def add_word(self, word):
self.words.append(word)

def combine_words(self):
combined_words = self.spacing.join(self.words)
return combined_words + self.final_punctuation

combiner = WordCombiner(", ", ".")
len(combiner)

Adding a __len__ method, we have plugged our combiner of words into the Python concept of length. In this particular example, we choose to return something that makes sense: the length of the internal word list. Instead, we could return whatever we want, including nonsense.

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing
self.words = []

def __len__(self):
return 888

def add_word(self, word):
self.words.append(word)

def combine_words(self):
combined_words = self.spacing.join(self.words)
return combined_words + self.final_punctuation

combiner = WordCombiner(", ", ".")
len(combiner)

Python gives you the means to plug your class into a concept used across the language; you can either use it or abuse it. By using it well, you make it easier for others to use your class — the essence of good programming.

What about adding our own len method to the combiner of words, would that not do away with the need to use the built-in len function?

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing
self.words = []

def len(self):
return len(self.words)

def add_word(self, word):
self.words.append(word)

def combine_words(self):
combined_words = self.spacing.join(self.words)
return combined_words + self.final_punctuation

combiner = WordCombiner(", ", ".")
combiner.len()

This works. But, adding your own custom method to express the length of your class, you move away from the conventional way of doing things in Python: your class becomes harder for someone else to use in their own code. Special methods like __len__ are a kind of reverse interface: they allow you to plug your own code into conventions of the language — the quickest way to make your code useable by others.

Lets now look at two other built-in classes: sets and dictionaries.

2.2 More Built-ins: Sets and Dictionaries

Sets are used to express membership.

a_set = {"one", "two"}

We have now created a set — once again Python gives us a convenient literal syntax for creating an instance — with two strings as the initial members.

We can add new members to the set.

a_set.add(“three”)
len(a_set)

But, because sets are about membership, adding the same thing to it twice will do nothing.

a_set.add("one")
len(a_set)

Since sets are about membership, they do not allow access using brackets like lists do — If you care about the order of items, use a list; if you care only about membership, use a set.

Dictionaries are yet another way to keep track of things in Python: they can store things at a specific location identified with a name — a store of values, indexed by unique keys.

a_dict = {"one": 1, "two": 2}

Here we store numbers at keys representing their English name, but we could store other things: for example instances of our combiner of words.

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing
self.words = []

def len(self):
return len(self.words)

def add_word(self, word):
self.words.append(word)

def combine_words(self):
combined_words = self.spacing.join(self.words)
return combined_words + self.final_punctuation

word_combiners = {
"dot_combiner": WordCombiner(", ", "."),
"exclamation_combiner": WordCombiner(", ", "!")
}

Using their keys, we can later access the stored items: either directly or, if we prefer, we can assign them to a variable first.

word_combiners["dot_combiner"].add_word("test")
a_dot_combiner = word_combiners["dot_combiner"]
a_dot_combiner.add_word("test 2")
a_dot_combiner.combine_words()

We can store additional items, but, using the same keys more than once will overwrite previously stored values. We can also remove items.

word_combiners["dot_combiner"] = "something else"
word_combiners["a_list_of_words"] = ["one", "two"]
word_combiners.pop("exclamation_combiner")
word_combiners

As you can see, you can fill up a dictionary with all kinds of things. But, as with all programming, clarity and logic should dictate how you use them. It might make more sense to use two different dictionaries: one for storing lists of words, one for storing combiner of words — separating the two concepts.

Being a mapping from unique name to value, a dictionary is similar to how things are named generally in Python. For a given scope, the code can give unique names to values: by assigning things to variables, but also by defining functions and classes. One can think of each scope as having its own dictionary, used to store and retrieve the values used in it by name: programmers call this a namespace.

Did you notice how the contents of the dictionary are shown in the console? While the lists is shown as, well, a list of words, our combiner of words is shown as something close to <__main__.WordCombiner object at 0x104a3c760>.

This is in fact the default way an instance of a class is shown, and we can change it, using another one of Python’s special methods.

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing

def __repr__(self):
return "A combiner of words, using '{}' as spacing, and '{}' as final punctuation.".format(self.spacing, self.final_punctuation)

print(WordCombiner(", ", “.”))

Above is a minimal version of our class, with a new method named __repr__: this is the method Python tries to call when it wants to print an instance in the console. If not present, a default string will be shown; we can change this default by adding this method, and in its body creating and returning the string we would like to see printed.

Our instance is now shown as: “A combiner of words, using ‘, ‘ as spacing, and ‘.’ as final punctuation.” In our __repr__ method, we create this string using the literal syntax, but with a twist: using the formatting feature. Python offers many such tools; these are all well documented online. As you are learning to think like a programmer, soon you will have no trouble reading the documentation of these tools, and using them as they fit in your program.

Let’s now look at something that lists, sets, and dictionaries all have in common: iteration.

2.3 Iteration

For each email in your inbox; for each post on your online profile; for each friend in your circle — these are all examples of iteration: processing one item at a time, from a list of items. We can rephrase “list of items” into “iterable”, meaning: something that produces items, one at a time.

For example, we could print each item in a list, set, or dictionary.

for item in ["one", "two", "three"]:
print(item)

for item in {"one", "two", "three"}:
print(item)

for item in {1: "one", 2: "two", 3: "three"}:
print(item)

The three examples above are all known as a “for loop”. The body of the loop — the indented code — will execute once for each item produced by the iterable. At each iteration, the current item is bound to a temporary variable, in this case we chose the name “item”.

Did you notice how the iteration over the dictionary only printed the keys? Iteration is yet another behavior in Python that is controlled via a special method: __iter__. For dictionaries, this gives you the keys, not the values — those are accessible to iterate over through a dedicated method.

for item in {1: "one", 2: "two", 3: "three"}.values():
print(item)

Iteration is an important concept in programming. But, you’ll find that in most cases it happens behind the scenes: when tempted to write a for loop, read the documentation first — you may find that what you want to do has already been done for you.

For example, let’s say that you wanted to sort the items in a list. For that, you can use the built-in function “sorted”.

unsorted_list = [2, 5, 6, 2, 1, 1, 8, 9, 34, 5, 232]
for item in sorted(unsorted_list):
print(item)

What if you want to not only sort the list, but also to remove duplicates from it?

unsorted_list = [2, 5, 6, 2, 1, 1, 8, 9, 34, 5, 232]
for item in sorted(set(unsorted_list)):
print(item)

Here, before sorting and then iterating, we first created a set from the list: removing duplicates along the way. We used the set class directly — a good example of when using the literal syntax is not convenient. Actually, a different kind of literal syntax, one that includes a kind of iteration, could have been used here: the comprehension. Below is an example, we won’t discuss it further; you’ll come across it soon enough while reading the Python docs online.

unsorted_list = [2, 5, 6, 2, 1, 1, 8, 9, 34, 5, 232]
for item in sorted({x for x in unsorted_list}):
print(item)

What if you had two lists, and you wanted to iterate over their items in sequence? Use the built-in “zip” function.

first_list = [1, 2, 3, 4, 5]
second_list = ["one", "two", "three", "four", "five"]
for number, word in zip(first_list, second_list):
print(number, word)

Incidentally, two lists zipped together can be used to create a dictionary.

first_list = [1, 2, 3, 4, 5]
second_list = ["one", "two", "three", "four", "five"]
dict(zip(first_list, second_list))

What if you wanted to sort a list of our combiner of words? How would you determine the order of the sort? The “sorted” function can take an optional argument: a function that will be called once for each item, and used to determine the value used for the sort. For example, we could sort a list of combiner of words based on the number of words that have been added to them.

class WordCombiner:
def __init__(self, spacing, final_punctuation):
self.final_punctuation = final_punctuation
self.spacing = spacing
self.words = []

def __repr__(self):
return "A combiner of words, using '{}' as spacing, and '{}' as final punctuation.".format(self.spacing, self.final_punctuation)

def __len__(self):
return len(self.words)

def add_word(self, word):
self.words.append(word)

first_combiner = WordCombiner(", ", "!")
first_combiner.add_word("one")
first_combiner.add_word("two")
first_combiner.add_word("three")

second_combiner = WordCombiner(", ", "?")
second_combiner.add_word("one")
second_combiner.add_word("two")

third_combiner = WordCombiner(", ", ".")
third_combiner.add_word("one")

def sort_key_for_combiner(combiner):
return len(combiner)

for combiner in sorted([first_combiner, second_combiner, third_combiner],
key=sort_key_for_combiner):
print(combiner)

The only new thing above is the use of the key parameter, and the use of a new syntax to pass an argument: the keyword. Python comes in fact with many ways to pass arguments to functions; you can review the use of keyword arguments, and other options, in the online documentation.

The above can be simplified: instead of passing our custom function sort_key_for_combiner to sorted, we can directly pass the built-in function len— it will be called once for all combiners in the list, so that they will be sorted by their length.

for combiner in sorted([first_combiner, second_combiner, third_combiner], 
key=len):
print(combiner)

Iteration is an important concept in programming; Python comes with tools that can help you achieve your iterative goals, often without writing much code yourself.

We have reviewed lists, sets, and dictionaries: a set of different tools, each useful for a set of different operations. We have also seen that these classes have certain methods that allows them to work with the language itself, and that your own classes can define these methods as well — plugging your code into a familiar way of doing things.

Programmers have a name for the set of interfaces that make a class what it is: a type. For example, the list: it can do certain things, such as giving you bracketed access to an item — that makes it a sequence. The type of the list can further be narrowed down to the mutable sequence: you can “append” and “pop” items from it. Since you can iterate over the items in a list, it is also part of the extended family of iterables. Not unlike zoologists, programmer like to classify their types. In Python, the focus is not on the name of the type, but rather what the type can do: this is called duck typing — if it quacks like a duck, it is a duck.

In your program, you will often use the types we have reviewed here. Sometimes, you will also define your own types: some with custom behavior — designing your own interfaces — and some that behave in similar ways to other types — plugging types into existing interfaces. A custom interface places a higher burden on others: they must spend time understanding something new. The capabilities this new thing offers must make it worth their time, otherwise, stick to the well-trodden path.

In the next part, we will look at how Python programs are run by the computer as processes.

--

--

Gregory Terzian
Python for Youngsters

I write in .js, .py, .rs, .tla, and English. Always for people to read