From a9d773f5580bd2975c68e09d2731014ba4b8f281 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Sun, 28 Nov 2010 10:37:06 +0100 Subject: Updated the documentation of decorator 3.2.1 --- documentation.html | 460 +++++++++++++++++++++++++---------------------------- 1 file changed, 215 insertions(+), 245 deletions(-) (limited to 'documentation.html') diff --git a/documentation.html b/documentation.html index 79163a7..55936b3 100644 --- a/documentation.html +++ b/documentation.html @@ -83,10 +83,10 @@ E-mail:michele.simionato@gmail.com Version: -3.2.0 (2010-05-25) +3.2.1 (2010-11-28) Requires:Python 2.4+ -Download page:http://pypi.python.org/pypi/decorator/3.2.0 +Download page:http://pypi.python.org/pypi/decorator/3.2.1 Installation:easy_install decorator @@ -179,24 +179,22 @@ but they do not preserve the signature. A simple implementation could be the following (notice that in general it is impossible to memoize correctly something that depends on non-hashable arguments):

-
-
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)
-
- -
+
+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 @@ -211,7 +209,7 @@ general memoize_uw returns a function with a

>>> @memoize_uw
 ... def f1(x):
-...     time.sleep(1) # simulate some long computation
+...     time.sleep(1) # simulate some long computation
 ...     return x
 
@@ -232,10 +230,10 @@ pydoc will tell you that the function accepts a generic signature *args, **kw, but when you try to call the function with more than an argument, you will get an error:

-
>>> f1(0, 1)
+
>>> f1(0, 1)
 Traceback (most recent call last):
    ...
-TypeError: f1() takes exactly 1 argument (2 given)
+TypeError: f1() takes exactly 1 argument (2 given)
 
@@ -257,29 +255,25 @@ 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)
-
- -
+
+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). @@ -289,7 +283,7 @@ decorate to the caller function.

>>> @memoize
 ... def heavy_computation():
-...     time.sleep(2)
+...     time.sleep(2)
 ...     return "done"
 
 >>> print heavy_computation() # the first time it will take 2 seconds
@@ -313,19 +307,15 @@ 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
@@ -336,8 +326,8 @@ function is called:

It is immediate to verify that f1 works

-
>>> f1(0)
-calling f1 with args (0,), {}
+
>>> f1(0)
+calling f1 with args (0,), {}
 
@@ -351,27 +341,27 @@ function is called:

The same decorator works with functions of any signature:

>>> @trace
-... def f(x, y=1, z=2, *args, **kw):
+... def f(x, y=1, z=2, *args, **kw):
 ...     pass
 
->>> f(0, 3)
-calling f with args (0, 3, 2), {}
+>>> f(0, 3)
+calling f with args (0, 3, 2), {}
 
 >>> print getargspec(f)
-ArgSpec(args=['x', 'y', 'z'], varargs='args', keywords='kw', defaults=(1, 2))
+ArgSpec(args=['x', 'y', 'z'], varargs='args', keywords='kw', defaults=(1, 2))
 

That includes even functions with exotic signatures like the following:

>>> @trace
-... def exotic_signature((x, y)=(1,2)): return x+y
+... def exotic_signature((x, y)=(1,2)): return x+y
 
 >>> print getargspec(exotic_signature)
-ArgSpec(args=[['x', 'y']], varargs=None, keywords=None, defaults=((1, 2),))
+ArgSpec(args=[['x', 'y']], varargs=None, keywords=None, defaults=((1, 2),))
 >>> exotic_signature()
-calling exotic_signature with args ((1, 2),), {}
-3
+calling exotic_signature with args ((1, 2),), {}
+3
 
@@ -405,7 +395,7 @@ For instance, you can write directly

object which can be used as a decorator:

>>> trace
-<function trace at 0x...>
+<function trace at 0x...>
 
@@ -429,40 +419,38 @@ 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:

>>> @blocking("Please wait ...")
 ... def read_data():
-...     time.sleep(3) # simulate a blocking resource
+...     time.sleep(3) # simulate a blocking resource
 ...     return "some data"
 
 >>> print read_data() # data is not available yet
 Please wait ...
 
