From 42345d200ef0ca7cfd859dff15c4c383f65a8d2e Mon Sep 17 00:00:00 2001
From: micheles
-def memoize25(func):
- func.cache = {}
- def memoize(*args, **kw):
- if kw: # frozenset is used to ensure hashability
- key = args, frozenset(kw.iteritems())
- else:
- key = args
- cache = func.cache
- if key in cache:
- return cache[key]
- else:
- cache[key] = result = func(*args, **kw)
- return result
- return functools.update_wrapper(memoize, func)
-
+def memoize_uw(func):
+ func.cache = {}
+ def memoize(*args, **kw):
+ if kw: # frozenset is used to ensure hashability
+ key = args, frozenset(kw.iteritems())
+ else:
+ key = args
+ cache = func.cache
+ if key in cache:
+ return cache[key]
+ else:
+ cache[key] = result = func(*args, **kw)
+ return result
+ return functools.update_wrapper(memoize, func)
+Here we used the functools.update_wrapper utility, which has been added in Python 2.5 expressly to simplify the definition of decorators (in older versions of Python you need to copy the function attributes @@ -203,11 +205,11 @@ from the original function to the decorated function by hand).
The implementation above works in the sense that the decorator can accept functions with generic signatures; unfortunately this implementation does not define a signature-preserving decorator, since in -general memoize25 returns a function with a +general memoize_uw returns a function with a different signature from the original function.
Consider for instance the following case:
>>> @memoize25
+>>> @memoize_uw
... def f1(x):
... time.sleep(1) # simulate some long computation
... return x
@@ -219,7 +221,7 @@ but the decorated function takes any number of arguments and
keyword arguments:
>>> from inspect import getargspec
->>> print getargspec(f1)
+>>> print getargspec(f1) # I am using Python 2.6+ here
ArgSpec(args=[], varargs='args', keywords='kw', defaults=None)
@@ -255,26 +257,30 @@ returns the decorated function. The caller function must have
signature (f, *args, **kw) and it must call the original function f
with arguments args and kw, implementing the wanted capability,
i.e. memoization in this case:
-
-def _memoize(func, *args, **kw):
- if kw: # frozenset is used to ensure hashability
- key = args, frozenset(kw.iteritems())
- else:
- key = args
- cache = func.cache # attributed added by memoize
- if key in cache:
- return cache[key]
- else:
- cache[key] = result = func(*args, **kw)
- return result
-
+
+def _memoize(func, *args, **kw):
+ if kw: # frozenset is used to ensure hashability
+ key = args, frozenset(kw.iteritems())
+ else:
+ key = args
+ cache = func.cache # attributed added by memoize
+ if key in cache:
+ return cache[key]
+ else:
+ cache[key] = result = func(*args, **kw)
+ return result
+
+
+
At this point you can define your decorator as follows:
-
-def memoize(f):
- f.cache = {}
- return decorator(_memoize, f)
-
-The difference with respect to the Python 2.5 approach, which is based
+
+def memoize(f):
+ f.cache = {}
+ return decorator(_memoize, f)
+
+
+
+The difference with respect to the memoize_uw approach, which is based
on nested functions, is that the decorator module forces you to lift
the inner function at the outer level (flat is better than nested).
Moreover, you are forced to pass explicitly the function you want to
@@ -307,15 +313,19 @@ decorate to the caller function.
As an additional example, here is how you can define a trivial
trace decorator, which prints a message everytime the traced
function is called:
-
-def _trace(f, *args, **kw):
- print "calling %s with args %s, %s" % (f.__name__, args, kw)
- return f(*args, **kw)
-
-
-def trace(f):
- return decorator(_trace, f)
-
+
+def _trace(f, *args, **kw):
+ print "calling %s with args %s, %s" % (f.__name__, args, kw)
+ return f(*args, **kw)
+
+
+
+
+def trace(f):
+ return decorator(_trace, f)
+
+
+
Here is an example of usage:
>>> @trace
@@ -419,21 +429,23 @@ object which can be used as a decorator:
sometimes it is best to have back a "busy" message than to block everything.
This behavior can be implemented with a suitable family of decorators,
where the parameter is the busy message:
-
-def blocking(not_avail):
- def blocking(f, *args, **kw):
- if not hasattr(f, "thread"): # no thread running
- def set_result(): f.result = f(*args, **kw)
- f.thread = threading.Thread(None, set_result)
- f.thread.start()
- return not_avail
- elif f.thread.isAlive():
- return not_avail
- else: # the thread is ended, return the stored result
- del f.thread
- return f.result
- return decorator(blocking)
-
+
+def blocking(not_avail):
+ def blocking(f, *args, **kw):
+ if not hasattr(f, "thread"): # no thread running
+ def set_result(): f.result = f(*args, **kw)
+ f.thread = threading.Thread(None, set_result)
+ f.thread.start()
+ return not_avail
+ elif f.thread.isAlive():
+ return not_avail
+ else: # the thread is ended, return the stored result
+ del f.thread
+ return f.result
+ return decorator(blocking)
+
+
+
Functions decorated with blocking will return a busy message if
the resource is unavailable, and the intended result if the resource is
available. For instance:
@@ -478,58 +490,66 @@ is executed in a separate thread. Moreover, it is possible to set
three callbacks on_success, on_failure and on_closing,
to specify how to manage the function call.
The implementation is the following:
-
-def on_success(result): # default implementation
- "Called on the result of the function"
- return result
-
-
-def on_failure(exc_info): # default implementation
- "Called if the function fails"
- pass
-
-
-def on_closing(): # default implementation
- "Called at the end, both in case of success and failure"
- pass
-
-
-class Async(object):
- """
- A decorator converting blocking functions into asynchronous
- functions, by using threads or processes. Examples:
-
- async_with_threads = Async(threading.Thread)
- async_with_processes = Async(multiprocessing.Process)
- """
-
- def __init__(self, threadfactory):
- self.threadfactory = threadfactory
-
- def __call__(self, func, on_success=on_success,
- on_failure=on_failure, on_closing=on_closing):
- # every decorated function has its own independent thread counter
- func.counter = itertools.count(1)
- func.on_success = on_success
- func.on_failure = on_failure
- func.on_closing = on_closing
- return decorator(self.call, func)
-
- def call(self, func, *args, **kw):
- def func_wrapper():
- try:
- result = func(*args, **kw)
- except:
- func.on_failure(sys.exc_info())
- else:
- return func.on_success(result)
- finally:
- func.on_closing()
- name = '%s-%s' % (func.__name__, func.counter.next())
- thread = self.threadfactory(None, func_wrapper, name)
- thread.start()
- return thread
-
+
+def on_success(result): # default implementation
+ "Called on the result of the function"
+ return result
+
+
+
+
+def on_failure(exc_info): # default implementation
+ "Called if the function fails"
+ pass
+
+
+
+
+def on_closing(): # default implementation
+ "Called at the end, both in case of success and failure"
+ pass
+
+
+
+
+class Async(object):
+ """
+ A decorator converting blocking functions into asynchronous
+ functions, by using threads or processes. Examples:
+
+ async_with_threads = Async(threading.Thread)
+ async_with_processes = Async(multiprocessing.Process)
+ """
+
+ def __init__(self, threadfactory):
+ self.threadfactory = threadfactory
+
+ def __call__(self, func, on_success=on_success,
+ on_failure=on_failure, on_closing=on_closing):
+ # every decorated function has its own independent thread counter
+ func.counter = itertools.count(1)
+ func.on_success = on_success
+ func.on_failure = on_failure
+ func.on_closing = on_closing
+ return decorator(self.call, func)
+
+ def call(self, func, *args, **kw):
+ def func_wrapper():
+ try:
+ result = func(*args, **kw)
+ except:
+ func.on_failure(sys.exc_info())
+ else:
+ return func.on_success(result)
+ finally:
+ func.on_closing()
+ name = '%s-%s' % (func.__name__, func.counter.next())
+ thread = self.threadfactory(None, func_wrapper, name)
+ thread.start()
+ return thread
+
+
+
The decorated function returns
the current execution thread, which can be stored and checked later, for
instance to verify that the thread .isAlive().
@@ -654,12 +674,14 @@ available. In the past I have considered this acceptable, since
inspect.getsource does not really work even with regular
decorators. In that case inspect.getsource gives you the wrapper
source code which is probably not what you want:
-
-def identity_dec(func):
- def wrapper(*args, **kw):
- return func(*args, **kw)
- return wrapper
-
+
+def identity_dec(func):
+ def wrapper(*args, **kw):
+ return func(*args, **kw)
+ return wrapper
+
+
+
@identity_dec
def example(): pass
@@ -698,16 +720,18 @@ decorator is not signature-preserving. Therefore you may want an easy way to
upgrade third party decorators to signature-preserving decorators without
having to rewrite them in terms of decorator. You can use a
FunctionMaker to implement that functionality as follows:
-
-def decorator_apply(dec, func):
- """
- Decorate a function by preserving the signature even if dec
- is not a signature-preserving decorator.
- """
- return FunctionMaker.create(
- func, 'return decorated(%(signature)s)',
- dict(decorated=dec(func)), undecorated=func)
-
+
+def decorator_apply(dec, func):
+ """
+ Decorate a function by preserving the signature even if dec
+ is not a signature-preserving decorator.
+ """
+ return FunctionMaker.create(
+ func, 'return decorated(%(signature)s)',
+ dict(decorated=dec(func)), undecorated=func)
+
+
+
decorator_apply sets the attribute .undecorated of the generated
function to the original function, so that you can get the right
source code.
@@ -721,51 +745,57 @@ pretty slick decorator that converts a tail-recursive function in an iterative
function. I have shamelessly stolen the basic idea from Kay Schluehr's recipe
in the Python Cookbook,
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691.
-
-class TailRecursive(object):
- """
- tail_recursive decorator based on Kay Schluehr's recipe
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691
- with improvements by me and George Sakkis.
- """
-
- def __init__(self, func):
- self.func = func
- self.firstcall = True
- self.CONTINUE = object() # sentinel
-
- def __call__(self, *args, **kwd):
- CONTINUE = self.CONTINUE
- if self.firstcall:
- func = self.func
- self.firstcall = False
- try:
- while True:
- result = func(*args, **kwd)
- if result is CONTINUE: # update arguments
- args, kwd = self.argskwd
- else: # last call
- return result
- finally:
- self.firstcall = True
- else: # return the arguments of the tail call
- self.argskwd = args, kwd
- return CONTINUE
-
+
+class TailRecursive(object):
+ """
+ tail_recursive decorator based on Kay Schluehr's recipe
+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496691
+ with improvements by me and George Sakkis.
+ """
+
+ def __init__(self, func):
+ self.func = func
+ self.firstcall = True
+ self.CONTINUE = object() # sentinel
+
+ def __call__(self, *args, **kwd):
+ CONTINUE = self.CONTINUE
+ if self.firstcall:
+ func = self.func
+ self.firstcall = False
+ try:
+ while True:
+ result = func(*args, **kwd)
+ if result is CONTINUE: # update arguments
+ args, kwd = self.argskwd
+ else: # last call
+ return result
+ finally:
+ self.firstcall = True
+ else: # return the arguments of the tail call
+ self.argskwd = args, kwd
+ return CONTINUE
+
+
+
Here the decorator is implemented as a class returning callable
objects.
-
-def tail_recursive(func):
- return decorator_apply(TailRecursive, func)
-
+
+def tail_recursive(func):
+ return decorator_apply(TailRecursive, func)
+
+
+
Here is how you apply the upgraded decorator to the good old factorial:
-
-@tail_recursive
-def factorial(n, acc=1):
- "The good old factorial"
- if n == 0: return acc
- return factorial(n-1, n*acc)
-
+
+@tail_recursive
+def factorial(n, acc=1):
+ "The good old factorial"
+ if n == 0: return acc
+ return factorial(n-1, n*acc)
+
+
+
>>> print factorial(4)
24
@@ -777,11 +807,13 @@ your mind ;) Notice that there is no recursion limit now, and you can
easily compute factorial(1001) or larger without filling the stack
frame. Notice also that the decorator will not work on functions which
are not tail recursive, such as the following
-
-def fact(n): # this is not tail-recursive
- if n == 0: return 1
- return n * fact(n-1)
-
+
+def fact(n): # this is not tail-recursive
+ if n == 0: return 1
+ return n * fact(n-1)
+
+
+
(reminder: a function is tail recursive if it either returns a value without
making a recursive call, or returns directly the result of a recursive
call).
@@ -940,7 +972,7 @@ the beginning, via the 2to3 conversion tool, b
been now integrated in the build process, thanks to the distribute
project, the Python 3-compatible replacement of easy_install.
The hard work (for me) has been converting the documentation and the
-doctests. This has been possibly only now that docutils and pygments
+doctests. This has been possible only now that docutils and pygments
have been ported to Python 3.
The decorator module per se does not contain any change, apart
from the removal of the functions get_info and new_wrapper,
@@ -968,11 +1000,11 @@ downgrade to the 2.3 version.
The examples shown here have been tested with Python 2.6. Python 2.4
is also supported - of course the examples requiring the with
statement will not work there. Python 2.5 works fine, but if you
-run the examples here in the interactive interpreter
+run the examples in the interactive interpreter
you will notice a few differences since
getargspec returns an ArgSpec namedtuple instead of a regular
tuple. That means that running the file
-documentation.py under Python 2.5 will a few errors, but
+documentation.py under Python 2.5 will print a few errors, but
they are not serious.
--
cgit v1.2.1