Day 1: Adaptability & Extendibility
* Modularity in python
* DRY
* SOLID
* Design Patterns
Day 2: Testing, Documentation & Readability
* Packaging
* Unit Tests
* Doctest
* Sphinx
* GitLab Integration
Day 3: Scalability & Performance
* Ahmdal's law
* Data Structures
* Dont's
* Profiling & Benchmarking
* Parallelisation with `joblib` and `mpi4py`
Goals:
conda, pip, etc.my_module.pyclass MyClassdef my_add(x, y)
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
-- A. Hunt & D.Thomas in The pragmatic Programmer
Why?
Further in a Team: - Gives rise to unmotivated local adaptions of repeated code -> clutter
Thus:
- Already the first <Ctrl>-<v> usually is the bad one!
- Early (but not premature!) abstractions
1 def check_area(b):
2 if (1 / 2 * 3.14 * b * b) < 0.2:
3 return True
4 elif (1 / 2 * 3.14 * b * b) < 0.8:
5 return False
6 elif (1 / 2 * 3.14 * b * b) < 2.0:
7 return True
8 else:
9 return False
This code is not DRY.
1 class ProductInterface:
2
3 def display_price(self):
4 raise NotImplementedError(
5 "You forgot to implement the displayPrice method")
6
7 class PlasticDuck(ProductInterface):
8
9 def __init__(self, price):
10 self._price = price
11
12 def display_price(self):
13 print("The price of this plastic"
14 "duck is {} euros!".format(self._price))
15
16 plast_duck = PlasticDuck(2)
17 plast_duck.displayPrice()
Think about the fact that the word 'price' appears 9! times
This code is DRY.
1 class CsvValidation:
2
3 def validate_product(self, product : dict):
4 if 'color' not in product:
5 raise RuntimeError(
6 'Import fail: the product attribute color is missing')
7
8 if 'size' not in product:
9 raise RuntimeError(
10 'Import fail: the product attribute size is missing')
11
12 if 'type' not in product:
13 raise RuntimeError(
14 'Import fail: the product attribute type is missing')
This code is DRY. But there is some code duplication.
1 class CsvValidation:
2
3 def validate_product(self, product : dict):
4 for property in ['color', 'size', 'type']:
5 if property not in product:
6 raise RuntimeError(
7 'Import fail: the product attribute {} is missing'.format(property))
This code is DRY. No duplications - but more difficult to understand! There is a tradeoff.
Tasks: Read a bitmap image
Principles of object-oriented design by Robert C. Martin
https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd
There should never be more than one reason for a class to change.
-- Robert C. Martin in "SRP: The Single Responsibility Principle"
A class (method, module) should have exactly one purpose
Separation of Concerns
Avoid the "egg-laying wool-milk-sow":
1 def percentage_of_word_in_localfile(search, file):
2 search = search.lower()
3 content = open(file, "r").read()
4 words = content.split()
5 number_of_words = len(words)
6 occurrences = 0
7 for word in words:
8 if word.lower() == search:
9 occurrences += 1
10 return occurrences / number_of_words
What do you think of this?
1 def read_localfile(file):
2 return open(file, "r").read()
3
4 def number_of_words(content):
5 return len(content.split())
6
7 def count_word_occurrences(word, content):
8 counter = 0
9 for e in content.split():
10 if word.lower() == e.lower():
11 counter += 1
12 return counter
13
14 def percentage_of_word(word, content):
15 total_words = number_of_words(content)
16 word_occurrences = count_word_occurrences(word, content)
17 return word_occurrences/total_words
18
19 def percentage_of_word_in_localfile(word, file):
20 content = read_localfile(file)
21 return percentage_of_word(word, content)
Modules should be both open (for extension) and closed (for modification).
- Bertrand Meyer in Object Oriented Software Construction

1 class Animal:
2 def __init__(self, name: str):
3 self.name = name
4
5 def get_name(self) -> str:
6 pass
7
8 def animal_sound(animals: list):
9 for animal in animals:
10 if animal.name == 'lion':
11 print('roar')
12
13 elif animal.name == 'mouse':
14 print('squeak')
15
16 # client code
17 animals = [
18 Animal('lion'),
19 Animal('mouse')
20 ]
21
22 animal_sound(animals)
1 class Animal:
2
3 ... # __init__ and get_name same as before
4
5 def make_sound(self):
6 pass
7
8 class Lion(Animal):
9 def make_sound(self):
10 return 'roar'
11
12 class Mouse(Animal):
13 def make_sound(self):
14 return 'squeak'
15
16 def animal_sound(animals: list):
17 for animal in animals:
18 print(animal.make_sound())
19
20 # client code
21 animals = [Lion('lion'), Mouse('mouse')]
22 animal_sound(animals)
Subtype Requirement: Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
-- Liskov, B. H. & Wing, J. M. in A behavioral notion of subtyping. (1994)
In other words: A base-class must be substitutable by its sub-class (anywhere!)
Implications on method parameters and return types


