diff options
author | michele.simionato <devnull@localhost> | 2008-12-10 07:05:52 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2008-12-10 07:05:52 +0000 |
commit | b4bcdb1fda1f8c033b81daa5c8ca1b33ad601e91 (patch) | |
tree | f49b40aaabe06c97f04f857d0da5b9d7a8f00a6c /decorator | |
parent | 8de0e0c7f4a26e5f9fe23b677153f971a4637c49 (diff) | |
download | micheles-b4bcdb1fda1f8c033b81daa5c8ca1b33ad601e91.tar.gz |
Removed decorator.apply and decorator.wrap
Diffstat (limited to 'decorator')
-rwxr-xr-x | decorator/decorator.py | 38 | ||||
-rw-r--r-- | decorator/documentation.py | 211 | ||||
-rw-r--r-- | decorator/setup.py | 61 |
3 files changed, 159 insertions, 151 deletions
diff --git a/decorator/decorator.py b/decorator/decorator.py index 1f1607b..a04f9e6 100755 --- a/decorator/decorator.py +++ b/decorator/decorator.py @@ -108,30 +108,22 @@ class FunctionMaker(object): self.update(func, __source__=source) return func -def decorator_wrap(caller, func): - "Decorate a function with a caller" - fun = FunctionMaker(func) - src = """def %(name)s(%(signature)s): +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, caller=caller, _call_=decorator) + else: # returns a decorated function + fun = FunctionMaker(func) + src = """def %(name)s(%(signature)s): return _call_(_func_, %(signature)s)""" - return fun.make(src, _func_=func, _call_=caller) - -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, decorated=dec(func)) - -def decorator(caller): - "decorator(caller) converts a caller function into a decorator" - fun = FunctionMaker(caller) - first_arg = fun.signature.split(',')[0] - src = 'def %s(%s): return _call_(caller, %s)' % ( - caller.__name__, first_arg, first_arg) - return fun.make(src, caller=caller, _call_=decorator_wrap) - -decorator.wrap = decorator_wrap -decorator.apply = decorator_apply + return fun.make(src, _func_=func, _call_=caller) ###################### deprecated functionality ######################### diff --git a/decorator/documentation.py b/decorator/documentation.py index a73693a..be2a3a2 100644 --- a/decorator/documentation.py +++ b/decorator/documentation.py @@ -4,8 +4,8 @@ The ``decorator`` module :author: Michele Simionato :E-mail: michele.simionato@gmail.com -:version: 3.0 (11 December 2008) -:Download page: http://pypi.python.org/decorator +:version: $VERSION ($DATE) +:Download page: http://pypi.python.org/pypi/decorator :Installation: ``easy_install decorator`` :License: BSD license @@ -88,6 +88,9 @@ A simple implementation for Python 2.5 could be the following: $$memoize25 +Notice that in general it is impossible to memoize correctly something +that depends on non-hashable arguments. + Here we used the ``functools.update_wrapper`` utility, which has been added in Python 2.5 to simplify the definition of decorators. @@ -141,10 +144,18 @@ and implements the tracing capability: $$_memoize -At this point you can define your decorator by means of ``decorator.wrap``: +At this point you can define your decorator by means of ``decorator``: $$memoize +The difference with respect to the Python 2.5 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 +decorate as first argument of the helper function, also know as +the *caller* function, since it calls the original function with the +given arguments. + Here is a test of usage: >>> @memoize @@ -163,9 +174,6 @@ The signature of ``heavy_computation`` is the one you would expect: >>> print getargspec(heavy_computation) ([], None, None, None) -Notice that in general it is impossible to memoize correctly something -that depends on mutable arguments. - A ``trace`` decorator ------------------------------------------------------ @@ -220,40 +228,35 @@ in Python 2.6 and removed in Python 3.0. ``decorator`` is a decorator --------------------------------------------- -The ``decorator`` module provides an easy shortcut to convert -the helper function into a signature-preserving decorator: the -``decorator`` function itself, which can be considered as a signature-changing +It may be annoying to be forced to write a caller function (like the ``_trace`` +function above) and then a trivial wrapper +(``def trace(f): return decorator(_trace, f)``) every time. For this reason, +the ``decorator`` module provides an easy shortcut to convert +the caller function into a signature-preserving decorator: +you can call ``decorator`` with a single argument and you will get out +your decorator: ``trace = decorator(_trace)``. +That means that the ``decorator`` function can be used as a signature-changing decorator, just as ``classmethod`` and ``staticmethod``. However, ``classmethod`` and ``staticmethod`` return generic objects which are not callable, while ``decorator`` returns signature-preserving decorators, i.e. functions of a single argument. -Therefore, you can write +For instance, you can write directly >>> @decorator -... def tracing(f, *args, **kw): +... def trace(f, *args, **kw): ... print "calling %s with args %s, %s" % (f.func_name, args, kw) ... return f(*args, **kw) -instead of - -.. code-block:: python - - def _tracing(f, *args, **kw): - 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: - def tracing(f): - return decorator.wrap(_tracing, f) - -We can easily check that the signature has changed: - ->>> print getargspec(tracing) +>>> print getargspec(trace) (['f'], None, None, None) -Therefore now ``tracing`` can be used as a decorator and +Therefore now ``trace`` can be used as a decorator and the following will work: ->>> @tracing +>>> @trace ... def func(): pass >>> func() @@ -440,18 +443,55 @@ interface requirements for (more stringent) inheritance requirements. .. _I generally dislike inheritance: http://stacktrace.it/articoli/2008/06/i-pericoli-della-programmazione-con-i-mixin1 -Dealing with third party decorators: ``decorator.apply`` ------------------------------------------------------------- +The ``FunctionMaker`` class +--------------------------------------------------------------- + +The public API of the ``decorator`` module consists in the +``decorator`` function and its two attributes ``decorator`` and +``decorator_apply``. Internally, the functionality is implemented via +a ``FunctionMaker`` class which is able to generate on the fly +functions with a given name and signature. You should not need to +resort to ``FunctionMaker`` when writing ordinary decorators, but it +is interesting to know how the module works internally, so I have +decided to add this paragraph. Notice that while I do not have plan +to change or remove the functionality provided in the +``FunctionMaker`` class, I do not guarantee that it will stay +unchanged forever. On the other hand, the functionality provided by +``decorator`` has been there from version 0.1 and it is guaranteed to +stay there forever. +``FunctionMaker`` takes the name and the signature of a function in +input, or a whole function. Here is an example of how to +restrict the signature of a function: + +>>> def f(*args, **kw): +... print args, kw + +>>> f1 = FunctionMaker(name="f1", signature="a,b").make(''' +... def %(name)s(%(signature)s): +... f(%(signature)s)''', f=f) + +>>> f1(1,2) +(1, 2) {} Sometimes you find on the net some cool decorator that you would like to include in your code. However, more often than not the cool 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``. To this aim the -``decorator`` module provides an utility function -``decorator.apply(third_party_decorator, func)``. +having to rewrite them in terms of ``decorator``. You can use a +``FunctionMaker`` to implement that functionality as follows: + +$$decorator_apply -In order to give an example of usage, I will show a +Notice that I am not providing this functionality in the ``decorator`` +module directly since I think it is best to rewrite a decorator rather +than adding an additional level of indirection. However, practicality +beats purity, so you can add ``decorator_apply`` to your toolbox and +use it if you need to. + +``tail-recursive`` +------------------------------------------------------------ + +In order to give an example of usage of ``decorator_apply``, I will show a 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, @@ -530,7 +570,7 @@ a penalty in your specific use case is to measure it. You should be aware that decorators will make your tracebacks longer and more difficult to understand. Consider this example: ->>> @tracing +>>> @trace ... def f(): ... 1/0 @@ -542,13 +582,13 @@ Traceback (most recent call last): File "<stdin>", line 1, in ? f() File "<string>", line 2, in f - File "<stdin>", line 4, in tracing + File "<stdin>", line 4, in trace return f(*args, **kw) File "<stdin>", line 3, in f 1/0 ZeroDivisionError: integer division or modulo by zero -You see here the inner call to the decorator ``tracing``, which calls +You see here the inner call to the decorator ``trace``, which calls ``f(*args, **kw)``, and a reference to ``File "<string>", line 2, in f``. This latter reference is due to the fact that internally the decorator module uses ``exec`` to generate the decorated function. Notice that @@ -579,7 +619,7 @@ a ``__source__`` attribute to the decorated function, therefore you can get the code which is executed: >>> print f.__source__ -# _call_=<function __main__.tracing> +# _call_=<function __main__.trace> # _func_=<function __main__.f> def f(): return _call_(_func_, ) @@ -610,14 +650,14 @@ callable objects, nor on built-in functions, due to limitations of the Moreover, you can decorate anonymous functions: ->>> tracing(lambda : None)() +>>> trace(lambda : None)() calling <lambda> with args (), {} There is a restriction on the names of the arguments: for instance, if try to call an argument ``_call_`` or ``_func_`` you will get a ``NameError``: ->>> @tracing +>>> @trace ... def f(_func_): print f ... Traceback (most recent call last): @@ -633,7 +673,7 @@ a copy of the original function attributes: >>> f.attr1 = "something" # setting an attribute >>> f.attr2 = "something else" # setting another attribute ->>> traced_f = tracing(f) # the decorated function +>>> traced_f = trace(f) # the decorated function >>> traced_f.attr1 'something' @@ -641,41 +681,6 @@ a copy of the original function attributes: >>> f.attr2 # the original attribute did not change 'something else' -The ``FunctionMaker`` class ---------------------------------------------------------------- - -The public API of the ``decorator`` module consists in the -``decorator`` function and its two attributes ``decorator.wrap`` and -``decorator.apply``. Internally, the functionality is implemented via -a ``FunctionMaker`` class which is able to generate on the fly -functions with a given name and signature. You should not need to -resort to ``FunctionMaker`` when writing ordinary decorators, but it -is interesting to know how the module works internally, so I have -decided to add this paragraph. Notice that while I do not have plan -to change or remove the functionality provided in the -``FunctionMaker`` class, I do not guarantee that it will stay -unchanged forever. On the other hand, the functionality provided by -``decorator`` has been there from version 0.1 and it is guaranteed to -stay there forever. -``FunctionMaker`` takes the name and the signature of a function in -input, or a whole function. Here is an example of how to -restrict the signature of a function: - ->>> def f(*args, **kw): -... print args, kw - ->>> f1 = FunctionMaker(name="f1", signature="a,b").make(''' -... def %(name)s(%(signature)s): -... f(%(signature)s)''', f=f) - ->>> f1(1,2) -(1, 2) {} - -The utility ``decorator.wrap`` instead takes a function in input and -returns a new function; it is defined as follows: - -$$decorator_wrap - Backward compatibility notes --------------------------------------------------------------- @@ -689,7 +694,7 @@ in the future. For the moment, using them raises a ``DeprecationWarning``. to be changed anyway to work with Python 3.0; ``new_wrapper`` has been removed since it was useless: its major use case (converting signature changing decorators to signature preserving decorators) -has been subsumed by ``decorator.apply`` +has been subsumed by ``decorator_apply`` and the other use case can be managed with the ``FunctionMaker``. Finally ``decorator`` cannot be used as a class decorator and the @@ -737,15 +742,25 @@ you are unhappy with it, send me a patch! from __future__ import with_statement import sys, threading, time, functools from decorator import * +from setup import VERSION + +today = time.strftime('%Y-%m-%d') + +__doc__ = __doc__.replace('$VERSION', VERSION).replace('$DATE', today) -decorator_wrap = decorator.wrap +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, decorated=dec(func)) def _trace(f, *args, **kw): print "calling %s with args %s, %s" % (f.func_name, args, kw) return f(*args, **kw) def trace(f): - return decorator.wrap(_trace, f) + return decorator(_trace, f) def delayed(nsec): def _delayed(proc, *args, **kw): @@ -762,13 +777,28 @@ def identity_dec(func): @identity_dec def example(): pass +def memoize25(func): + func.cache = {} + def memoize(*args, **kw): + if kw: + key = args, frozenset(kw.items()) + 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(func, *args, **kw): # args and kw must be hashable if kw: - key = args, frozenset(kw.items()) + key = args, frozenset(kw.items()) else: - key = args - cache = func.cache # created at decoration time + key = args + cache = func.cache if key in cache: return cache[key] else: @@ -777,22 +807,7 @@ def _memoize(func, *args, **kw): def memoize(f): f.cache = {} - return decorator.wrap(_memoize, f) - -def memoize25(func): - func.cache = {} - def memoize(*args, **kw): - if kw: - key = args, frozenset(kw.items()) - else: - key = args - cache = func.cache # created at decoration time - if key in cache: - return cache[key] - else: - cache[key] = result = func(*args, **kw) - return result - return functools.update_wrapper(memoize, func) + return decorator(_memoize, f) threaded = delayed(0) # no-delay decorator @@ -847,7 +862,7 @@ class Restricted(object): '%s does not have the permission to run %s!' % (userclass.__name__, func.__name__)) def __call__(self, func): - return decorator.wrap(self.call, func) + return decorator(self.call, func) class Action(object): @Restricted(User) @@ -894,7 +909,7 @@ class TailRecursive(object): return result def tail_recursive(func): - return decorator.apply(TailRecursive, func) + return decorator_apply(TailRecursive, func) @tail_recursive def factorial(n, acc=1): diff --git a/decorator/setup.py b/decorator/setup.py index e6240f6..3b18292 100644 --- a/decorator/setup.py +++ b/decorator/setup.py @@ -3,35 +3,36 @@ try: except ImportError: from distutils.core import setup -setup(name='decorator', - version='2.3.2', - description=\ - 'Better living through Python with decorators.', - long_description="""\ -As of now, writing custom decorators correctly requires some experience -and it is not as easy as it could be. For instance, typical implementations -of decorators involve nested functions, and we all know -that flat is better than nested. Moreover, typical implementations -of decorators do not preserve the signature of decorated functions, -thus confusing both documentation tools and developers. +VERSION = '3.0.0' -The aim of the decorator module it to simplify the usage of decorators -for the average programmer, and to popularize decorators usage giving -examples of useful decorators, such as memoize, tracing, -redirecting_stdout, locked, etc.""", - author='Michele Simionato', - author_email='michele.simionato@gmail.com', - url='http://www.phyast.pitt.edu/~micheles/python/documentation.html', - license="BSD License", - py_modules = ['decorator'], - keywords="decorators generic utility", - classifiers=['Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities'], - zip_safe=False) +if __name__ == '__main__': + setup(name='decorator', + version=VERSION, + description='Better living through Python with decorators', + long_description="""\ + As of now, writing custom decorators correctly requires some experience + and it is not as easy as it could be. For instance, typical implementations + of decorators involve nested functions, and we all know + that flat is better than nested. Moreover, typical implementations + of decorators do not preserve the signature of decorated functions, + thus confusing both documentation tools and developers. + + The aim of the decorator module it to simplify the usage of decorators + for the average programmer, and to popularize decorators usage giving + examples of useful decorators, such as memoize, tracing, threaded, etc.""", + author='Michele Simionato', + author_email='michele.simionato@gmail.com', + url='http://pypi.python.org/pypi/decorator', + license="BSD License", + py_modules = ['decorator'], + keywords="decorators generic utility", + classifiers=['Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities'], + zip_safe=False) |