summaryrefslogtreecommitdiff
path: root/documentation.py
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2015-07-20 11:12:42 +0200
committerMichele Simionato <michele.simionato@gmail.com>2015-07-20 11:12:42 +0200
commitcb0effeeb8800abf028c89a392684cb599fd8259 (patch)
tree6ed2d8326d82acab2181afbb751065c8d84c2380 /documentation.py
parentcc752587247ffe103bacfa86e84b5797959cd845 (diff)
downloadpython-decorator-git-cb0effeeb8800abf028c89a392684cb599fd8259.tar.gz
Multidispatch, initial implementation
Diffstat (limited to 'documentation.py')
-rw-r--r--documentation.py176
1 files changed, 163 insertions, 13 deletions
diff --git a/documentation.py b/documentation.py
index 363c67b..53b42cd 100644
--- a/documentation.py
+++ b/documentation.py
@@ -25,7 +25,7 @@ decision made it possible to use a single code base both for Python
2.X and Python 3.X. This is a *huge* bonus, since I could remove over
2,000 lines of duplicated documentation. Having to maintain separate
docs for Python 2 and Python 3 effectively stopped any development on
-the module for several years. Moreover, it is now trivial to
+the module for several years. Moreover, it is now trivial to
distribute the module as a wheel since 2to3 is no more required.
This version supports all Python releases from 2.6 up to 3.5. If
@@ -35,14 +35,25 @@ decorator module version 3.4.2.
What's new
---------------------
-By leveraging on the fact that now there is a single manual
-for all Python versions, the documentation has been overhauled.
-Even if you are an old time user of the module, you may want to read
-the manual again, since several examples have been improved.
-A new utility function ``decorate(func, caller)` has been
-added, doing the same job that in the past was done by
-``decorator(caller, func)``. The old functionality is still there for
-compatibility sake, but it is deprecated and not documented anymore.
+Since now there is a single manual for all Python versions, I took the
+occasion for overhauling the documentation. Therefore, even if you are
+an old time user, you may want to read the manual again, since several
+examples have been improved. A new utility function ``decorate(func,
+caller)` has been added, doing the same job that in the past was done
+by ``decorator(caller, func)``. The old functionality is still there
+for compatibility sake, but it is deprecated and not documented
+anymore.
+
+Apart from that, there are no changes. There is a new experimental
+feature, though. The decorator module now include an implementation
+of generic (multiple dispatch) functions. The API is designed to
+mimic the one of `functools.singledispatch` but the implementation
+is much simpler and more general; moreover it preserves the signature of
+the decorated functions. For the moment it is there to exemplify
+the power of the module. In the future it could change and/or be
+enhanced/optimized; on the other hand, it could even become
+deprecated. Such is the fate of experimental features. In any case
+it is only 40 lines of code. Take it as food for thought.
Usefulness of decorators
------------------------------------------------
@@ -279,9 +290,11 @@ the ``decorator`` module provides an easy shortcut to convert
the caller function into a signature-preserving decorator: the
``decorator`` function:
->>> from decorator import decorator
->>> print(decorator.__doc__)
-decorator(caller) converts a caller function into a decorator
+.. code-block:: python
+
+ >>> from decorator import decorator
+ >>> print(decorator.__doc__)
+ decorator(caller) converts a caller function into a decorator
The ``decorator`` function can be used as a signature-changing
decorator, just as ``classmethod`` and ``staticmethod``.
@@ -616,6 +629,92 @@ $$fact
making a recursive call, or returns directly the result of a recursive
call).
+Multiple dispatch
+-------------------------------------------
+
+There has been talk of implementing multiple dispatch (i.e. generic)
+functions in Python for over ten years. Last year for the first time
+something was done and now in Python 3.4 we have a decorator
+`functools.singledispatch` which can be used to implement generic
+functions. As the name implies, it has the restriction of being
+limited to single dispatch, i.e. it is able to dispatch on the first
+argument of the function only. The decorator module provide a
+decorator factory `dispatch_on` which can be used to implement generic
+functions dispatching on any argument; moreover it can manage
+dispatching on more than one argument; of course it is
+signature-preserving too.
+
+Here I will give a very concrete example where it is desiderable to
+dispatch on the second argument. Suppose you have an XMLWriter class,
+which is instantiated with some configuration parameters and has
+a `.write` method which is able to serialize objects to XML:
+
+$$XMLWriter
+
+Here you want to dispatch on the second argument since the first, `self`
+is already taken. The `dispatch_on` facility allows you to specify
+the dispatch argument by simply passing its name as a string (notice
+that if you mispell the name you will get an error). The function
+decorated with `dispatch_on` is turned into a generic function
+and it is the one which is called if there are no more specialized
+implementations. Usually such default function should raise a
+NotImplementedError, forcing peope to register some implementation.
+The registration can be done as a decorator:
+
+$$writefloat
+
+Now the XMLWriter is able to serialize floats:
+
+.. code-block:: python
+
+ >>> writer = XMLWriter()
+ >>> writer.write(2.3)
+ '<float>2.3</float>'
+
+I could give a down-to-earth example of situations in which it is desiderable
+to dispatch on more than one argument (for instance once I implemented
+a database-access library where the first dispatching argument was the
+the database driver and the second the database record), but here I prefer
+to follow the old tradition and show the time-honored
+Rock-Paper-Scissor example:
+
+$$Rock
+$$Paper
+$$Scissor
+
+I have added an ordinal to the Rock-Paper-Scissor classes to
+simplify the implementation of the generic function. There are
+9 combinations, however combinations with the same ordinal
+correspond to parity. Moreover by exchanging.
+
+$$win
+$$winRockPaper
+$$winPaperScissor
+$$winRockScissor
+
+Here is the result:
+
+.. code-block:: python
+
+ >>> win(Paper(), Rock())
+ 1
+ >>> win(Scissor(), Paper())
+ 1
+ >>> win(Rock(), Scissor())
+ 1
+ >>> win(Paper(), Paper())
+ 0
+ >>> win(Rock(), Rock())
+ 0
+ >>> win(Scissor(), Scissor())
+ 0
+ >>> win(Rock(), Paper())
+ -1
+ >>> win(Paper(), Scissor())
+ -1
+ >>> win(Scissor(), Rock())
+ -1
+
Caveats and limitations
-------------------------------------------
@@ -826,7 +925,8 @@ import time
import functools
import itertools
from setup import VERSION
-from decorator import decorator, decorate, FunctionMaker, contextmanager
+from decorator import (decorator, decorate, FunctionMaker, contextmanager,
+ dispatch_on)
if sys.version < '3':
function_annotations = ''
@@ -1116,3 +1216,53 @@ def hello(user):
AFTER
"""
print('hello %s' % user)
+
+
+class XMLWriter(object):
+ def __init__(self, **config):
+ self.cfg = config
+
+ @dispatch_on('obj')
+ def write(self, obj):
+ raise NotImplementedError(type(obj))
+
+
+@XMLWriter.write.register(float)
+def writefloat(self, obj):
+ return '<float>%s</float>' % obj
+
+
+class Rock(object):
+ ordinal = 0
+
+
+class Paper(object):
+ ordinal = 1
+
+
+class Scissor(object):
+ ordinal = 2
+
+
+@dispatch_on('a', 'b')
+def win(a, b):
+ if a.ordinal == b.ordinal:
+ return 0
+ elif a.ordinal > b.ordinal:
+ return -win(b, a)
+ raise NotImplementedError((type(a), type(b)))
+
+
+@win.register(Rock, Paper)
+def winRockPaper(a, b):
+ return -1
+
+
+@win.register(Rock, Scissor)
+def winRockScissor(a, b):
+ return 1
+
+
+@win.register(Paper, Scissor)
+def winPaperScissor(a, b):
+ return -1