diff options
| author | Michele Simionato <michele.simionato@gmail.com> | 2015-07-20 11:12:42 +0200 |
|---|---|---|
| committer | Michele Simionato <michele.simionato@gmail.com> | 2015-07-20 11:12:42 +0200 |
| commit | cb0effeeb8800abf028c89a392684cb599fd8259 (patch) | |
| tree | 6ed2d8326d82acab2181afbb751065c8d84c2380 /documentation.rst | |
| parent | cc752587247ffe103bacfa86e84b5797959cd845 (diff) | |
| download | python-decorator-git-cb0effeeb8800abf028c89a392684cb599fd8259.tar.gz | |
Multidispatch, initial implementation
Diffstat (limited to 'documentation.rst')
| -rw-r--r-- | documentation.rst | 167 |
1 files changed, 155 insertions, 12 deletions
diff --git a/documentation.rst b/documentation.rst index 81161dd..2e89ca0 100644 --- a/documentation.rst +++ b/documentation.rst @@ -23,7 +23,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 @@ -33,14 +33,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 ------------------------------------------------ @@ -361,9 +372,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``. @@ -801,6 +814,136 @@ are not tail recursive, such as the following 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: + +.. code-block:: python + + class XMLWriter(object): + def __init__(self, **config): + self.cfg = config + + @dispatch_on('obj') + def write(self, obj): + raise NotImplementedError(type(obj)) + + +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: + +.. code-block:: python + + @XMLWriter.write.register(float) + def writefloat(self, obj): + return '<float>%s</float>' % obj + + +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: + +.. code-block:: python + + class Rock(object): + ordinal = 0 + +.. code-block:: python + + class Paper(object): + ordinal = 1 + +.. code-block:: python + + class Scissor(object): + ordinal = 2 + +.. code-block:: python + + @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))) + +.. code-block:: python + + @win.register(Rock, Paper) + def winRockPaper(a, b): + return -1 + +.. code-block:: python + + @win.register(Paper, Scissor) + def winPaperScissor(a, b): + return -1 + +.. code-block:: python + + @win.register(Rock, Scissor) + def winRockScissor(a, b): + return 1 + + +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 ------------------------------------------- |
