"See https://docs.python.org/3/tutorial/classes.html\n",
"\n",
"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."
]
},
...
...
@@ -112,7 +114,12 @@
"source": [
"The first line states that instances of the class `AdultBee` will be Python objects. The class `AdultBee` inherits from the class `object`.\n",
"\n",
"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`.\n"
"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`.\n",
"\n",
"- `kind` and `limit_age` are **class variables**,\n",
"- `mother`, `father`, `tag` and `living` are **instance variables**,\n",
"- `__init__` is a **\"special\" method**,\n",
"- `act_and_envolve` and `die` are **methods**.\n"
]
},
{
...
...
@@ -205,8 +212,8 @@
}
},
"source": [
"### Warning for C++ users\n",
"`__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."
"### Warning for C++ developers\n",
"`__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.\n"
]
},
{
...
...
@@ -280,7 +287,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"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`"
"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`"
]
},
{
...
...
@@ -466,7 +473,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 10,
"metadata": {},
"outputs": [
{
...
...
@@ -484,6 +491,7 @@
"\n",
"class Student(Person):\n",
" role = 'student'\n",
"\n",
" @classmethod\n",
" def show_role(cls):\n",
" print('The role for this class is ' +\n",
...
...
@@ -500,14 +508,14 @@
}
},
"source": [
"### \"Static methods\"\n",
"### \"Static methods\" and class variables\n",
"\n",
"For some situation we don't even need to explicitly use the class or an instance. We can use static methods."
]
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
...
...
@@ -525,7 +533,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 12,
"metadata": {},
"outputs": [
{
...
...
@@ -537,297 +545,13 @@
}
],
"source": [
"p1 = IdPerson('Pierre')\n",
"p1 = IdPerson('Maya')\n",
"p2 = IdPerson('Cyrille')\n",
"p3 = IdPerson('Olivier')\n",
"p4 = IdPerson('Franck')\n",
"\n",
"IdPerson.show_nb_person()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Do it yourself\n",
"At the end of the last presentation, we asked the following question about our weather stations measuring wind and temperature:\n",
"> What if we now have a weather station that also measure humidity ? Do we have to rewrite everything ?\n",
"\n",
"Give your own answer by doing the following tasks:\n",
"- Write a class `HumidWeatherStation` inheriting `WeatherStation` (code reproduced below) to implement a new attribute to store the humidity measurements.\n",
"- Write a function `humidity_at_max_temp` that returns the value of the humidity at the maximal temperature. Use the fact that `HumidWeatherStation` inherits from `WeatherStation` and therefore can use the method `arg_max_temp` previously implemented !\n",
"- *Advanced*: Overloadg the methods of `WeatherStation` to take humidity into account when computing percieved temperatures `Tp`. For simplicity, we will assume that `Tp = Tw + 5*humidity` with `Tw` the temperature computed with the wind chill effect.\n",
"- *Advanced*: Write tests for this new class"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"# Code to use for the DIY\n",
"class WeatherStation(object):\n",
" \"\"\" A weather station that holds wind and temperature \"\"\"\n",
"\n",
" def __init__(self, wind, temperature):\n",
" \"\"\" initialize the weather station. \n",
" Precondition: wind and temperature must have the same length\n",
" :param wind: any ordered iterable\n",
" :param temperature: any ordered iterable\"\"\"\n",
" self.wind = [x for x in wind]\n",
" self.temp = [x for x in temperature]\n",
" if len(self.wind) != len(self.temp):\n",
" raise ValueError(\n",
" \"wind and temperature should have the same size\"\n",
" )\n",
"\n",
" def perceived_temp(self, index):\n",
" \"\"\" computes the perceived temp according to \n",
" https://en.wikipedia.org/wiki/Wind_chill\n",
" i.e. The standard Wind Chill formula for Environment Canada is: \n",
"# As we overloaded perceived_temp, the rest of the class features work with the new behaviour\n",
"print(singapore.perceived_temps())\n",
"print(singapore.max_temp(perceived=True))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this case, we used inheritance to create the new object (`HumidWeatherStation`) to:\n",
"\n",
"- Add new features (`humidity_at_max_temp`) to an existing object without rewritting the common features\n",
"- Define new behaviours for features already present (`perceived_temp`) that integrate well with the structure in place\n",
"\n",
"For such needs, think about inheritance."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Do it yourself (advanced)\n",
"\n",
"- Write a class named `MovingObject` that has at least one attribute `position` and implements two functions `start()` and `stop()`. These 2 functions could just print for example \"starting\" and \"stoping\" (or they could do more funny things)...\n",
"\n",
"- Write another class named `Car` that inherits `MovingObject` and overload start and stop functions.\n",
"\n",
"- Use the classes (instantiate objects and use them).\n",
"\n",
"- Options : add a static method in Car class that returns the number of cars."
**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)
# Object-oriented programming: inheritance
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.
%% Cell type:markdown id: tags:
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.
# Concepts
## Object
An object is an entity that has state and behavior. Objects are the basic elements of object oriented system.
## Class
Classes are "families" of objects. A class describes how are organized its objects and how they work.
%% Cell type:markdown id: tags:
## Example of problem: Simulate populations of honeybees
Each type of adult bee have different characteristics, behaviors, activities and tasks.
%% Cell type:markdown id: tags:
## Class definition
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.
%% Cell type:code id: tags:
``` python
classAdultBee(object):
kind=None
limit_age=50.
def__init__(self,mother,father,tag=None):
self.mother=mother
self.father=father
iftagisNone:
self.tag=(self.mother.tag,self.father.tag)
else:
self.tag=tag
# age in days
self.age=0.
self.living=True
defact_and_envolve(self,duration=1):
"""Time stepping method"""
self.age+=duration
ifself.age>self.limit_age:
self.die()
defdie(self):
self.living=False
```
%% Cell type:markdown id: tags:
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**.
%% Cell type:markdown id: tags:
### Instantiation of a class
We can create objects `AdultBee`. We say that we *instantiate objects of the class `AdultBee`*.
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.
### Protected methods and attributes (no notion of public, private, virtual as in C++)
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.
%% Cell type:markdown id: tags:
### Warning for C++ users
`__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.
### Warning for C++ developers
`__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.
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`
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`
%% Cell type:code id: tags:
``` python
classQueenBee(AdultBee):
kind='queen'
limit_age=4*365
defact_and_envolve(self,duration=1):
"""Time stepping method"""
super().act_and_envolve(duration)
print('I am the Queen!')
classWorkerBee(AdultBee):
kind='worker'
# actually it depends on the season...
limit_age=6*7
defdance(self):
print('I communicate by dancing')
defmake_honey(self):
print('I make honey')
```
%% Cell type:markdown id: tags:
- The methods that are not rewritten are automatically inherited from the parent class.
- The methods that are rewritten are completely replaced. To use the method of the parent class, it has to be called explicitly (for example with the `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`.
%% Cell type:markdown id: tags:
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`.
All the methods that star with the prefix `__` are inherited from the class `object`. All classes in Python3 inherit from `object`.
%% Cell type:code id: tags:
``` python
queen.act_and_envolve()
```
%%%% Output: stream
I am the Queen!
%% Cell type:markdown id: tags:
### `super` function
We have used the function `super()` like this to call a function of the parent class:
```python
super().act_and_envolve(duration)
```
Remark: the python 2 syntax was more complicated. We would have to write:
```python
super(QueenBee,self).act_and_envolve(duration)
```
Remark: we can also call the method explicitly:
```
AdultBee.act_and_envolve(self, duration)
```
%% Cell type:markdown id: tags:
## Remark: the exceptions are classes...
We can define our own exceptions classes inheriting from an exception class.
%% Cell type:code id: tags:
``` python
classMyValueError(ValueError):
pass
defmyfunc():
raiseMyValueError('oops')
try:
myfunc()
exceptOSError:
print('An OSError was raised')
exceptValueError:
print('A ValueError was raised')
```
%%%% Output: stream
A ValueError was raised
%% Cell type:markdown id: tags:
## Static methods and class methods (advanced)
%% Cell type:markdown id: tags:
### "Class methods"
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`:
%% Cell type:code id: tags:
``` python
classPerson(object):
def__init__(self):
pass
classStudent(Person):
role='student'
@classmethod
defshow_role(cls):
print('The role for this class is '+
cls.role+'.')
Student.show_role()
```
%%%% Output: stream
The role for this class is student.
%% Cell type:markdown id: tags:
### "Static methods"
### "Static methods" and class variables
For some situation we don't even need to explicitly use the class or an instance. We can use static methods.
%% Cell type:code id: tags:
``` python
classIdPerson(Person):
count=0
def__init__(self,name):
self.name=name
self.id=IdPerson.count
IdPerson.count+=1
@staticmethod
defshow_nb_person():
print('Number of persons created: ',IdPerson.count)
```
%% Cell type:code id: tags:
``` python
p1=IdPerson('Pierre')
p1=IdPerson('Maya')
p2=IdPerson('Cyrille')
p3=IdPerson('Olivier')
p4=IdPerson('Franck')
IdPerson.show_nb_person()
```
%%%% Output: stream
Number of persons created: 4
%% Cell type:markdown id: tags:
## Do it yourself
At the end of the last presentation, we asked the following question about our weather stations measuring wind and temperature:
> What if we now have a weather station that also measure humidity ? Do we have to rewrite everything ?
Give your own answer by doing the following tasks:
- Write a class `HumidWeatherStation` inheriting `WeatherStation` (code reproduced below) to implement a new attribute to store the humidity measurements.
- Write a function `humidity_at_max_temp` that returns the value of the humidity at the maximal temperature. Use the fact that `HumidWeatherStation` inherits from `WeatherStation` and therefore can use the method `arg_max_temp` previously implemented !
-*Advanced*: Overloadg the methods of `WeatherStation` to take humidity into account when computing percieved temperatures `Tp`. For simplicity, we will assume that `Tp = Tw + 5*humidity` with `Tw` the temperature computed with the wind chill effect.
-*Advanced*: Write tests for this new class
%% Cell type:code id: tags:
``` python
# Code to use for the DIY
classWeatherStation(object):
""" A weather station that holds wind and temperature """
def__init__(self,wind,temperature):
""" initialize the weather station.
Precondition: wind and temperature must have the same length
:param wind: any ordered iterable
:param temperature: any ordered iterable"""
self.wind=[xforxinwind]
self.temp=[xforxintemperature]
iflen(self.wind)!=len(self.temp):
raiseValueError(
"wind and temperature should have the same size"
)
defperceived_temp(self,index):
""" computes the perceived temp according to
https://en.wikipedia.org/wiki/Wind_chill
i.e. The standard Wind Chill formula for Environment Canada is: