So, most of us end up stuck between various unpalatable choices: |
So, most of us end up stuck between various unpalatable choices: |
|
|
1. use a global and get it over with (but suffer a guilty conscience and |
1. use a global and get it over with (but suffer a guilty conscience and |
the fear of later disasters in retribution for our sins), or |
the fear of later disasters in retribution for our sins), |
|
|
2. attempt to use a dependency injection framework, paying extra now to be |
2. attempt to use a dependency injection framework, paying extra now to be |
reassured that things will work out later. |
reassured that things will work out later, or |
|
|
3. use a thread-local variable, and bear the cost of introducing a possible |
3. use a thread-local variable, and bear the cost of introducing a possible |
threading dependency, and still not having a reasonable way to test or |
threading dependency, and still not having a reasonable way to test or |
Replaceable Singletons |
Replaceable Singletons |
---------------------- |
---------------------- |
|
|
Here's what a simple "global" counter object implemented with ``peak.context`` |
Here's what a simple "global" counter service implemented with ``peak.context`` |
looks like:: |
looks like:: |
|
|
>>> from peak.util import context |
>>> from peak import context |
|
|
>>> class Counter(context.Replaceable): |
>>> class Counter(context.Service): |
... value = 0 |
... value = 0 |
... |
... |
... def inc(self): |
... def inc(self): |
|
... "test" |
... self.value += 1 |
... self.value += 1 |
... |
... |
|
|
>>> count = Counter.proxy() |
>>> Counter.value |
|
|
>>> count.value |
|
0 |
0 |
>>> count.inc() |
>>> Counter.inc() |
>>> count.value |
>>> Counter.value |
1 |
1 |
|
|
Code that wants to use this global counter just calls ``count.inc()`` or |
Code that wants to use this global counter just calls ``Counter.inc()`` or |
accesses ``count.value``, and it will automatically use the right ``Counter`` |
accesses ``Counter.value``, and it will automatically use the right ``Counter`` |
instance for the current thread or task. Want to use a fresh counter for |
instance for the current thread or task. Want to use a fresh counter for |
a test? Just do this:: |
a test? Just do this:: |
|
|
``context`` library also includes a decorator that emulates a ``with`` |
``context`` library also includes a decorator that emulates a ``with`` |
statement:: |
statement:: |
|
|
>>> count.value # before using a different counter |
>>> Counter.value # before using a different counter |
1 |
1 |
|
|
>>> @context.call_with(Counter()) |
>>> @context.call_with(Counter()) |
... def do_it(c): |
... def do_it(c): |
... print count.value |
... print Counter.value |
0 |
0 |
|
|
>>> count.value # The original counter is now in use again |
>>> Counter.value # The original counter is now in use again |
1 |
1 |
|
|
The ``@call_with`` decorator is a bit uglier than a ``with`` statement, but |
The ``@call_with`` decorator is a bit uglier than a ``with`` statement, but |
Want to create an alternative implementation of the same service, that can |
Want to create an alternative implementation of the same service, that can |
be plugged in to replace it? That's simple too:: |
be plugged in to replace it? That's simple too:: |
|
|
>>> class DoubleCounter(context.Replaceable): |
>>> class DoubleCounter(context.Service): |
... context.replaces(Counter) |
... context.replaces(Counter) |
... value = 0 |
... value = 0 |
... def inc(self): |
... def inc(self): |
|
|
>>> @context.call_with(DoubleCounter()) |
>>> @context.call_with(DoubleCounter()) |
... def do_it(c): |
... def do_it(c): |
... print count.value |
... print Counter.value |
... count.inc() |
... Counter.inc() |
... print count.value |
... print Counter.value |
0 |
0 |
2 |
2 |
|
|
And of course, once a replacement is no longer in use, the original instance |
And of course, once a replacement is no longer in use, the original instance |
becomes active again:: |
becomes active again:: |
|
|
>>> count.value |
>>> Counter.value |
1 |
1 |
|
|
All this, with no interfaces to declare or register, and no XML or |
All this, with no interfaces to declare or register, and no XML or |
want. You can even take a snapshot of the entire current context and restore |
want. You can even take a snapshot of the entire current context and restore |
all the previous values:: |
all the previous values:: |
|
|
old = context.swap(context.new()) |
with context.Globals(): |
try: |
|
# code to read config file and set ``current()`` services |
# code to read config file and set ``current()`` services |
# code that uses the configured services |
# code that uses the configured services |
finally: |
|
context.swap(old) # restore the previous context |
|
|
|
This code won't share any "globals" with the code that calls it; it will not |
This code won't share any "globals" with the code that calls it; it will not |
only get its own private ``Counter`` instance, but a private instance of any |
only get its own private ``Counter`` instance, but a private instance of any |
other ``Replaceable`` objects it uses as well. (Instances are created lazily |
other ``Service`` objects it uses as well. (Instances are created lazily |
in new contexts, so if you don't use a particular service, it's never created.) |
in new contexts, so if you don't use a particular service, it's never created.) |
Try doing that with global or thread-local variables! |
Try doing that with global or thread-local variables! |
|
|