Use Lists and Dictionaries
When programming, you often store data in a collection. Collections include lists - where data has a position and is indexable - and dictionaries - where data is assigned a key.
You might remember that you can put all types of data - including a mix of types - in a list or dictionary:
numberList = [1.2, 3.5, 4.3, 2.8]
phoneNumberDictionary = { “alice” : “01234 567890”, “bob” : “01324 765098”}
mixedList = [5, 4, 3, 2, 1, “boom”]
But wait a moment, can’t I change an int? If I say x = 5, I can later set x to be 6, can’t I?
When you set x to be 6, you aren’t changing the value of the number 5 - that still exists - you’re just changing what the variable x contains. If I change a Person object’s name attribute, I am changing that object’s state.
Anyway, objects of any class are the same as anything else! They can be stored in collections too, a bit like this. Let’s look at a list of volunteers for a local food drive:
volunteers = [Person(“Alice”), Person(“Bob”), Person(“Carol”)]
for volunteer in volunteers:
volunteer.walk()
toolbox = {
“screwdriver” : Screwdriver(50),
“hammer” : Hammer(),
“nails” : [Nail(), Nail(), Nail() …]
}
In the same way as before, you have a list and a dictionary - but this time, the list contains people, and the dictionary contains all sorts of things! You even have a for loop that iterates over the volunteers (person objects) and calls their .walk() method.
What could go wrong?
Use Duck Typing Safely
Python employs what is known as duck typing: if something looks like a duck, and it quacks like a duck, then it’s probably a duck. In other words, an object's methods or attributes are more important than its type or class.
Let's look at an example. What if our volunteers this week are a little unusual?
volunteers = [Person(“Alice”), Fish(“Wanda”), Person(“Bob”)]
for volunteer in volunteers:
volunteer.walk() #Oops!
Fish can’t walk, so if you ran this code, you’d get something like this:
AttributeError: ‘Fish’ object has no attribute ‘walk’
In other languages, you’d have to define the list as holding a particular type - but that doesn't matter in Python! You just have a list, and it holds things, and you don’t find out that fish can’t walk until your program crashes. 😬 Uh oh...
So how do I make sure this doesn’t happen?
There are a few different things you can do in different ways and to different degrees. Which strategies you use will depend heavily on your program.
Documentation
Code can be documented with comments, using a hash (#
) symbol. A common approach to documenting methods and functions is to describe what the method does, what types the parameters are, and what type it returns (if any.) This allows you to, at a glance, see if your use of a function/method is correct.
Typed Python
Python 3 provides you with the typing
library, allowing you to write code with type annotations like other languages. It’s not commonly used, but it is helpful for some projects - follow the project’s style guide (more on that in the next part) on whether to use it.
Here’s an example:
from typing import List
def highest(numbers : List[int]) -> int:
max_value = 0
for n in numbers:
if n > max_value:
max_value = n
return max_value
This function returns the highest integer in a list of integers. This function signature says that numbers must be a List[int] - a list of integers - and the return type of the function is int.
Typing can help ensure that the right variables are in the right place, but it is worth noting that Python does not guarantee correctness - you could put a list of strings into the highest
function.
Python typing is entirely within the purview of the programmer and, more likely, the IDE. IDEs like PyCharm will tell you if you have misused a typed function or method, which will allow you to catch errors.
Defensive Programming
Defensive programming is an approach based on guarding against the possibility of mistakes and errors. There are a few tools you can use to do this, and the simplest of which is the "if" statement:
def divide(a, b):
if b != 0:
return a / b
else:
return 0
Here you guard against the possibility of dividing by zero. There are a few other tools that Python provides, such as the assert
statement - often used in tests - which causes a program to raise an exception if something isn't true. For example:
def divide(a, b):
assert b != 0
return a / b
If you provide this divide function with a denominator of 0, the program will terminate before attempting the division.
Python also provides some functions to ensure that objects are of a type (isinstance
) or have a particular attribute (hasattr
), which can be used like this:
for volunteer in volunteers:
if hasattr(volunteer, "walk"):
volunteer.walk()
if not isinstance(x, Y):
raise Z("M")
There is such a thing as being overly defensive - after all, each assertion, if statement, or function call requires computational time that is not being used in running your actual program.
Exceptions
While running a Python program, certain invalid operations cause exceptions, indicating to the user and other parts of the program that an error has occurred.
You can write your own exceptions, cause them to be thrown when there’s a problem, and handle them in various ways. We talk more about exceptions and exception handling in Part 3.
A Hands-Off Approach
Smaller and less important scripts and programs often have a much more hands-off approach to these errors. Which is to say, sometimes just letting the program crash is sometimes an option! While you want to avoid this in larger or important applications, it's often the right thing to do if a situation isn't recoverable from.
A Word From Your Teacher
Your Turn: Use Objects in Lists and Dictionaries
Context:
In this final part, we'll complete our application by implementing the display
method of the Thread
class. This method will fully display the metadata and messages of each thread, providing a comprehensive user experience and finalizing all the parts worked on in the previous chapters of this section.
Instructions:
Finally, you’re going to implement the thread display method in this part’s code.
Remember that to display a thread, you need to display the thread’s metadata (title, date, and poster), and then all of the posts in that thread in order.
Let’s Recap!
You can store objects in collections - just like you can anything else.
The way Python deals with types is known as duck typing - if it looks like a duck and it quacks like a duck, it’s probably a duck.
When using collections, try not to access an object's attributes that it doesn't have. There are a variety of ways you can do that.
Now you know all about inheritance, test your knowledge with the Part 2 quiz! In Part 3, we’ll talk about structuring your code and error handling.