summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xCHANGES.txt6
-rwxr-xr-xdecorator.py56
-rw-r--r--documentation.py186
-rw-r--r--setup.py2
4 files changed, 155 insertions, 95 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index dc09489..00ccdef 100755
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,10 @@
HISTORY
----------
+3.1. Added decorator.factory, an easy way to define families of decorators
+ (requested by various users, including David Laban). Refactored the
+ FunctionMaker class and added an easier to use .create classmethod.
+ Internally, functools.partial is used for Python >= 2.5 (16/08/2009)
3.0.1. Improved the error message in case a bound/unbound method is passed
instead of a function and documented this case; that should make life
easier for users like Gustavo Nerea (16/02/2009)
@@ -8,7 +12,7 @@ HISTORY
syntax for ``decorator``. Moreover, added support for getting the
source code. This version is Python 3.0 ready.
Major overhaul of the documentation, now hosted on
- http://packages.python.org/decorator (14/12/2008).
+ http://packages.python.org/decorator (14/12/2008)
2.3.2. Small optimization in the code for decorator factories. First version
with the code uploaded to PyPI (01/12/2008)
2.3.1. Set the zipsafe flag to False, since I want my users to have the source, not
diff --git a/decorator.py b/decorator.py
index 1a8db8b..2a5c090 100755
--- a/decorator.py
+++ b/decorator.py
@@ -31,6 +31,15 @@ for the documentation.
__all__ = ["decorator", "FunctionMaker", "deprecated", "getinfo", "new_wrapper"]
import os, sys, re, inspect, warnings
+try:
+ from functools import partial
+except ImportError: # for Python version < 2.5
+ def partial(func, *args):
+ "A poor man replacement of partial for use in the decorator module only"
+ f = lambda *otherargs: func(*(args + otherargs))
+ f.args = args
+ f.func = func
+ return f
DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(')
@@ -77,7 +86,7 @@ class FunctionMaker(object):
func.__name__ = self.name
func.__doc__ = getattr(self, 'doc', None)
func.__dict__ = getattr(self, 'dict', {})
- func.func_defaults = getattr(self, 'defaults', None)
+ func.func_defaults = getattr(self, 'defaults', ())
callermodule = sys._getframe(3).f_globals.get('__name__', '?')
func.__module__ = getattr(self, 'module', callermodule)
func.__dict__.update(kw)
@@ -110,23 +119,44 @@ class FunctionMaker(object):
self.update(func, **attrs)
return func
+ @classmethod
+ def create(cls, obj, body, evaldict, defaults=None, addsource=True,**attrs):
+ """
+ Create a function from the strings name, signature and body.
+ evaldict is the evaluation dictionary. If addsource is true an attribute
+ __source__ is added to the result. The attributes attrs are added,
+ if any.
+ """
+ if isinstance(obj, str): # "name(signature)"
+ name, rest = obj.strip().split('(', 1)
+ signature = rest[:-1]
+ func = None
+ else: # a function
+ name = None
+ signature = None
+ func = obj
+ fun = cls(func, name, signature, defaults)
+ ibody = ''.join(' ' + line for line in body.splitlines())
+ return fun.make('def %(name)s(%(signature)s):\n' + ibody,
+ evaldict, addsource, **attrs)
+
def decorator(caller, func=None):
"""
decorator(caller) converts a caller function into a decorator;
decorator(caller, func) decorates a function using a caller.
"""
- if func is None: # returns a decorator
- fun = FunctionMaker(caller)
- first_arg = inspect.getargspec(caller)[0][0]
- src = 'def %s(%s): return _call_(caller, %s)' % (
- caller.__name__, first_arg, first_arg)
- return fun.make(src, dict(caller=caller, _call_=decorator),
- undecorated=caller)
- else: # returns a decorated function
- fun = FunctionMaker(func)
- src = """def %(name)s(%(signature)s):
- return _call_(_func_, %(signature)s)"""
- return fun.make(src, dict(_func_=func, _call_=caller), undecorated=func)
+ if func is not None: # returns a decorated function
+ return FunctionMaker.create(
+ func, "return _call_(_func_, %(signature)s)",
+ dict(_call_=caller, _func_=func), undecorated=func)
+ else: # returns a decorator
+ return partial(decorator, caller)
+
+def decorator_factory(decfac):
+ "decorator.factory(decfac) returns a one-parameter family of decorators"
+ return partial(lambda df, param: decorator(partial(df, param)), decfac)
+
+decorator.factory = decorator_factory
###################### deprecated functionality #########################
diff --git a/documentation.py b/documentation.py
index e5cc32c..2452333 100644
--- a/documentation.py
+++ b/documentation.py
@@ -275,16 +275,15 @@ For instance, you can write directly
... print "calling %s with args %s, %s" % (f.func_name, args, kw)
... return f(*args, **kw)
-and now ``trace`` will be a decorator. You
-can easily check that the signature has changed:
+and now ``trace`` will be a decorator. Actually ``trace`` is a ``partial``
+object which can be used as a decorator:
.. code-block:: python
- >>> print getargspec(trace)
- (['f'], None, None, None)
+ >>> trace # doctest: +ELLIPSIS
+ <functools.partial object at 0x...>
-Therefore now ``trace`` can be used as a decorator and
-the following will work:
+Here is an example of usage:
.. code-block:: python
@@ -294,21 +293,59 @@ the following will work:
>>> func()
calling func with args (), {}
-For the rest of this document, I will discuss examples of useful
-decorators built on top of ``decorator``.
+If you are using an old Python version (Python 2.4) the
+``decorator`` module provides a poor man replacement for
+``functools.partial`` as a higher order function.
+
+There is also an easy way to create one-parameter factories of
+decorators. Suppose for instance you want to generated different
+tracing generator, with different tracing messages.
+Here is how to do it:
+
+.. code-block:: python
+
+ >>> @decorator.factory
+ ... def trace_factory(message_template, f, *args, **kw):
+ ... name = f.func_name
+ ... print message_template % locals()
+ ... return f(*args, **kw)
+
+``decorator.factory`` converts a function with signature
+``(param, func, *args, **kw)`` into a one-parameter family
+of decorators.
+
+.. code-block:: python
+
+ >>> trace_factory # doctest: +ELLIPSIS
+ <functools.partial object at 0x...>
+
+ >>> trace = trace_factory('Calling %(name)s with args %(args)s '
+ ... 'and keywords %(kw)s')
+
+In this example the parameter (``message_template``) is
+just a string, but in general it can be a tuple, a dictionary, or
+a generic object, so there is no real restriction (for instance,
+if you want to define a two-parameter family of decorators just
+use a tuple with two arguments as parameter).
+Here is an example of usage:
+
+.. code-block:: python
+
+ >>> @trace
+ ... def func(): pass
+
+ >>> func()
+ Calling func with args () and keywords {}
``blocking``
-------------------------------------------
Sometimes one has to deal with blocking resources, such as ``stdin``, and
sometimes it is best to have back a "busy" message than to block everything.
-This behavior can be implemented with a suitable decorator:
+This behavior can be implemented with a suitable family of decorators,
+where the parameter is the busy message:
$$blocking
-
-(notice that without the help of ``decorator``, an additional level of
-nesting would have been needed). This is actually an example
-of a one-parameter family of decorators.
Functions decorated with ``blocking`` will return a busy message if
the resource is unavailable, and the intended result if the resource is
@@ -387,13 +424,13 @@ be locked. Here is a minimalistic example:
Each call to ``write`` will create a new writer thread, but there will
be no synchronization problems since ``write`` is locked.
->>> write("data1")
-<Thread(write-1, started)>
+>>> write("data1") # doctest: +ELLIPSIS
+<Thread(write-1, started ...)>
>>> time.sleep(.1) # wait a bit, so we are sure data2 is written after data1
->>> write("data2")
-<Thread(write-2, started)>
+>>> write("data2") # doctest: +ELLIPSIS
+<Thread(write-2, started ...)>
>>> time.sleep(2) # wait for the writers to complete
@@ -409,48 +446,24 @@ a ``FunctionMaker`` class which is able to generate on the fly
functions with a given name and signature from a function template
passed as a string. Generally speaking, you should not need to
resort to ``FunctionMaker`` when writing ordinary decorators, but
-it is handy in some circumstances. We will see an example in two
-paragraphs, when implementing a custom decorator factory
-(``decorator_apply``).
-
-Notice that while I do not have plans
-to change or remove the functionality provided in the
-``FunctionMaker`` class, I do not guarantee that it will stay
-unchanged forever. For instance, right now I am using the traditional
-string interpolation syntax for function templates, but Python 2.6
-and Python 3.0 provide a newer interpolation syntax and I may use
-the new syntax in the future.
-On the other hand, the functionality provided by
-``decorator`` has been there from version 0.1 and it is guaranteed to
-stay there forever.
+it is handy in some circumstances. You will see an example shortly, in
+the implementation of a cool decorator utility (``decorator_apply``).
-``FunctionMaker`` takes the name and the signature (as a string) of a
-function in input, or a whole function. Then, it creates a new
-function (actually a closure) from a function template (the function
-template must begin with ``def`` with no comments before and you cannot
-use a ``lambda``) via its
-``.make`` method: the name and the signature of the resulting function
-are determinated by the specified name and signature. For instance,
-here is an example of how to restrict the signature of a function:
+``FunctionMaker`` provides a ``.create`` classmethod which
+takes as input the name, signature, and body of the function
+we want to generate as well as the execution environment
+were the function is generated by ``exec``. Here is an example:
.. code-block:: python
>>> def f(*args, **kw): # a function with a generic signature
... print args, kw
- >>> fun = FunctionMaker(name="f1", signature="a,b")
- >>> f1 = fun.make('''\
- ... def %(name)s(%(signature)s):
- ... f(%(signature)s)''', dict(f=f))
- ...
+ >>> f1 = FunctionMaker.create('f1(a, b)', 'f(a, b)', dict(f=f))
>>> f1(1,2)
(1, 2) {}
-The dictionary passed in this example (``dict(f=f)``) is the
-execution environment: ``FunctionMaker.make`` actually returns a
-closure, and the original function ``f`` is a variable in the
-closure environment.
-``FunctionMaker.make`` also accepts keyword arguments and such
+``FunctionMaker.create`` also accepts keyword arguments and such
arguments are attached to the resulting function. This is useful
if you want to set some function attributes, for instance the
docstring ``__doc__``.
@@ -462,19 +475,28 @@ be added to the decorated function:
.. code-block:: python
- >>> f1 = fun.make('''\
- ... def %(name)s(%(signature)s):
- ... f(%(signature)s)''', dict(f=f), addsource=True)
- ...
+ >>> f1 = FunctionMaker.create(
+ ... 'f1(a, b)', 'f(a, b)', dict(f=f), addsource=True)
>>> print f1.__source__
- def f1(a,b):
- f(a,b)
+ def f1(a, b):
+ f(a, b)
<BLANKLINE>
+Notice that while I do not have plans
+to change or remove the functionality provided in the
+``FunctionMaker`` class, I do not guarantee that it will stay
+unchanged forever. For instance, right now I am using the traditional
+string interpolation syntax for function templates, but Python 2.6
+and Python 3.0 provide a newer interpolation syntax and I may use
+the new syntax in the future.
+On the other hand, the functionality provided by
+``decorator`` has been there from version 0.1 and it is guaranteed to
+stay there forever.
+
Getting the source code
---------------------------------------------------
-Internally ``FunctionMaker.make`` uses ``exec`` to generate the
+Internally ``FunctionMaker.create`` uses ``exec`` to generate the
decorated function. Therefore
``inspect.getsource`` will not work for decorated functions. That
means that the usual '??' trick in IPython will give you the (right on
@@ -647,9 +669,10 @@ would require to change the CPython implementation of functions and
add an hook to make it possible to change their signature directly.
That could happen in future versions of Python (see PEP 362_) and
then the decorator module would become obsolete. However, at present,
-even in Python 3.0 it is impossible to change the function signature
+even in Python 3.1 it is impossible to change the function signature
directly, therefore the ``decorator`` module is still useful.
-Actually, this is one of the main reasons why I am releasing version 3.0.
+Actually, this is one of the main reasons why I keep maintaining
+the module and releasing new versions.
.. _362: http://www.python.org/dev/peps/pep-0362
@@ -747,6 +770,9 @@ Finally ``decorator`` cannot be used as a class decorator and the
`functionality introduced in version 2.3`_ has been removed. That
means that in order to define decorator factories with classes you
need to define the ``__call__`` method explicitly (no magic anymore).
+Starting from version 3.1 there is
+an easy way to define decorator factories by using ``decorator.factory``,
+so that there is less need to use classes to implement decorator factories.
All these changes should not cause any trouble, since they were
all rarely used features. Should you have any trouble, you can always
@@ -756,10 +782,9 @@ The examples shown here have been tested with Python 2.5. Python 2.4
is also supported - of course the examples requiring the ``with``
statement will not work there. Python 2.6 works fine, but if you
run the examples here in the interactive interpreter
-you will notice a couple of minor differences since
+you will notice a few differences since
``getargspec`` returns an ``ArgSpec`` namedtuple instead of a regular
-tuple, and the string representation of a thread object returns a
-thread identifier number. That means that running the file
+tuple. That means that running the file
``documentation.py`` under Python 2.5 will a few errors, but
they are not serious. Python 3.0 is kind of supported too.
Simply run the script ``2to3`` on the module
@@ -818,11 +843,13 @@ today = time.strftime('%Y-%m-%d')
__doc__ = __doc__.replace('$VERSION', VERSION).replace('$DATE', today)
def decorator_apply(dec, func):
- "Decorate a function using a signature-non-preserving decorator"
- fun = FunctionMaker(func)
- src = '''def %(name)s(%(signature)s):
- return decorated(%(signature)s)'''
- return fun.make(src, dict(decorated=dec(func)), undecorated=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 _trace(f, *args, **kw):
print "calling %s with args %s, %s" % (f.__name__, args, kw)
@@ -917,20 +944,19 @@ def _memoize(func, *args, **kw):
def memoize(f):
f.cache = {}
return decorator(_memoize, f)
-
-def blocking(not_avail="Not Available"):
- 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)
+
+@decorator.factory
+def blocking(not_avail, 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
class User(object):
"Will just be able to see a page"
diff --git a/setup.py b/setup.py
index 83e990d..67d18e2 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ try:
except ImportError:
from distutils.core import setup
-VERSION = '3.0.1'
+VERSION = '3.1.0'
if __name__ == '__main__':
try: