summaryrefslogtreecommitdiff
path: root/documentation.rst
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2015-07-21 16:43:59 +0200
committerMichele Simionato <michele.simionato@gmail.com>2015-07-21 16:43:59 +0200
commit10e035eaf93997a63a30687f6fe33b918714ba61 (patch)
tree7bc04aceba74e15306e54b542414cdda99291de1 /documentation.rst
parent5e4bc16af3f5d0e6eca3ac95ddb89ef4331c8fb8 (diff)
downloadpython-decorator-git-10e035eaf93997a63a30687f6fe33b918714ba61.tar.gz
First version of dispatch_on
Diffstat (limited to 'documentation.rst')
-rw-r--r--documentation.rst89
1 files changed, 76 insertions, 13 deletions
diff --git a/documentation.rst b/documentation.rst
index 054c180..107055b 100644
--- a/documentation.rst
+++ b/documentation.rst
@@ -162,10 +162,12 @@ Consider for instance the following case:
.. code-block:: python
- >>> @memoize_uw
- ... def f1(x):
- ... time.sleep(1) # simulate some long computation
- ... return x
+ @memoize_uw
+ def f1(x):
+ "Simulate some long computation"
+ time.sleep(1)
+ return x
+
Here the original function takes a single argument named ``x``,
but the decorated function takes any number of arguments and
@@ -178,7 +180,8 @@ keyword arguments:
ArgSpec(args=[], varargs='args', keywords='kw', defaults=None)
This means that introspection tools such as *pydoc* will give
-wrong informations about the signature of ``f1``. This is pretty bad:
+wrong informations about the signature of ``f1``, unless you are
+using a recent of Python 3.X. This is pretty bad:
*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:
@@ -190,6 +193,12 @@ argument, you will get an error:
...
TypeError: f1() takes exactly 1 positional argument (2 given)
+Notice even in Python 3.5 `inspect.getargspec` and
+`inspect.getfullargspec` (which are deprecated in that release) will
+give the wrong signature. `inspect.signature` will return the right
+signature on the surface.
+
+
The solution
-----------------------------------------
@@ -670,11 +679,13 @@ source code which is probably not what you want:
return func(*args, **kw)
return wrapper
-
.. code-block:: python
- @identity_dec
- def example(): pass
+ def wrapper(*args, **kw):
+ return func(*args, **kw)
+
+
+.. code-block:: python
>>> import inspect
>>> print(inspect.getsource(example))
@@ -829,8 +840,8 @@ 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.
+dispatching on more than one argument and, of course, it is
+signature-preserving.
Here I will give a very concrete example where it is desiderable to
dispatch on the second argument. Suppose you have an XMLWriter class,
@@ -855,7 +866,7 @@ 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.
+`NotImplementedError`, thus forcing people to register some implementation.
The registration can be done with a decorator:
.. code-block:: python
@@ -901,11 +912,11 @@ the implementation. The idea is to define a generic function `win(a,
b)` of two arguments corresponding to the moves of the first and
second player respectively. The moves are instances of the classes
Rock, Paper and Scissors; Paper wins over Rock, Scissor wins over
-Paper and Rock wins over Scissor. The function with return +1 for a
+Paper and Rock wins over Scissor. The function will return +1 for a
win, -1 for a loss and 0 for parity. There are 9 combinations, however
combinations with the same ordinal (i.e. the same class) return 0;
moreover by exchanging the order of the arguments the sign of the
-result changes, so it is enough to specify only 3 direct
+result changes, so it is enough to specify directly only 3
implementations:
.. code-block:: python
@@ -960,6 +971,58 @@ Here is the result:
>>> win(Scissor(), Rock())
-1
+Generic functions implementations in Python are
+complicated by the existence of "virtual ancestors", i.e. superclasses
+which are not in the class hierarchy.
+Consider for instance this class:
+
+.. code-block:: python
+
+ class WithLength(object):
+ def __len__(self):
+ return 0
+
+
+This class defines a `__len__` method and as such is
+considered to be a subclass of the abstract base class `Sized`:
+
+>>> issubclass(WithLength, collections.Sized)
+True
+
+However, `collections.Sized` is not an ancestor of `WithLenght`.
+Any implementation of generic functions, even
+with single dispatch, must go through some contorsion to take into
+account the virtual ancestors.
+
+In particular if we define a generic function
+
+.. code-block:: python
+
+ @dispatch_on('obj')
+ def get_length(obj):
+ raise NotImplementedError(type(obj))
+
+
+implemented on all classes with a lenght
+
+.. code-block:: python
+
+ @get_length.register(collections.Sized)
+ def get_length_sized(obj):
+ return len(obj)
+
+
+then `get_length` must be defined on `WithLength` instances:
+
+>>> get_length(WithLength())
+0
+
+The implementation of generic functions in the decorator module is
+marked as experimental because it may
+fail in some corner cases. Also, the implementation does not even
+attempt to use a cache, so it is not as fast as it could be.
+Simplicity was the paramount concern of this implementation.
+
Caveats and limitations
-------------------------------------------