12. Classes and Objects
A class is the template from which objects are created. An object is said to be an instance of a class.

Note: The [shared] folder has been placed in the project's [Root Sources].
12.1. script [classes_01]: an Object class
The [classes_01] script demonstrates an obsolete use of classes:
# a class
class Object(object):
"""an empty Object class"""
# any variable of type [Object] can have attributes by default
obj1 = Object()
obj1.attr1 = "one"
obj1.attr2 = 100
# displays the object
print(f"object1=[{obj1}, {type(obj1)},{id(obj1)},{obj1.attr1},{obj1.attr2}]")
# modifies the object
obj1.attr2 += 100
# displays the object
print(f"object1=[{obj1.attr1},{obj1.attr2}]")
# assigns the reference obj1 to obj2
obj2 = obj1
# modifies the object pointed to by obj2
obj2.attr2 = 0
# displays both objects - obj1 now points to a modified object
print(f"object1=[{obj1.attr1},{obj1.attr2}]")
print(f"object2=[{obj2.attr1},{obj2.attr2}]")
# obj1 and obj2 point to the same object
print(f"object1=[{obj1}, {id(obj1)},{obj1.attr1},{obj1.attr2}]")
print(f"object2=[{obj2}, {id(obj2)},{obj2.attr1},{obj2.attr2}]")
print(obj1 == obj2)
# type of the obj1 instance
print(f"type(obj1)={type(obj1)}")
print(f"isinstance(obj1, Object) = {isinstance(obj1, Object)}, isinstance(obj1, object) = {isinstance(obj1, object)}")
# Every type is an object in Python
print(f"type(4)={type(4)}")
print(f"isinstance(4, int)={isinstance(4, int)}, isinstance(4, object)={isinstance(4, object)}")
Notes:
- lines 2-3: an empty [Object] class;
- Line 2: The class can be declared in three forms:
- class Object;
- class Object();
- class Object(object);
- line 3: another form of comment. This one, preceded by three "", can span multiple lines;
- line 7: instantiation of the Object class. The result is an address, as shown in lines 24–26;
- lines 8–9: direct initialization of two attributes of the object;
- line 17: copying references. The variables obj1 and obj2 are two pointers (references) to the same object;
- line 19: we modify the object pointed to by [obj2]. Since [obj1] and [obj2] point to the same object, the displays of the objects [obj1, obj2] in lines 21 and 22 will show that the object pointed to by [obj1] has changed;
- lines 24–26: these lines are intended to demonstrate that the variables [obj1] and [obj2] are equal. The output of line 26 will confirm this. In this comparison, it is the addresses of [obj1] and [obj2] that are equal;
- Every Python object is identified by a unique ID obtained using the expression [id(object)]. Lines 24 and 25 will show that the IDs of the objects pointed to by [obj1] and [obj2] are identical, thereby demonstrating that these two references point to the same object;
- Lines 27–29: The function [isinstance(expr, Type)] returns the Boolean value True if the expression [expr] is of type [Type]. Here, we will see that [obj1] is of type [Object], which seems natural, but also of type [object]. The [object] class is the parent class of all Python classes. By the property of class inheritance, a child class F has all the properties of its parent class P, and the function [isinstance(instance of F, P)] returns True;
- lines 30–32: show that type [int] is also a type [object]. All Python types derive from the [object] class;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_01.py
object1=[<__main__.Object object at 0x0000025C3F469BB0>, <class '__main__.Object'>,2595221838768,one,100]
object1 = [1, 200]
object1 = [1, 0]
object2 = [1, 0]
object1 = [<__main__.Object object at 0x0000025C3F469BB0>, 2595221838768, one, 0]
object2=[<__main__.Object object at 0x0000025C3F469BB0>, 2595221838768,one,0]
True
type(obj1) = <class '__main__.Object'>
isinstance(obj1, Object) = True, isinstance(obj1, object) = True
type(4) = <class 'int'>
isinstance(4, int)=True, isinstance(4, object)=True
Process finished with exit code 0
12.2. Script [classes_02]: a Person class
The [classes_02] script demonstrates that a class's attributes are public: they can be accessed directly from outside the class. This is another example of a class usage that is not recommended. We include it, however, because you may occasionally encounter this type of code (Python allows it), and you need to be able to understand it.
# Person class
class Person:
# class attributes
# undeclared - can be created dynamically
# method
def identity(self: object) -> str:
# by default, uses non-existent attributes
return f"[{self.first_name},{self.last_name},{self.age}]"
# ---------------------------------- main
# attributes are public and can be created dynamically
# class instantiation
p = Person()
# direct initialization of class attributes
p.first_name = "Paul"
p.last_name = "de la Hûche"
p.age = 48
# calling a class method
print(f"person={p.identity()}")
# type of the p instance
print(f"type(p)={type(p)}")
print(f"isinstance(Person) = {isinstance(p, Person)}, isinstance(object) = {isinstance(p, object)}")
Notes:
- lines 2–9: a class with a method;
- line 7: every method of a class must have the self object, which refers to the current object, as its first parameter. The [identity] method returns a string;
- line 15: instantiation of a [Person] object;
- lines 16–19: show that the object’s attributes can be created dynamically (they do not exist in the class definition);
- line 9: the class’s attributes are denoted by the notation [self.attribute];
- lines 23–24 demonstrate that the object [p] is both an instance of the [Person] class and of the [object] class;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_02.py
person = [Paul, de la Hûche, 48]
type(p) = <class '__main__.Personne'>
isinstance(Person) = True, isinstance(object) = True
Process finished with exit code 0
12.3. Script [classes_03]: the Person class with a constructor
The script [classes_03] demonstrates the standard use of a class:
# Person class
class Person:
# constructor - initializes three attributes
def __init__(self: object, first_name: str, last_name: str, age: int):
# first_name: the person's first name
# name: person's name
# age: person's age
self.first_name = first_name
self.last_name = last_name
self.age = age
# returns the class attributes as a string
def identity(self: object) -> str:
return f"[{self.first_name},{self.last_name},{self.age}]"
# ---------------------------------- main
# a Person object
p = Person("Paul", "de la Hûche", 48)
# calling a method
print(f"person={p.identity()}")
Notes:
- line 4: the class constructor is called __init__. As with other methods, its first parameter is self;
- line 20: a Person object is created using the class constructor;
- lines 13–15: the [identity] method returns a string representing the object’s content;
- line 22: displays the person's identity;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_03.py
person=[Paul, de la Hûche, 48]
Process finished with exit code 0
12.4. Script [classes_04]: static methods
We define the following [Utils] class (utils.py) in the [modules] folder:

class Utils:
# static method
@staticmethod
def is_string_ok(string: str) -> bool:
# Is string a string?
error = not isinstance(string, str)
if not error:
# Is the string empty?
error = string.strip() == ''
# result
return not error
Notes
- Line 3: The [@staticmethod] annotation indicates that the method marked with it is a class method, not an instance method. This is evident from the fact that the first parameter of the annotated method is not the [self] keyword. Therefore, the static method does not have access to the object's attributes. Instead of writing:
we write
Because we wrote [Utils.is_string_ok] above, the [is_string_ok] method is called a class method (the Utils class in this case). To achieve this, the [Utils.is_string_ok] method must be annotated with the [@staticmethod] keyword.
The static method [Utils.is_string_ok] allows us to verify here that a piece of data is a non-empty string.
The [classes_04] script uses the [Utils] class as follows:
# configure the application
import config
config = config.configure()
# the syspath is configured - we can now import
from utilitaires import Utils
print(Utils.is_string_ok(" "))
print(Utils.is_string_ok(47))
print(Utils.is_string_ok(" q "))
- Lines 1-4: We use a configuration script;
The configuration script [config.py] is as follows:
def configure():
import os
# directory of the configuration file
script_dir = os.path.dirname(os.path.abspath(__file__))
# absolute paths of directories to add to the syspath
absolute_dependencies = [
f"{script_dir}/shared",
"C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/venv/lib/site-packages",
"C:/myprograms/Python38/lib",
"C:/myprograms/Python38/DLLs"
]
# Update the syspath
from myutils import set_syspath
set_syspath(absolute_dependencies)
# return the configuration
return {}
- Line 9: The [shared] folder will be added to the Python Path;
The result of the execution is as follows:
12.5. Script [classes_05]: Attribute validity checks
The [classes_05] script introduces new concepts:
- definition of a custom exception type;
- definition of the [_str_] method, which is the default identity method for classes;
- definition of properties;
# configuring the application
import config
config = config.configure()
# the syspath is configured - we can now import
from utilities import Utils
# A custom exception class derived from [BaseException]
class MyException(BaseException):
# We do nothing: empty class
pass
# Person class
class Person:
# constructor
def __init__(self: object, first_name: str = "x", last_name: str = "y", age: int = 0):
# first_name: the person's first name
# last_name: the person's last name
# age: the person's age
# storing parameters
# initialization will be done via setters
self.first_name = first_name
self.last_name = last_name
self.age = age
# toString method of the class
def __str__(self: object) -> str:
return f"[{self.__first_name},{self.__last_name},{self.__age}]"
# getters
@property
def first_name(self) -> str:
return self.__first_name
@property
def name(self) -> str:
return self.__name
@property
def age(self) -> int:
return self.__age
# setters
@first_name.setter
def first_name(self, first_name: str):
# the first_name must not be empty
if Utils.is_string_ok(first_name):
self.__first_name = first_name.strip()
else:
raise MyException("The first name must be a non-empty string")
@surname.setter
def last_name(self, last_name: str):
# the first_name must not be empty
if Utils.is_string_ok(name):
self.__name = name.strip()
else:
raise MyException("The name must be a non-empty string")
@age.setter
def age(self, age: int):
# age must be an integer >= 0
error = False
if isinstance(age, int):
if age >= 0:
self.__age = age
else:
error = True
else:
error = True
# error?
if error:
raise MyException("Age must be an integer >= 0")
# ---------------------------------- main
# a Person object
try:
# instantiating the Person class
p = Person("Paul", "de la Hûche", 48)
# display object p
print(f"person={p}")
except MyException as error:
# display error
print(error)
# another Person object
try:
# instantiate the Person class
p = Person("xx", "yy", "zz")
# display object p
print(f"person={p}")
except MyException as error:
# display error
print(error)
# another person, this time without parameters
try:
# instantiate the Person class
p = Person()
# display object p
print(f"person={p}")
except MyException as error:
# display error message
print(error)
# cannot access the class's private attributes __attr
p.__first_name = "Gaëlle"
print(f"p.first_name={p.first_name}")
print(f"p.__first_name={p.__first_name}")
p.first_name = "Sébastien"
print(f"p.first_name={p.first_name}")
print(f"p.__first_name={p.__first_name}")
Notes:
- lines 10–13: a MyException class derived from the BaseException class (we’ll cover this point a bit later). It adds no functionality to the latter. It’s only there to provide a custom exception;
- line 19: the constructor has default values for its parameters. Thus, the operation p = Person() is equivalent to p = Person("x", "y", 0);
- lines 34–45: the class properties. These are methods annotated with the [@property] keyword. They are used to set the values of the attributes;
- lines 47–77: the class’s setters. These are methods annotated with the [@attributsetter] keyword. They are used to set the value of the attributes;
- lines 48–54: the setter for the [first_name] attribute. This method will be called every time a value is assigned to the [first_name] attribute:
Line 2 will trigger the call [p.firstName(value)]. The advantage of using a setter to assign a value to an attribute is that, since the setter is a function, we can verify the validity of the value assigned to the attribute;
- line 51: we verify that the value assigned to the [first_name] attribute is a non-empty string. To do this, we use the static method [Utils.isStringOk] seen earlier;
- line 52: the value assigned to the [first_name] attribute is stripped of its leading and trailing whitespace and assigned to the [self.__first_name] attribute. Therefore, the [first_name] attribute itself is not used here. We could not have done otherwise, or we would have had an infinite recursive call. We could have used any attribute name. The fact that we used the attribute [__prénom] with two underscores at the beginning of the identifier has a special meaning: attributes preceded by two underscores are private to the class. This means they are not visible from outside the class. Therefore, we cannot write:
In fact, we’ll soon see that you can write it, but it doesn’t change the first name. It does something else;
- lines 53–54: if the value assigned to first_name is incorrect, an exception is thrown. This way, the calling code will know that its call is incorrect;
- lines 35–37: the [first_name] property. It will be called every time [p.first_name] is written in an expression. The [p.first_name()] method will then be called. Line 37: We return the value of the [__first_name] attribute, since we saw that the setter for the [first_name] attribute assigns its value to the private [__first_name] attribute;
- Lines 56–62: The setter for the [last_name] attribute is constructed similarly to that of the [first_name] attribute. The same applies to the setter for the [age] attribute on lines 64–77;
- although the properties [first_name, last_name, value] are not the actual attributes—which are in fact [__first_name, __last_name, __age]—we will continue to refer to them as the class attributes, since they are used as such;
- lines 19–28: The class constructor implicitly uses the setters for the attributes [first_name, last_name, age]. In fact, by writing [self.first_name = first_name] on line 26, the method [first_name(self, first_name)] is implicitly called. The validity of the [first_name] parameter will then be checked. The same applies to the other two attributes [last_name, age];
- with this model, we cannot assign incorrect values to the class attributes [first_name, last_name, age];
- lines 30–32: the __str__ function replaces the method previously called identity. The name [__str__] (two underscores before and after) is not insignificant. We will see this later;
- lines 83–86: instantiation of a person, followed by display of their identity;
- line 84: instantiation;
- Line 86: Display. The operation requires displaying the object p as a string. The Python interpreter automatically calls the method p.__str__() if it exists. This method serves the same purpose as the toString() method in Java or .NET languages;
- lines 87–89: Handling a potential MyException. Then displays the error;
- lines 91–99: same as above for a second person instantiated with incorrect parameters;
- lines 102–109: same as above for a third person instantiated with default parameters: no parameters are passed. The default values of these parameters in the constructor are then used here;
- lines 112–117: we said that the [__first_name] attribute was private and therefore normally inaccessible from outside the class. We want to verify this;
- lines 112–114: we assign a value to the [__first_name] attribute, then check the values of the [__first_name] and [first_name] attributes, which should normally be the same;
- lines 115–117: We repeat the operation, this time initializing the [first_name] attribute;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_05.py
person=[Paul,de la Hûche,48]
Age must be an integer >= 0
person = [x, y, 0]
p.first_name = x
p.first_name = Gaëlle
p.first_name = Sébastien
p.first_name=Gaëlle
Process finished with exit code 0
Notes
- lines 5-6: we see that the assignment [p.__first_name = "Gaëlle"] did not change the value of the [first_name] attribute, line 5;
- lines 7-8: we see that the assignment [p.first_name = "Sébastien"] has not changed the value of the [__first_name] attribute, line 8;
What can we conclude from this? That likely, the operation [p.__first_name = "Gaëlle"] created a public attribute [__first_name] for the class, but that this is different from the private attribute [__first_name] manipulated within it;
12.6. Script [classes_06]: Adding an object initialization method
The script [classes_06] adds a method to the [Person] class:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from utilities import Utils
# A custom exception class derived from [BaseException]
class MyException(BaseException):
# We do nothing: empty class
pass
# Person class
class Person:
# constructor
def __init__(self: object, first_name: str = "x", last_name: str = "y", age: int = 0):
# first_name: the person's first name
# last_name: the person's last name
# age: the person's age
# storing parameters
# initialization will be done via setters
self.first_name = first_name
self.last_name = last_name
self.age = age
# another initialization method
def init_with_person(self: object, p: object):
# initializes the current object with a person p
self.__init__(p.first_name, p.last_name, p.age)
# toString method of the class
def __str__(self: object) -> str:
return f"[{self.__first_name},{self.__last_name},{self.__age}]"
# getters
…
# setters
…
# ---------------------------------- main
# a Person object
try:
# instantiating the Person class
p = Person("Paul", "de la Hûche", 48)
# display object p
print(f"person={p}")
except MyException as error:
# display error message
print(error)
# another Person object
try:
# instantiate the Person class
p = Person("xx", "yy", "zz")
# display object p
print(f"p={p}")
except MyException as error:
# display error message
print(error)
# another person, this time without parameters
try:
# instantiate the Person class
p = Person()
# display object p
print(f"p={p}")
except MyException as error:
# display error message
print(error)
# another Person object created by copying
try:
# instantiate the Person class
p2 = Person()
p2.init_with_Person(p)
# display object p2
print(f"p2={p2}")
except MyException as error:
# display error message
print(error)
Notes:
- The difference from the previous script is in lines 30–33. We have added the initWithPerson method. This method calls the __init__ constructor. Unlike in typed languages, it is not possible to have methods with the same name that are distinguished by the nature of their parameters or their return values. Therefore, it is not possible to have multiple constructors that would create the object from different parameters, in this case an object of type Person;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_06.py
person=[Paul, de la Hûche, 48]
age must be an integer >= 0
p = [x, y, 0]
p2 = [x, y, 0]
Process finished with exit code 0
12.7. Script [classes_07]: a list of Person objects
We will now place the [MyException] and [Person] classes in a module so we can use them without having to copy their code:

Both classes will be in the [myclasses.py] module above.
The [classes_07] script shows that we can have a list of objects:
# configure the application
import config
config = config.configure()
# the syspath is configured - we can do the imports
from myclasses import Person
# ---------------------------------- main
# create a list of Person objects
group = [Person("Paul", "Langevin", 48), Person("Sylvie", "Lefur", 70)]
# identities of these people
for i in range(len(group)):
print(f"group[{i}]={group[i]}")
Notes:
- line 7: we import the [Person] class;
- line 11: a list of objects of type [Person];
- lines 13–14: we iterate through this list to display each of its elements;
- line 14: the [print] function will display the string representing the object [group[i]]. By default, the [__str__] method of these objects will be called;
Results
C:\Users\serge\.virtualenvs\cours-python-v02\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/v-02/classes/01/classes_07.py
group[0] = [Paul, Langevin, 48]
group[1] = [Sylvie, Lefur, 70]
Process finished with exit code 0
12.8. Script [classes_08]: creating a class derived from the Person class
We define the following [Teacher] class in the [myclasses] module:
# Teacher class
class Teacher(Person):
# constructor
def __init__(self, first_name: str = "x", last_name: str = "x", age: int = 0, subject: str = "x"):
# first_name: the person's first name
# last_name: person's last name
# age: person's age
# subject: subject taught
# Initialization of the parent
Person.__init__(self, first_name, last_name, age)
# other initializations
self.subject = subject
# toString
def __str__(self) -> str:
return f"teacher[{super().__str__()},{self.subject}]"
# properties
@property
def discipline(self) -> str:
return self.__discipline
@discipline.setter
def discipline(self, discipline: str):
# the discipline must be a non-empty string
if Utils.is_string_ok(discipline):
self.__discipline = discipline
else:
raise MyException("The discipline must be a non-empty string")
- Line 2: declares the Teacher class as a subclass of the Person class. A subclass has all the properties (attributes and methods) of its parent class plus its own;
- line 13: the [Teacher] class defines a new attribute [subject];
- line 11: the constructor of the derived Teacher class must call the constructor of the parent Person class, passing it the parameters it expects;
- line 17: the [super()] function calls the parent class. Here, we call the [__str__] function of the parent class;
- lines 19–30: the getter and setter for the new [subject] attribute are defined;
The [classes_08] script uses the [Teacher] class as follows:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import
from myclasses import Person, Teacher
# ---------------------------------- main
# create an array of Person objects and derivatives
group = [Teacher("Paul", "Langevin", 48, "English"), Person("Sylvie", "Lefur", 70)]
# identities of these people
for i in range(len(group)):
print(f"group[{i}]={group[i]}")
Notes:
- line 7: we import the [Person] and [Teacher] classes defined in the [myclasses.py] file;
- lines 11–14: we define a group of people and then display their identities;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_08.py
group[0] = teacher[[Paul, Langevin, 48], English]
group[1] = [Sylvie, Lefur, 70]
Process finished with exit code 0
12.9. Script [classes_09]: second class derived from the Person class
The script [classes_09] introduces the class [Student] derived from the class [Person]. This is defined as follows in the module [myclasses]:
# Student class
class Student(Person):
# constructor
def __init__(self: object, first_name: str = "x", last_name: str = "y", age: int = 0, major: str = "x"):
Person.__init__(self, first_name, last_name, age)
self.education = education
# toString
def __str__(self: object) -> str:
return f"student[{super().__str__()},{self.education}]"
# properties
@property
def education(self) -> str:
return self.__formation
@formation.setter
def training(self, training: str):
# the training must be a non-empty string
if Utils.is_string_ok(training):
self.__training = training
else:
raise MyException("The course must be a non-empty string")
The [classes_09] script uses the [Student] class as follows:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import
from myclasses import Person, Teacher, Student
# ---------------------------------- main
# create an array of Person objects and derived classes
group = [Teacher("Paul", "Langevin", 48, "English"), Person("Sylvie", "Lefur", 70),
Student("Steve", "Boer", 22, "iup2 quality")]
# identities of these people
for person in group:
# Display person
print(person)
Notes:
- This script is similar to the previous one.
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/classes/01/classes_09.py
teacher[[Paul,Langevin,48],English]
[Sylvie,Lefur,70]
student[[Steve,Boer,22],iup2 quality]
Process finished with exit code 0
12.10. Script [classes_10]: the [__dict__] property
The [classes_10] script introduces the [__dict__] property, which we will use frequently later on:
# configure the application
import config
config = config.configure()
# The syspath is configured—we can now import modules
from myclasses import Student
# ---------------------------------- main
# Create a student
student = Student("Steve", "Boer", 22, "iup2 quality")
# dictionary of properties
print(student.__dict__)
Comments
- lines 1-4: the application is set up;
- line 7: the [Student] class is imported;
- line 11: instantiation of a student;
- line 13: use of the predefined method [__dict__] (two underscores before and after the identifier);
The results are as follows:
- line 2, we obtain a dictionary whose keys are the object’s properties prefixed by the name of the class to which they belong. We will use this dictionary to create a bridge between the object and the dictionary;