A training to acquire strong basis in Python to use it efficiently
Pierre Augier (LEGI), Cyrille Bonamy (LEGI), Eric Maldonado (Irstea), Franck Thollard (ISTerre), Christophe Picard (LJK), Loïc Huder (ISTerre)
See https://docs.python.org/3/tutorial/classes.html
Python is also a Object-oriented language. Object-oriented programming is very useful and used in many libraries so it is very useful to understand how the simple Object-oriented mechanisms work in Python.
For some problems, Object-oriented programming is a very efficient paradigm. Many libraries use it so it is necessary to understand how it works in Python to really use these libraries.
An object is an entity that has state and behavior. Objects are the basic elements of object oriented system.
Classes are "families" of objects. A class describes how are organized its objects and how they work.
A class is a logical entity which contains attributes and have some behavior. When a class is defined, we need to specify its name, its dependencies (the class inherits from at least one other class), its attributes and its methods.
class AdultBee(object):
kind = None
limit_age = 50.0
def __init__(self, mother, father, tag=None):
self.mother = mother
self.father = father
if tag is None:
self.tag = (self.mother.tag, self.father.tag)
else:
self.tag = tag
# age in days
self.age = 0.0
self.living = True
def act_and_envolve(self, duration=1):
"""Time stepping method"""
self.age += duration
if self.age > self.limit_age:
self.die()
def die(self):
self.living = False
The first line states that instances of the class AdultBee
will be Python objects. The class AdultBee
inherits from the class object
.
The first line could have been replaced by the less explicit class AdultBee:
. Actually, in Python 3, the classes that do not inherit explicitly from another class automatically inherit from the class object
.
kind
and limit_age
are class variables,mother
, father
, tag
and living
are instance variables,__init__
is a "special" method,act_and_envolve
and die
are methods.We can create objects AdultBee
. We say that we instantiate objects of the class AdultBee
.
bee0 = AdultBee("mother0", "father0", tag="0")
bee1 = AdultBee("mother1", "father1", tag="1")
bee_second_generation0 = AdultBee(bee0, bee1)
bee_second_generation1 = AdultBee(bee0, bee1)
bee_third_generation = AdultBee(bee_second_generation0, bee_second_generation1)
bees = [
bee0,
bee1,
bee_second_generation0,
bee_second_generation1,
bee_third_generation,
]
In this example, we manipulate the notions of class, object (instance), abstraction and encapsulation...
bee2 = AdultBee("mother2", "father2", tag="2")
__init__
is automatically called like this (for bee0
): AdultBee.__init__(bee0, 'mother0', 'father0', tag='0')
In Python, methods or attributes that starts with __
are "special". Such methods and attributes are used internally by Python. They define how the class works internally.
For example the method __init__
is automatically called by Python during the instantiation of an object with the arguments used for the instantiation.
Attributes and methods whose names start with _
are said to be "protected". It is just a name convention. It tells the users that they should not use these objects directly.
__init__
is NOT the constructor. The real constructor is __new__
. This method is called to really create the Python object and it really returns the object. Usually, we do not need to redefine it. Python __init__
and C++ constructor have to be used in very different ways. Only the __init__
method of the class is automatically called by Python during the instantiation. Nothing like the Russian dolls C++ mechanism. All methods in Python are effectively virtual.
print("second generation:", bee_second_generation0.tag)
print("third generation; ", bee_third_generation.tag)
print("warning: consanguinity...")
second generation: ('0', '1') third generation; (('0', '1'), ('0', '1')) warning: consanguinity...
# 100 days
for i in range(100):
for bee in bees:
bee.act_and_envolve()
bees = [bee for bee in bees if bee.living]
if len(bees) == 0:
print("After 100 days, no more bees :-(")
After 100 days, no more bees :-(
To indicate the dependency to another class, we put the parent class in parenthesis in the definition. The class QueenBee
inherits from the class AdultBee
class QueenBee(AdultBee):
kind = "queen"
limit_age = 4 * 365
def act_and_envolve(self, duration=1):
"""Time stepping method"""
super().act_and_envolve(duration)
print("I am the Queen!")
class WorkerBee(AdultBee):
kind = "worker"
# actually it depends on the season...
limit_age = 6 * 7
def dance(self):
print("I communicate by dancing")
def make_honey(self):
print("I make honey")
super()
function).We see that we do not need to rewrite all methods. For example the method __init__
of the class QueenBee
is the method __init__
of the class AdultBee
.
The class AdultBee
that we defined is also derived from a more generic class that is called object
. Let's check the content of the class QueenBee
.
queen = QueenBee("mother0", "father0", tag="0")
print(dir(queen))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'act_and_envolve', 'age', 'die', 'father', 'kind', 'limit_age', 'living', 'mother', 'tag']
All the methods that star with the prefix __
are inherited from the class object
. All classes in Python3 inherit from object
.
queen.act_and_envolve()
I am the Queen!
super
function¶We have used the function super()
like this to call a function of the parent class:
super().act_and_envolve(duration)
Remark: the python 2 syntax was more complicated. We would have to write:
super(QueenBee, self).act_and_envolve(duration)
Remark: we can also call the method explicitly:
AdultBee.act_and_envolve(self, duration)
We can define our own exceptions classes inheriting from an exception class.
class MyValueError(ValueError):
pass
def myfunc():
raise MyValueError("oops")
try:
myfunc()
except OSError:
print("An OSError was raised")
except ValueError:
print("A ValueError was raised")
A ValueError was raised
When we simply define a method in a class, it is a instance method, i.e. the first argument of the method (self
) points toward the instance used to call the method. This is the normal and most common mechanism.
We could also define methods that work for the class using the decorator @classmethod
:
class Person(object):
def __init__(self):
pass
class Student(Person):
role = "student"
@classmethod
def show_role(cls):
print("The role for this class is " + cls.role + ".")
Student.show_role()
The role for this class is student.
For some situation we don't even need to explicitly use the class or an instance. We can use static methods.
class IdPerson(Person):
count = 0
def __init__(self, name):
self.name = name
self.id = IdPerson.count
IdPerson.count += 1
@staticmethod
def show_nb_person():
print("Number of persons created: ", IdPerson.count)
p1 = IdPerson("Maya")
p2 = IdPerson("Cyrille")
p3 = IdPerson("Olivier")
p4 = IdPerson("Franck")
IdPerson.show_nb_person()
Number of persons created: 4