->>> time.sleep(1)
+>>> time.sleep(1)
 >>> print read_data() # data is not available yet
 Please wait ...
 
->>> time.sleep(1)
+>>> time.sleep(1)
 >>> print read_data() # data is not available yet
 Please wait ...
 
@@ -490,66 +478,58 @@ 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().

@@ -566,7 +546,7 @@ be locked. Here is a minimalistic example:

... def write(data): ... # append data to the datalist by locking ... with threading.Lock(): -... time.sleep(1) # emulate some long running operation +... time.sleep(1) # emulate some long running operation ... datalist.append(data) ... # other operations not requiring a lock here
@@ -576,14 +556,14 @@ be locked. Here is a minimalistic example:

be no synchronization problems since write is locked.

>>> write("data1")
-<Thread(write-1, started...)>
+<Thread(write-1, started...)>
 
->>> time.sleep(.1) # wait a bit, so we are sure data2 is written after data1
+>>> time.sleep(.1) # wait a bit, so we are sure data2 is written after data1
 
 >>> write("data2")
-<Thread(write-2, started...)>
+<Thread(write-2, started...)>
 
->>> time.sleep(2) # wait for the writers to complete
+>>> time.sleep(2) # wait for the writers to complete
 
 >>> print datalist
 ['data1', 'data2']
@@ -610,8 +590,8 @@ were the function is generated by exec. Here i
 ...     print args, kw
 
 >>> f1 = FunctionMaker.create('f1(a, b)', 'f(a, b)', dict(f=f))
->>> f1(1,2)
-(1, 2) {}
+>>> f1(1,2)
+(1, 2) {}
 
@@ -674,14 +654,12 @@ 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
@@ -694,7 +672,7 @@ source code which is probably not what you want:

(see bug report 1764286 for an explanation of what is happening). -Unfortunately the bug is still there, even in Python 2.6 and 3.0. +Unfortunately the bug is still there, even in Python 2.7 and 3.1. There is however a workaround. The decorator module adds an attribute .undecorated to the decorated function, containing a reference to the original function. The easy way to get @@ -703,10 +681,10 @@ undecorated function:

>>> print inspect.getsource(factorial.undecorated)
 @tail_recursive
-def factorial(n, acc=1):
+def factorial(n, acc=1):
     "The good old factorial"
-    if n == 0: return acc
-    return factorial(n-1, n*acc)
+    if n == 0: return acc
+    return factorial(n-1, n*acc)
 <BLANKLINE>
 
@@ -720,18 +698,16 @@ 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.

@@ -745,60 +721,54 @@ 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
+
>>> print factorial(4)
+24
 
@@ -807,13 +777,11 @@ 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).

@@ -859,7 +827,7 @@ longer and more difficult to understand. Consider this example:

>>> @trace
 ... def f():
-...     1/0
+...     1/0
 
@@ -869,11 +837,11 @@ function is decorated the traceback will be longer:

>>> f()
 Traceback (most recent call last):
   ...
-     File "<string>", line 2, in f
-     File "<doctest __main__[18]>", line 4, in trace
+     File "<string>", line 2, in f
+     File "<doctest __main__[18]>", line 4, in trace
        return f(*args, **kw)
-     File "<doctest __main__[47]>", line 3, in f
-       1/0
+     File "<doctest __main__[47]>", line 3, in f
+       1/0
 ZeroDivisionError: integer division or modulo by zero
 
@@ -926,7 +894,7 @@ called too late:

The solution is to extract the inner function from the unbound method:

>>> trace(C.meth.im_func)
-<function meth at 0x...>
+<function meth at 0x...>
 
@@ -945,7 +913,9 @@ you will get a NameError:

-

Finally, the implementation is such that the decorated function contains +

Finally, the implementation is such that the decorated function +attribute .func_globals is a copy of the original function +attribute. Moreover the decorated function contains a copy of the original function dictionary (vars(decorated_f) is not vars(f)):

-- cgit v1.2.1