Skip to content

Commit ccfcacf

Browse files
author
Don Johnson
committed
Merge remote-tracking branch 'upstream/master'
2 parents 4c5251d + 7739094 commit ccfcacf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+837
-769
lines changed

.coveragerc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[report]
2+
exclude_lines =
3+
pragma: no cover
4+
# Don't complain if tests don't hit defensive assertion code:
5+
# See: https://stackoverflow.com/a/9212387/3407256
6+
raise NotImplementedError

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
__pycache__
22
*.pyc
3+
.idea

.travis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ sudo: false
66

77
python:
88
- "2.7"
9-
- "3.3"
109
- "3.4"
1110
- "3.5"
1211
- "3.6"
@@ -18,16 +17,15 @@ cache:
1817
- pip
1918

2019
install:
21-
- travis_retry pip install -q coveralls codecov
20+
- travis_retry pip install -q coveralls codecov six
2221
- pip install flake8 # eventually worth
2322

2423
script:
2524
# Run tests
2625
- PYTHONPATH=. nosetests -s -v --with-doctest --with-cov --cover-package . --logging-level=INFO -v .
2726
# Actually run all the scripts, contributing to coverage
2827
- PYTHONPATH=. ./run_all.sh
29-
# for now failure in flaking is ignored
30-
- flake8 *py || echo "PEP8 the code"
28+
- flake8 *py
3129

3230
after_success:
3331
- coveralls

README.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@ python-patterns
33

44
A collection of design patterns and idioms in Python.
55

6-
When an implementation is added or modified, be sure to update this file and
7-
rerun `append_output.sh` (eg. ./append_output.sh borg.py) to keep the output
8-
comments at the bottom up to date.
9-
10-
Current Patterns:
6+
Current Patterns
7+
----------------
118

129
__Creational Patterns__:
1310

@@ -28,7 +25,7 @@ __Structural Patterns__:
2825
| [3-tier](structural/3-tier.py) | data<->business logic<->presentation separation (strict relationships) |
2926
| [adapter](structural/adapter.py) | adapt one interface to another using a white-list |
3027
| [bridge](structural/bridge.py) | a client-provider middleman to soften interface changes |
31-
| [composite](structural/composite.py) | encapsulate and provide access to a number of different objects |
28+
| [composite](structural/composite.py) | lets clients treat individual objects and compositions uniformly |
3229
| [decorator](structural/decorator.py) | wrap functionality with other functionality in order to affect outputs |
3330
| [facade](structural/facade.py) | use one class as an API to a number of others |
3431
| [flyweight](structural/flyweight.py) | transparently reuse existing instances of objects with similar/identical state |
@@ -75,3 +72,29 @@ __Others__:
7572
| [blackboard](other/blackboard.py) | architectural model, assemble different sub-system knowledge to build a solution, AI approach - non gang of four pattern |
7673
| [graph_search](other/graph_search.py) | graphing algorithms - non gang of four pattern |
7774
| [hsm](other/hsm/hsm.py) | hierarchical state machine - non gang of four pattern |
75+
76+
77+
Contributing
78+
------------
79+
When an implementation is added or modified, please review the following guidelines:
80+
81+
##### Output
82+
All files with example patterns have `### OUTPUT ###` section at the bottom.
83+
84+
Run `append_output.sh` (e.g. `./append_output.sh borg.py`) to generate/update it.
85+
86+
##### Docstrings
87+
Add module level description in form of a docstring with links to corresponding references or other useful information.
88+
89+
[strategy.py](behavioral/strategy.py) has a good example of detailed description,
90+
but sometimes the shorter one as in [template.py](behavioral/template.py) would suffice.
91+
92+
In some cases class-level docstring with doctest would also help (see [adapter.py](structural/adapter.py))
93+
94+
##### Python2/3 compatibility
95+
Try to keep it (discussion is held in [issue #208](https://github.com/faif/python-patterns/issues/208))
96+
- use new style classes (inherit from `object`)
97+
- use `from future import print`
98+
99+
##### Update README
100+
When everything else is done - update corresponding part of README.

behavioral/catalog.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3+
34
"""
45
A class that uses different static function depending of a parameter passed in
56
init. Note the use of a single dictionary instead of multiple conditions
67
"""
7-
__author__ = "Ibrahim Diop <http://ibrahim.zinaria.com>"
8-
__gist__ = "<https://gist.github.com/diopib/7679559>"
8+
9+
__author__ = "Ibrahim Diop <ibrahim@sikilabs.com>"
910

1011

1112
class Catalog(object):
@@ -19,8 +20,7 @@ def __init__(self, param):
1920
# dictionary that will be used to determine which static method is
2021
# to be executed but that will be also used to store possible param
2122
# value
22-
self._static_method_choices = {'param_value_1': self._static_method_1,
23-
'param_value_2': self._static_method_2}
23+
self._static_method_choices = {'param_value_1': self._static_method_1, 'param_value_2': self._static_method_2}
2424

2525
# simple test to validate param value
2626
if param in self._static_method_choices.keys():
@@ -67,8 +67,7 @@ def _instance_method_1(self):
6767
def _instance_method_2(self):
6868
print("Value {}".format(self.x2))
6969

70-
_instance_method_choices = {'param_value_1': _instance_method_1,
71-
'param_value_2': _instance_method_2}
70+
_instance_method_choices = {'param_value_1': _instance_method_1, 'param_value_2': _instance_method_2}
7271

7372
def main_method(self):
7473
"""will execute either _instance_method_1 or _instance_method_2
@@ -103,8 +102,7 @@ def _class_method_1(cls):
103102
def _class_method_2(cls):
104103
print("Value {}".format(cls.x2))
105104

106-
_class_method_choices = {'param_value_1': _class_method_1,
107-
'param_value_2': _class_method_2}
105+
_class_method_choices = {'param_value_1': _class_method_1, 'param_value_2': _class_method_2}
108106

109107
def main_method(self):
110108
"""will execute either _class_method_1 or _class_method_2
@@ -136,8 +134,7 @@ def _static_method_1():
136134
def _static_method_2():
137135
print("executed method 2!")
138136

139-
_static_method_choices = {'param_value_1': _static_method_1,
140-
'param_value_2': _static_method_2}
137+
_static_method_choices = {'param_value_1': _static_method_1, 'param_value_2': _static_method_2}
141138

142139
def main_method(self):
143140
"""will execute either _static_method_1 or _static_method_2
@@ -167,6 +164,7 @@ def main():
167164
test = CatalogStatic('param_value_1')
168165
test.main_method()
169166

167+
170168
if __name__ == "__main__":
171169

172170
main()

behavioral/chain.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3-
"""http://www.dabeaz.com/coroutines/"""
3+
4+
"""
5+
*What is this pattern about?
6+
This pattern aims to decouple the senders of a request from its
7+
receivers. It does this by allowing a request to move through chained
8+
objects until it is handled by an appropriate receiver.
9+
10+
This is useful as it reduces the number of connections between objects,
11+
since the sender does not need explicit knowledge of the handler, and
12+
the receiver won't need to refer to all potential receivers, but keeps
13+
a reference to a single successor.
14+
15+
*References:
16+
http://www.dabeaz.com/coroutines/
17+
18+
*TL;DR80
19+
Allow a request to pass down a chain of objects until an object handles
20+
the request.
21+
"""
422

523
from contextlib import contextmanager
624
import os
725
import sys
826
import time
27+
import abc
928

1029

1130
class Handler(object):
31+
__metaclass__ = abc.ABCMeta
1232

1333
def __init__(self, successor=None):
1434
self._successor = successor
@@ -18,46 +38,41 @@ def handle(self, request):
1838
if not res:
1939
self._successor.handle(request)
2040

41+
@abc.abstractmethod
2142
def _handle(self, request):
2243
raise NotImplementedError('Must provide implementation in subclass.')
2344

2445

2546
class ConcreteHandler1(Handler):
26-
2747
def _handle(self, request):
2848
if 0 < request <= 10:
2949
print('request {} handled in handler 1'.format(request))
3050
return True
3151

3252

3353
class ConcreteHandler2(Handler):
34-
3554
def _handle(self, request):
3655
if 10 < request <= 20:
3756
print('request {} handled in handler 2'.format(request))
3857
return True
3958

4059

4160
class ConcreteHandler3(Handler):
42-
4361
def _handle(self, request):
4462
if 20 < request <= 30:
4563
print('request {} handled in handler 3'.format(request))
4664
return True
4765

4866

4967
class DefaultHandler(Handler):
50-
5168
def _handle(self, request):
5269
print('end of chain, no handler for {}'.format(request))
5370
return True
5471

5572

5673
class Client(object):
57-
5874
def __init__(self):
59-
self.handler = ConcreteHandler1(
60-
ConcreteHandler3(ConcreteHandler2(DefaultHandler())))
75+
self.handler = ConcreteHandler1(ConcreteHandler3(ConcreteHandler2(DefaultHandler())))
6176

6277
def delegate(self, requests):
6378
for request in requests:
@@ -69,6 +84,7 @@ def start(*args, **kwargs):
6984
cr = func(*args, **kwargs)
7085
next(cr)
7186
return cr
87+
7288
return start
7389

7490

@@ -110,7 +126,6 @@ def default_coroutine():
110126

111127

112128
class ClientCoroutine:
113-
114129
def __init__(self):
115130
self.target = coroutine1(coroutine3(coroutine2(default_coroutine())))
116131

@@ -120,12 +135,12 @@ def delegate(self, requests):
120135

121136

122137
def timeit(func):
123-
124138
def count(*args, **kwargs):
125139
start = time.time()
126140
res = func(*args, **kwargs)
127141
count._time = time.time() - start
128142
return res
143+
129144
return count
130145

131146

@@ -153,7 +168,7 @@ def suppress_stdout():
153168
with suppress_stdout():
154169
client1_delegate(requests)
155170
client2_delegate(requests)
156-
# lets check what is faster
171+
# lets check which is faster
157172
print(client1_delegate._time, client2_delegate._time)
158173

159174
### OUTPUT ###

behavioral/chaining_method.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66

77
class Person(object):
8-
98
def __init__(self, name, action):
109
self.name = name
1110
self.action = action
@@ -16,7 +15,6 @@ def do_action(self):
1615

1716

1817
class Action(object):
19-
2018
def __init__(self, name):
2119
self.name = name
2220

@@ -27,6 +25,7 @@ def amount(self, val):
2725
def stop(self):
2826
print('then stop')
2927

28+
3029
if __name__ == '__main__':
3130

3231
move = Action('move')

behavioral/command.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3+
4+
"""
5+
*TL;DR80
6+
Encapsulates all information needed to perform an action or trigger an event.
7+
"""
8+
39
from __future__ import print_function
410
import os
511
from os.path import lexists
612

713

814
class MoveFileCommand(object):
9-
1015
def __init__(self, src, dest):
1116
self.src = src
1217
self.dest = dest
@@ -30,9 +35,9 @@ def main():
3035
command_stack.append(MoveFileCommand('bar.txt', 'baz.txt'))
3136

3237
# verify that none of the target files exist
33-
assert(not lexists("foo.txt"))
34-
assert(not lexists("bar.txt"))
35-
assert(not lexists("baz.txt"))
38+
assert not lexists("foo.txt")
39+
assert not lexists("bar.txt")
40+
assert not lexists("baz.txt")
3641
try:
3742
with open("foo.txt", "w"): # Creating the file
3843
pass
@@ -47,6 +52,7 @@ def main():
4752
finally:
4853
os.unlink("foo.txt")
4954

55+
5056
if __name__ == "__main__":
5157
main()
5258

behavioral/iterator.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
3+
34
"""
45
http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/
56
Implementation of the iterator pattern with a generator
7+
8+
*TL;DR80
9+
Traverses a container and accesses the container's elements.
610
"""
711

812
from __future__ import print_function
@@ -11,11 +15,10 @@
1115
def count_to(count):
1216
"""Counts by word numbers, up to a maximum of five"""
1317
numbers = ["one", "two", "three", "four", "five"]
14-
# enumerate() returns a tuple containing a count (from start which
15-
# defaults to 0) and the values obtained from iterating over sequence
16-
for pos, number in zip(range(count), numbers):
18+
for number in numbers[:count]:
1719
yield number
1820

21+
1922
# Test the generator
2023
count_to_two = lambda: count_to(2)
2124
count_to_five = lambda: count_to(5)

0 commit comments

Comments
 (0)