designates my notes. / designates important.
I don’t necessarily disagree with what is being said, the patterns themselves are perfectly reasonable, and maybe his style works better on very complex programs, that I don’t write, but it seems like his is adding unnecessary classes and complexity in the name of extensive “decoupling”.
A python talk I liked titled: Stop Writing Classes, is seemingly antithetical to this book’s advice, which is rife with classes. Particularly, if you have a class with two methods, one being init, you probably don’t have a class. Many of the classes in this book fit the bill.
After re-watching Stop Writing Classes, that I agree very much with, presented by a python core developer, I think this author seems to be trying to force python into Java-like object oriented box. The book does, in fact, mention Java-like interfaces several times.
Personally I prefer more functional programming to OOP, but that doesn’t make one better or worse. And, while I wasn’t a huge fan of this book, it was interesting, a different perspective than my normal one, and, if nothing else, it was a breeze to read.
My biggest pet peeve comes toward the end of the book when the author talks about constantly upgrading you code. Adding new features. Maybe I am getting to be a curmudgeon, but I prefer FEWER updates (security aside).
You should build your programs in such a way that you can constantly swap out bits of the program without changing any other part of the code. Every period—depending on the size and scope of your project, this might mean a year or five years or six months -you want to have cycled out all the old code and replaced it with better, more elegant, and more efficient code.
Let me repeat that last line:
…five years or six months -you want to have cycled out all the old code and replaced it with better, more elegant, and more efficient code.
While I don’t agree with much of the book, this part here is the worst. Constantly updating your code is the bane of software, not the holy grail. It will lead to bloat and bugs. Code should be updated as little as possible. Write it right, IMHO.
How often is grep updated? sed? awk? And they are great tools, timeless. How about Firefox? Every few weeks? And it is a bloated POS. Good code should move towards being static, despite what the buzz is about dynamic, constantly changing. As far as I am concerned, constantly updated code exists primarily to keep developers fed. Imagine any other industry providing “updates”. Oh, I have to bring my car in AGAIN? I need a patch on my refrigerator or my food will spoil!? It wouldn’t fly. Yet in the tech world, shoddy, not-yet-ready code is lauded as dynamic and agile.
Chapter one starts typically, with the obligatory setting up your environment. This seems ridiculous to me, given the level you’d be at if you are learning patterns.
It offers comparisons and some critique of the classic Gang of Four book, which introduced, or at least mainstreamed, the concept of patterns into the programming world.
There is some great advice on learning: go slowly and deliberately, talk to others who are more skilled than you, and code, code, code. Write something, throw it out and do it again if you have to. This will also help you learn that it is sometimes OK to chuck out ten thousand lines if you now see how you can solve the problem in one hundred lines. Don’t think of the ten thousand as wasted, you needed to write them to get to the one hundred.
From here, each chapter goes on to focus on a particular pattern. It is usually introduced with suboptimal code and then, through a series of revisions, if ends up taking the final shape of the pattern in question.
The first pattern covered is the singleton. While there are many uses that this is perfectly fine using, like logging or load balancing, it is really a fancy way to have a global variable. As with all of the patterns and advice in the book, none of them are catch-all; each is a unique tool that should be deployed only when appropriate.
Next is the prototype pattern. The example give is of a real-time strategy game where you need to construct units. It seems to create new units by copying old units, but it is unclear how the first unit is created. It does clearly separate the values, statistics, and abilities, of units into data read from disk, and is not included in the code itself. This seems obvious to me, but what do I know?
The factory is the first one I am not really sold on. I’m missing the benefit over straight inheritance. A @staticmethod that takes a string defining what to construct, or a number of methods, one for each thing you want to construct. You’ll need an if statement in the former and a number of methods in the latter. As I’ll repeat again and again, I could be missing something with this.
With the builder, like the factory, I don’t see the benefit. You still need a method for each part of the, in the example’s case, form. Using several classes, creating objects that reference one another, seems messy. Maybe in a larger code base than the example or I am used to working with (only my little personal programs), but I’m not seeing it jump out at me why this is better than a function to decide what other function will be called, all in one class.
def create(self, field_list):
for field in field_list:
if field["type"] == "text_field":
buildTextField(field)
elif field["type"] == "whatever_field":
buildWhateverField(field):
else:
buildBlahField(field)
def buildTextField(field):
# build text field
return text_field
Again, I am probably missing something, but this is what I would do. I would need to add an elif and a method for building each new field type, but you’d have to do the same with the builder pattern.
The adapter pattern I completely agree with. If you have a library that provides something, but not exactly the way you’d like it, you can create an adapter to handle what you require. You can even pass everything else that you don’t need to modify off to the base library to handle.
Here the open/closed principle is also discussed. Code should be open to extension, but closed to modification. You can use adapters (is this the same as wrapper?) to extend code without have access to, or touching, the source.
I never thought of decorators as a pattern, but it isn’t unreasonable. The pattern of having to do something before or after a number of functions. There is really not much to say about these, but class decorators were something I wasn’t aware of. It mentions Flask as a good source to read to see decorators used interestingly (the routing).
The facade pattern feels much like an interface, a single point, likely a class, that can be seen externally. All the messy business is hidden behind a clean implementation. The guts can be modified or swapped out and the facade should keep on working on the client side without modification.
This one seems reasonable, kind of like an adapter on steroids. I can see it being more valuable if you are building a library or something many other people, who want a simple, clean product.
With the proxy pattern, you want the interface to remain constant, with some actions taking place in the background. Conversely, the adapter pattern is targeted at changing the interface.
the example, hiding the memoization of a Fibonacci generating function, is another example showing how to hide the guts of a program behind simpler interface.
The chain of responsibility is on of the more confusing patterns, but upon sussing it out, I like it. It uses handlers to handle certain kinds or parts of a request.
You can use a dispatcher to create a chain of handlers (functions) and then iterate through them, each iteration will produce an updated request and pass it to the next handler. a catch all handler at the end cleans up the leftovers.
The command pattern tries to decouple commands from execution, creating essentially an execution queue, that gives you an undo stack for free. It feels like a producer-consumer. What I wonder is, why not use an actual queue?
The interpreter pattern hinges on domain specific language. This is one of my least favorite patterns, but is a neat solution if you have some very specific, domain, problem to solve.
The example was a restaurant that had complex rules for discounts. You can use the interpreter to allow the restaurateur to write, in a domain specific language, how the specials will be provided. This DSL is then interpreted by the python program.
Here is another place where I see the author as being class happy. Everything, even the day of week and time become (unnecessarily IMHO) objects.
In contrast, one of my favorite patterns is that of the iterator. At the end of this chapter it says something like: this will lead you into the realm of functional programming. Makes sense that I like this pattern a lot.
The observer pattern is another one I am all on board with. The author seems to be letting up on the classes, showing that the abstract base class isn’t really needed with python’s duck typing. He even says it forces a Java-like interface. Why would I want to force python to be Java-like?
I watched a few videos, to dig deeper, on ABC and python, and my takeaway was, as was one of the presenters, that they aren’t really in the spirit of python, but they are there if you need that specific tool.
The decoupling and loose coupling concepts I am on board with, though I’ve heard a lot of people say that language simply doesn’t come up as much as you’d think, given how drummed into modern CS students they are.
With the state machine, which in general I love, the author goes class crazy again, making every state its own class. Dictionaries would probably work, although it would be problem dependent.
The strategy pattern tries to avoid ’long’ if-else blocks, something the author seems to despise. I wonder what he thinks about case statements?
You can use an executor function initiated with another function that implements the particular strategy you want, at run-time.
I also like this pattern. Though the chapter started out by writing several (unnecessary) classes, it made its way back to only 1 executor function and x strategy functions.
The template method; another pattern I like, when appropriate. The author argues for the use of an abstract base class and I might agree this time. if the classes you are creating have many functions that must be implemented the extra sanity check provided by the abc will be useful. That is to say, if you need to implement a new class, SystemX, down the road, will you remember that it must implement step1 … stepX without forgetting any? the abc forces you to implement all of the require methods.
The visitor pattern might be ok, but the way he implements it: no fucking way. 19 classes to control 5 peripherals with settings for two people. most classes have only init and one other method. No, no, no.
I would use classes to control the peripherals, but I would have 1 class per peripheral (5 in this example), 1 class for the person, 1 class (maybe) for a controller. Person has all the settings that are then broadcast to the peripherals, the controller could mediate between multiple present persons.
State machine where the states are the people that are home. I.e. P1+P2, P1+!P2, !P1+P2, !P1+!P2, would possibly simplify it farther.
Not much to say about model-view-controller, it feels like common sense to me. Anyone who has used Flask or Django will be familiar with this. Really anyone who has a smidgen of coding sense will recognize that separating responsibility of code is more than a reasonable goal.
Last we have the publisher-subscriber, which is really a rehash of the observer. The irony is how the author goes out of his way, through the whole book to decouple everything, but, and this is a perfect example, you can’t totally decouple everything. I think I would go about this a little different, probably approach it from an API standpoint, have the point of contact be the published server where the client subscribers occasionally ping for updates. RSS really.
All in all, I would probably not recommend this book to someone wanting to learn about these patterns. I’d probably call the book intermediate to advanced, but I think the author is forcing python to be Java-like, and I don’t condone that.
In essence, deliberate practice has these components:
Each practice session has a single focus.
The distance (time) between attempt and feedback is as short as possible.
Work on something that you cannot yet do.
Follow in the path of those who have gone before you.
All design patterns deal with these three fundamental questions: 1) What are the elements and how are they created? 2) What are the connections between elements, or what does the structure look like? 3) How do the elements interact, or what does their behavior look like?
Systems thinking.
from third_party import WhatIHave
class ObjectAdapter(object):
def __init__(self, what_i_have):
self.what_i_have = what_i_have
def required_function(self):
return self.what_i_have.provided_function_1()
def __getattr__(self, attr):
# Everything else is handeled by the wrapped object
return getattr(self.what_i_have, attr)
Our new version of the adapter pattern uses composition rather than inheritance to deliver a more Pythonic version of this structural design pattern. Thus, we do not require access to the source code of the interface class we are given. This, in turn, allows us to not violate the open/closed principle proposed by Bertrand Meyer, creator of the Eiffel programming language, and the idea of design by contract.
The open/closed principle states: Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
If the parameters we pass to each provided_function remain the same as those of the required_function, we could create a more generic adapter that no longer needs to know anything about the adaptee; we simply supply it an object and the provided_ function, and we’re done.
This is what the generic implementation of the idea would look like:
class ObjectAdapter(object):
def __init__(self, what_i_have, provided_function):
self.what_i_have = what_i_have
self.required_function = provided_function
def __getattr__(self, attr):
return getattr(self.what_i_have, attr)
def dummy_decorator(f):
def wrap_f():
print("Function to be decorated: ", f.__name__)
print("Nested wrapping function: ", wrap_f.__name__)
return f()
wrap_f.__name__ = f.__name__
wrap_f.__doc__ = wrap_f.__doc__ #i think this should be f.__doc__
return wrap_f
@dummy_decorator
def do_nothing():
print("Inside do_nothing")
if __name__ == "__main__":
print("Wrapped function: ",do_nothing.__name__)
do_nothing()
Now there is no longer a difference between the do_nothing() function and the decorated do_nothing() function.
The Python standard library includes a module that allows retaining the name and doc attributes without setting it ourselves. What makes it even better in the context of this chapter is that the module does so by applying a decorator to the wrapping function.
from functools import wraps
def dummy_decorator(f):
@wraps(f)
def wrap_f():
print("Function to be decorated: ", f.__name__)
print("Nested wrapping function: ", wrap_f.__name__)
return f()
return wrap_f
@dummy_decorator
def do_nothing():
print("Inside do_nothing")
if __name__ == "__main__":
print("Wrapped function: ",do_nothing.__name__)
do_nothing()
import time
from functools import wraps
def profiling_decorator_with_unit(unit):
def profiling_decorator(f):
@wraps(f)
def wrap_f(n):
start_time = time.time()
result = f(n)
end_time = time.time()
if unit == "seconds":
elapsed_time = (end_time - start_time) / 1000
else:
elapsed_time = (end_time - start_time)
print("[Time elapsed for n = {}] {}".format(n, elapsed_time))
return result
return wrap_f
return profiling_decorator
@profiling_decorator_with_unit("seconds")
def fib(n):
print("Inside fib")
if n < 2:
return
fibPrev = 1
fib = 1
for num in range(2, n):
fibPrev, fib = fib, fib + fibPrev
return fib
if __name__ == "__main__":
n = 77
print("Fibonacci number for n = {}: {}".format(n, fib(n)))
import time
class RawCalculator(object):
def fib(self, n):
if n < 2:
return 1
return self.fib(n - 2) + self.fib(n - 1)
def memoize(fn):
__cache = {}
def memoized(*args):
key = (fn.__name__, args)
if key in __cache:
return __cache[key]
__cache[key] = fn(*args)
return __cache[key]
return memoized
class CalculatorProxy(object):
def __init__(self, target):
self.target = target
fib = getattr(self.target, 'fib')
setattr(self.target, 'fib', memoize(fib))
def __getattr__(self, name):
return getattr(self.target, name)
if __name__ == "__main__":
calculator = CalculatorProxy(RawCalculator())
start_time = time.time()
fib_sequence = [calculator.fib(x) for x in range(0, 80)]
end_time = time.time()
print("Calculating the list of {} Fibonacci numbers took {}seconds".format(len(fib_sequence),
end_time - start_time))
Attempt a non-trivial project (one that will require more than 1,000 lines of code to solve) by following these rules:
(1) Only one level of indentation is allowed per method, thus no if (statements in loops or nesting)
(2) You’re not allowed to use the keyword else.
(3) All primitive types and strings must be wrapped in objects— (specifically for the use they are put to)
(4) Collections are first class, and as such require their own objects.
(5) Don’t abbreviate names. If names are too long, you are probably (doing more than one thing in a method or class—don’t do that)
(6) Only one object operator allowed per line, so object.method() is (ok, but object) attribute.method- () is not.
(7) Keep your entities small (packages < 15 objects, classes < 50 lines, - (methods < 5 lines) )
(8) No class may have more than two instance variables.
(9) You are not allowed to use getters, setters, or access properties directly.
Interesting challenges.
class ConcreteObserver(object):
def update(self, observed):
print("Observing: " + observed)
class Observable(object):
def __init__(self):
self.observers = set()
def register(self, observer):
self.observers.add(observer)
def unregister(self, observer):
self.observers.discard(observer)
def unregister_all(self):
self.observers = set()
def update_all(self):
for observer in self.observers:
observer.update(self)
def executor(arg1, arg2, func=None):
if func is None:
return "Strategy not implemented..."
return func(arg1, arg2)
def strategy_addition(arg1, arg2):
return arg1 + arg2
def strategy_subtraction(arg1, arg2):
return arg1 - arg2
def main():
print(executor(4, 6))
print(executor(4, 6, strategy_addition))
print(executor(4, 6, strategy_subtraction))
if __name__ == "__main__":
main()
import abc
class ThirdPartyInteractionTemplate(metaclass=abc.ABCMeta):
def sync_stock_items(self):
self._sync_stock_items_step_1()
self._sync_stock_items_step_2()
self._sync_stock_items_step_3()
self._sync_stock_items_step_4()
def send_transaction(self, transaction):
self._send_transaction(transaction)
@abc.abstractmethod
def _sync_stock_items_step_1(self): pass
@abc.abstractmethod
def _sync_stock_items_step_2(self): pass
@abc.abstractmethod
def _sync_stock_items_step_3(self): pass
@abc.abstractmethod
def _sync_stock_items_step_4(self): pass
@abc.abstractmethod
def _send_transaction(self, transaction): pass
class System1(ThirdPartyInteractionTemplate):
def _sync_stock_items_step_1(self):
print("running stock sync between local and remote system1")
def _sync_stock_items_step_2(self):
print("retrieving remote stock items from system1")
def _sync_stock_items_step_3(self):
print("updating local items")
def _sync_stock_items_step_4(self):
print("sending updates to third party system1")
def _send_transaction(self, transaction):
print("send transaction to system1: {0!r}".format(transaction))
class System2(ThirdPartyInteractionTemplate):
def _sync_stock_items_step_1(self):
print("running stock sync between local and remote system2")
def _sync_stock_items_step_2(self):
print("retrieving remote stock items from system2")
def _sync_stock_items_step_3(self):
print("updating local items")
def _sync_stock_items_step_4(self):
print("sending updates to third party system2")
def _send_transaction(self, transaction):
print("send transaction to system2: {0!r}".format(transaction))
class System3(ThirdPartyInteractionTemplate):
def _sync_stock_items_step_1(self):
print("running stock sync between local and remote system3")
def _sync_stock_items_step_2(self):
print("retrieving remote stock items from system3")
def _sync_stock_items_step_3(self):
print("updating local items")
def _sync_stock_items_step_4(self):
print("sending updates to third party system3")
def _send_transaction(self, transaction):
print("send transaction to system3: {0!r}".format(transaction))
def main():
transaction = {"id": 1,
"items": [{"item_id": 1,
"amount_purchased": 3,
"value": 238
}],
}
for system in [System1, System2, System3]:
print("="*10)
system.sync_stock_items()
system.send_transaction(transaction)
if __name__ == "__main__":
main()
You should build your programs in such a way that you can constantly swap out bits of the program without changing any other part of the code. Every period—depending on the size and scope of your project, this might mean a year or five years or six months -you want to have cycled out all the old code and replaced it with better, more elegant, and more efficient code.
While I don’t agree with much of the book, this part here is the worst. Constantly updating your code is the bane of software, not the holy grail. It will lead to bloat and bugs. Code should be updated as little as possible. Write it right.
How often is grep updated? sed? awk? And they are great tools, timeless. How about Firefox? Every few weeks? And it is a bloated POS. Good code should move towards being static.