Objects in Python
Overview
Teaching: 20 min
Exercises: 15 minQuestions
What are objects in Python?
What is a class or type?
Can objects belong to more than one class?
How can objects be created from a class?
Objectives
Be able to distinguish between class and object
Be able to construct objects via a class’s constructor
Be able to distinguish between equality and identity of objects
You may recall that we calculated the mean of a Numpy array by using
numpy.mean
, as
import numpy
numbers = numpy.arange(10)
print(numpy.mean(numbers))
4.5
However, we can also calculate the mean of a Numpy array as:
print(numbers.mean())
4.5
Let’s see if we can do this with a normal list:
more_numbers = [1, 2, 3, 4]
print(more_numbers.mean())
In this case Python will complain with an error. How does Python know it can do this for numbers
but not more_numbers
?
What type is it?
Let’s investigate this further by using type
to identify what the data type of numbers
is:
type(numbers)
<class 'numpy.ndarray'>
What about the type of the variable more_numbers
?
type(more_numbers)
<class 'list'>
We can see here that numbers
is an object of the type numpy.ndarray
. In Python, anything which can be stored in a variable or passed to a function is called an object. Objects are classified by their type
, or their class
.
Class or Type?
Note that in literature, you’ll find a subtle distinction between class and type. However, since in Python 3 we can’t have one without the other, we will use both terms interchangably.
Let’s find some types
Can you find anything that you can store in a variable which does not have a class? What is the type of the number
1
, or the string"hello"
?Does the class change if they are passed directly to
type
, or if they are stored in a variable?Solution
type(1)
<class 'int'>
type("hello")
<class 'string'>
Does everything have a class?
Try to find words that Python recognises that do not have classes. What about
numpy.mean
ornumpy
? What aboutif
orfor
? Can you think of others?Solution
type(numpy.mean)
<class 'function'>
type(numpy)
<class 'module'>
The objects
numpy.mean
andnumpy
are things that we typically wouldn’t store in variables or passed around. However, they could in principle be stored in variables, and therefore are objects with a class.type(if)
File "<stdin>", line 1 type(if) ^ SyntaxError: invalid syntax
type(for)
File "<stdin>", line 1 type(for) ^ SyntaxError: invalid syntax
The words
if
andfor
are part of the Python language itself, they can’t be stored in variables. Only things which can be stored in variables can have a class.
Changing things
In Python, there are two ways in which objects can behave. The most intuitive case is when object are created with a value, and they keep the value forever. Many objects we’re familiar with, such as integers or strings, are objects which hold a value.
Let’s store a string in a variable:
message = "Hello"
The variable message
now refers to an object, which has the value
"Hello"
. We can point another variable at the same object with:
second_message = message
But, we can never change the value of the string object itself. The
string “hello” will always be the string “hello”. We can set the
variable second_message
to a new object, with
second_message = message + ", world"
But the original object is still there, unchanged. We can still get to it by typing
print(message)
This may not seem surprising, but not all objects in Python behave this way. Consider the following list of strings:
messages = ["Hello", "world!"]
Let’s point new variable duplicate_messages
at the list named messages
duplicate_messages = messages
Think of this as pointing duplicate_messages
at the same underlying
object contained in messages
: Now let’s change a part of
duplicate_messages
:
duplicate_messages[1] = "there!"
What is the value of messages
now?
print(messages)
['Hello', 'there!']
Note how we changed messages
through the variable
duplicate_messages
. We can do this because both messages
and
duplicate_messages
refer to the same underlying object, and that
underlying object can be changed.
We say that objects which can’t be changed, like numbers and string, are immutable. Numbers are an intuitive example of immutable objects, the number 1000 will always be the number 1000. We say that these objects that can be changed are mutable, they can be “mutated” after they’ve created.
Immutable lists
Python has a class similar to a list called a
tuple
. Is atuple
mutable or immutable?Check if you can change a tuple by setting:
messages = ("Hello", "world!")
and trying to modify the second element with:
messages[1] = "there!'
Solution
messages = ("Hello", "world!") messages[1] = "there!"
You should see an error containing the text:
TypeError: 'tuple' object does not support item assignment
This is telling you that you can’t modify the tuple object, this is true because the tuple object is immutable.
What kind of objects?
List some objects that you think are mutable and immutable. Verify this by trying to find ways to change the objects.
Note: Be careful that you’re not “cheating” by using
=
to point to a new object.
Instances and Methods
We say that an object of a particular class is an instance of that
class. To use a real world example, we could have the type or class
Chair
which describes to all the chairs in the world. The chair that
you are sitting on right now is a specific instance of the chair
class.
We can check if an object is an instance of a particular class with the isinstance
function.
isinstance(numbers, numpy.ndarray)
True
Every object is created with a single class, and which can’t be changed. The class of an object can also provide behaviour that the object might have, by providing functions to objects in its class. These functions can be called by using a dot after the variable name, for example:
numbers.mean()
The functions which are associated with an object are provided by the class of the object. When a class provides a function to an object we call that function a method of the class.
We say that the numpy.ndarray
class provides the mean
method. Since numbers
belongs to the class numpy.ndarray
, we can use the mean
method on the object referred to by numbers
, by calling numbers.mean()
. This allows objects of a numpy.ndarray
to provide functionality specific to objects of class numpy.ndarray
.
It’s worth noting that both mutable and immutable objects can have methods. Methods of immutable objects, however, can’t change the underlying object. If needed, they will return a brand new object, and set the expected value in the new object. To keep this change, you will need to store it in a variable, for example:
hello = "hello, world"
capital_hello = hello.capitalize()
print(capital_hello)
Methods of mutable objects can, and often do, change the object.
grades = [84, 78, 91]
grades.append(66)
print(grades)
[84, 78, 91, 66]
In this case, we don’t need the extra =
to assign the value to a new object.
Finding out what things are
use
type()
to find the type ofstudents
, defined asstudents = ['Petra', 'Aalia', 'Faizan', 'Shona']
and check this with
isinstance
.Solution
type(students)
<class 'list'>
isinstance(students, list)
True
Other common classes
What other classes have you encountered previously when using Python? What methods did they provide?
Making an object
A class can be called as a function, in which case it constructs new instances of itself. While this is not the only way to make objects, it is one that all classes offer. For example, a new list can be created as:
students = list()
print(students)
print(type(students))
[]
<class 'list'>
Making a Numpy array
While all classes can be constructed by calling their name, some classes don’t recommend this route. For example,
numpy.ndarray
is used internally by Numpy to initialise its arrays, but Numpy recommends using one of the higher-level functions likenumpy.zeros
,numpy.ones
,numpy.empty
, ornumpy.asarray
to construct an array (of zeroes, of ones, without initialising the data, and initialising from an existing data structure like a list, respectively).
Make a dict
Given the following list of students and their grades, how would you construct a
dict
with students as keys, and grades as values?students = ['Petra', 'Aalia', 'Faizan', 'Shona'] grades = [84, 78, 91, 66]
You can check the type of the object you’ve created with
isinstance
Hint:
zip()
can be used to turn two lists into tuples of corresponding pairs of elements.Solution
student_grades = dict(zip(students, grades)) isinstance(student_grades, dict)
True
Equality and identity
Python has two ways of testing whether two objects are the “same”. The first is equality, or whether the associated values or contents of the object are the same.
The second is identity, or whether the objects are in fact the same instance, with names referring to the same underlying object.
Equality is tested with ==
, which you have probably used before. We can test for identity with the is
keyword:
old_students = students
new_students = ['Petra', 'Aalia', 'Faizan', 'Shona']
if old_students == students:
print("old_students is equal to the students list")
if new_students == students:
print("new_students is equal to the students list")
if old_students is students:
print("old_students is identical to the students list")
if new_students is students:
print("new_students is identical to the students list")
Constructing a new list that has the same elements as an existing list gives a list that is equal, but not identical, to the existing one. This is true for any class: constructing a new object that is the same as an existing one will give a result that is equal, but not identical, to the existing one.
Inheritance
Object-oriented programming allows relationships to be defined between classes or types. One class may be considered to be a specialisation or subclass of another. For a real world example, a car could be considered a specialisation or subclass of the class of all vehicles.
This is very frequently seen in the way Python handles exceptions. For example,
if we check what type a ValueError
is, we see that it is of
class 'ValueError'
:
an_error = ValueError("A value must be provided")
print(type(an_error))
if isinstance(an_error, ValueError):
print("an_error is a ValueError")
<class 'ValueError'>
an_error is a ValueError
However, we can also check if it is an Exception
:
if isinstance(an_error, Exception):
print("an_error is an Exception")
an_error is an Exception
This is because ValueError
is a subclass of Exception
: value errors are a
specific type of exception that can occur, and so should have all the same
logic that is common to all exceptions.
One place this can be used is to structure exception handling; for example:
numerator = 5
denominator = 0
try:
print(numerator, "divided by", denominator, "is", numerator / denominator)
except ZeroDivisionError:
print("You can't divide by zero!")
except Exception:
print("Something else went wrong.")
ZeroDivisionError
is another subclass of Exception
. On encountering an
exception, Python checks each except
in turn to see whether the exception
matches the class being tested for. The more specific ZeroDivisionError
catches the specific case of dividing by zero, but the block is skipped for
all other issues, which are then handled by the more general Exception
.
Key Points
Anything that we can store in a variable in Python is an object
Every object in Python has a class (or type)
list
andnumpy.ndarray
are commonly-used classes; lists and arrays are corresponding objectsCalling the class as a function constructs new objects of that class
Classes can inherit from other classes; objects of the subclass are automatically also of the parent class