Clients should not be forced to depend on methods that they do not use.
-- Robert C. Martin in "Agile Software Development: Principles, Patterns, and Practices"
A simple approach:
1 class Interface(object):
2
3 def method1(self):
4 raise NotImplementedError("this is not implemented") # -> provoke runtime error
5
6 class Concrete(Interface):
7
8 def method1(self):
9 print("method 1 is implemented here")
10 # ...
abcabc == "Abstract base class"
A better approach assuring implementation before any execution starts:
1 import abc
2
3 class Interface(object, metaclass=abc.ABCMeta):
4
5 @abc.abstractmethod
6 def method1(self):
7 #pass
8
9 class Concrete(Interface):
10
11 def method1(self):
12 print("method 1 is implemented here")
13 # ...
Tasks:
1 class Lamp(object):
2 def __init__(self):
3 self._is_shining = False
4
5 def turn_on(self):
6 self._is_shining = True
7
8 def turn_off(self):
9 self._is_shining = False
10
11 class Switch(object):
12 def __init__(self, lamp: Lamp):
13 self._lamp = lamp
14 self._pressed = False
15
16 def press(self):
17 self._pressed = not self._pressed
18 if self._pressed:
19 self._lamp.turn_on() # <---- PROBLEM IS HERE
20 else:
21 self._lamp.turn_off() # <---- AND HERE
1 #### module: lamp.py
2 import switch
3 class Lamp(Switchable):
4 def __init__(self):
5 self._is_shining = False
6
7 def turn_on(self):
8 self._is_shining = True
9
10 def turn_off(self):
11 self._is_shining = False
1 #### module: switch.py
2 import abc
3 import lamp
4 class Switchable(object, metaclass=abc.ABCMeta):
5 @abc.abstractmethod
6 def turn_on():
7 pass
8
9 @abc.abstractmethod
10 def turn_off():
11 pass
12
13 class Switch(object):
14 def __init__(self, switched: Switchable):
15 self._switched = switched
16 self._pressed = False
17
18 def press(self):
19 self._pressed = not self._pressed
20 if self._pressed:
21 self._switched.turn_on() # <---- No Ref to concrete implementation
22 else:
23 self._switched.turn_off() # <---- No Ref to concrete implementation
Tasks:
We will write a smoother (filter) that replaces each pixel value with the arithmetic
mean of itself and its eight direct neighbors. We directly design it as an implementation of
a more generic FilterInterface
Bitmap (signature of the filter method)
Behavioral
Structural
Creational
1 import abc
2
3 class AbstractAlgorithm(object, metaclass=abc.ABCMeta):
4 """
5 Defines an algorithm template, sub-classes implement part of it.
6 """
7
8 def template_method(self) -> None:
9 """
10 Algorithm skeleton.
11 """
12 self.base_operation()
13 self.required_operation1()
14 self.hook()
15 self.required_operation2()
16
17 # These operations already have implementations.
18
19 def base_operation(self) -> None:
20 print("AbstractAlgorithm says: I am doing some alway necessary preparation work")
1 # These operations have to be implemented in subclasses.
2
3 @abc.abstractmethod
4 def required_operation1(self) -> None:
5 pass
6
7 @abc.abstractmethod
8 def required_operation2(self) -> None:
9 pass
10
11 # These are "hooks." Subclasses may override them optionally. Adds flexibility
12
13 def hook(self) -> None:
14 pass
1 class ConcreteAlgo1(AbstractAlgorithm):
2 """
3 needs to implement all abstract operations of the base
4 class. They can also override some operations with a default implementation.
5 """
6
7 def required_operation1(self) -> None:
8 print("ConcreteAlgo1 says: Implemented Operation1")
9
10 def required_operation2(self) -> None:
11 print("ConcreteAlgo1 says: Implemented Operation2")
12
13 class ConcreteAlgo2(AbstractAlgorithm):
14 # accordingly
1 def client_code(abstract_class: AbstractAlgorithm) -> None:
2 """
3 The client code calls the template method to execute the algorithm. Note
4 that we operate with the interface
5 """
6 # ...
7 abstract_class.template_method()
8 # ...
9
10
11 if __name__ == "__main__":
12 print("Same client code can work with different subclasses:")
13 client_code(ConcreteAlgo1()) # Or: client_code(ConcreteAlgo2())
14 print("")
if elif blocksTasks: Study a simple factory
Triangle type of shapeclass in class (Factory). How can we avoid it?Shape sub-class? 1 class Singleton:
2 __instance = None # NOTE: class variable!
3
4 @staticmethod
5 def get_instance():
6 if Singleton.__instance is None:
7 Singleton() # first time initialization
8 return Singleton.__instance
9
10 def __init__(self):
11 if Singleton.__instance is not None:
12 # in python we have to raise:
13 raise Exception("Do not call the constructor!! This is a Singleton")
14 else:
15 Singleton.__instance = self
__iter__ and __next__ magic methodsTasks: Study a python iterator example
ExceptionThanks!
| Table of Contents | t |
|---|---|
| Exposé | ESC |
| Presenter View | p |
| Source Files | s |
| Slide Numbers | n |
| Toggle screen blanking | b |
| Show/hide next slide | c |
| Notes | 2 |
| Help | h |