GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing python-patterns.guide — The Prebound Method Pattern The Prebound Method Pattern¶A pattern native to the Python language. Verdict A powerful technique for offering callables at the top level of your module that share state through a common object. There are occasions on which a Python module wants to offer several routines in the module’s global namespace that will need to share state with each other at runtime. Probably the most famous example
is the Python Standard Library’s How do these top-level routines share state? Behind the scenes these callables are, in fact, methods
that have all been bound ahead of time
to a single instance of After reviewing the problem that the Prebound Methods pattern solves, we will see how the pattern looks in Python code. Alternatives¶The most primitive approach to sharing state between a pair of module-level callables is to write a pair of functions that manipulate global data that’s stored next to them at the top level of the module. Imagine that we want to offer a simple random number generator.
It returns, in an endless loop,
the numbers 1 through 255 in a fixed pseudo-random order.
We also want to offer a simple If Python only supported plain functions, we might store the shared seed as a module global that our functions would directly access and modify: from datetime import datetime _seed = datetime.now().microsecond % 255 + 1 def set_seed(value): global _seed _seed = value def random(): global _seed _seed, carry = divmod(_seed, 2) if carry: _seed ^= 0xb8 return _seed There are several problems with this approach. First, it is impossible to ever instantiate a second copy of this random number generator. If two threads each wanted their own generator to avoid needing to protect it with locks, then they would be out of luck. (Okay, not really; this is Python.
Think of the possibilities!
You could import the module,
save a reference to it,
remove it from the Second, it is more difficult to decouple your random number generator tests from each other if the generator’s state is global. Instead of each test getting to create and exercise a separate isolated instance, the tests will all have to share the single generator and correctly reset its state before the next test runs. Third, this approach abandons encapsulation.
This will sound like a fuzzier complaint
than the previous two,
but it can detract from readability
(“readability counts”)
for a small tight system of two functions and a To solve the above problems, we indent the two functions and wrap them up as methods of a new Python class. The two methods and their state can then be happily bundled together: from datetime import datetime class Random8(object): def __init__(self): self.set_seed(datetime.now().microsecond % 255 + 1) def set_seed(self, value): self.seed = value def random(self): self.seed, carry = divmod(self.seed, 2) if carry: self.seed ^= 0xb8 return self.seed This imposes an extra step on the caller: the object will have to be instantiated before the two methods can be called. Is there a way to let the caller skip that step? The software engineer would do well to stop for a moment and to think quite seriously about the fact that in the vast majority of cases simply defining a class like this is enough. It will take only a single statement for your user to create an instance of your class. If their own application’s architecture is too deeply compromised for them to easily pass the instance everywhere it’s needed, they can always store the instance as a module global in one of their own modules for the use of the rest of their code. There are several reasons that that the Prebound Methods pattern is a particularly good fit for a random number generator:
If the costs and benefits strike a similar balance for a module of your own you are designing, then the Prebound Method pattern can let you deliver remarkable convenience. The pattern¶To offer your users a slate of Prebound Methods:
For the random number generator that we used as an illustration above, the entire module might look like this: from datetime import datetime class Random8(object): def __init__(self): self.set_seed(datetime.now().microsecond % 255 + 1) def set_seed(self, value): self.seed = value def random(self): self.seed, carry = divmod(self.seed, 2) if carry: self.seed ^= 0xb8 return self.seed _instance = Random8() random = _instance.random set_seed = _instance.set_seed Users will now be able to invoke each method as though it were a stand-alone function. But the methods will secretly share state thanks to the common instance that they are bound to, without requiring the user to manage or pass that state explicitly. When exercising this pattern,
please be responsible about the dangers
of instantiating an object at import time.
This pattern is usually not appropriate
for a class whose constructor creates files,
reads a database configuration,
opens sockets,
or that in general will inflict side effects on the program importing them.
In that case,
you will do better to either avoid the Prebound Methods pattern entirely,
or else to defer any actual side effects
until one of the methods is called.
You could choose a middle ground of providing a But for lightweight objects that can be instantiated without substantial delay or complication, the Prebound Methods pattern is an elegant way to make the stateful behavior of a class instance available up at a module’s global level. Examples of the pattern are strewn merrily across the Standard Library
even beyond the c = TextCalendar() ... monthcalendar = c.monthdayscalendar prweek = c.prweek week = c.formatweek weekheader = c.formatweekheader prmonth = c.prmonth month = c.formatmonth calendar = c.formatyear prcal = c.pryear As does the venerable old aRepr = Repr() repr = aRepr.repr As do several other modules — you will find the Prebound Methods pattern used with each of these Standard Library objects:
One final hint:
it is almost always better
to assign methods to global names explicitly.
Even if there are a dozen methods,
I recommend going ahead and writing
a quick stack of a dozen assignment statements
aligned at the left-hand column of your module
that make visible the whole list of global names you are defining.
The fact that Python is a dynamic language
might tempt you to automate the series of assignments instead,
using attribute introspection and a |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |