summaryrefslogtreecommitdiff
path: root/src/tests
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2015-07-23 07:03:48 +0200
committerMichele Simionato <michele.simionato@gmail.com>2015-07-23 07:03:48 +0200
commit80072c50a765547736c03810cfb3e4f5afcb928d (patch)
tree72e254b8b8b8f542286c4f30d7dd121f9512eca9 /src/tests
parent14b603738eed475281a38350bfaf0df5bca5f3c8 (diff)
downloadpython-decorator-git-80072c50a765547736c03810cfb3e4f5afcb928d.tar.gz
Changed the implementation of generic functions
Diffstat (limited to 'src/tests')
-rw-r--r--src/tests/documentation.py80
-rw-r--r--src/tests/test.py45
2 files changed, 44 insertions, 81 deletions
diff --git a/src/tests/documentation.py b/src/tests/documentation.py
index f4aa995..6ce800b 100644
--- a/src/tests/documentation.py
+++ b/src/tests/documentation.py
@@ -782,6 +782,11 @@ then ``get_length`` must be defined on ``WithLength`` instances:
>>> get_length(WithLength())
0
+You can find the virtual ancestors of a given set of classes as follows:
+
+ >> get_length.vancestors(WithLength,)
+ [[<class 'collections.abc.Sized'>]]
+
Of course this is a contrived example since you could just use the
builtin ``len``, but you should get the idea.
@@ -809,69 +814,43 @@ as a virtual ancestor):
Now, let us define an implementation of ``get_length`` specific to set:
-.. code-block:: python
-
- >>> @get_length.register(collections.Set)
- ... def get_length_set(obj):
- ... return 1
+$$get_length_set
-The current implementation first check in the MRO and then look
-for virtual ancestors; since ``SomeSet`` inherits directly
-from ``collections.Sized`` that implementation is found first:
+The current implementation, as the one used by ``functools.singledispatch``,
+is able to discern that a ``Set`` is a ``Sized`` object, so the
+implementation for ``Set`` is taken:
.. code-block:: python
>>> get_length(SomeSet())
- 0
-
-Generic functions implemented via ``functools.singledispatch`` use
-a more sophisticated lookup algorithm; in particular they are able
-to discern that a ``Set`` is a ``Sized`` object, so the
-implementation for ``Set`` is taken and the result is 1, not 0.
-Still, the implementation in the decorator module is easy to
-undestand, once one declare that real ancestors take the precedence
-over virtual ancestors and the problem can be solved anyway by
-subclassing. As a matter of fact, if we define a subclass
-
-$$SomeSet2
-
-which inherits from ``collections.Set``, we get as expected
-
-.. code-block:: python
-
- >>> get_length(SomeSet2())
- 1
-
-consistently with the method resolution order, with ``Set`` having the
-precedence with respect to ``Sized``:
-
-.. code-block:: python
-
- >>> [c.__name__ for c in SomeSet2.mro()]
- ['SomeSet2', 'SomeSet', 'Set', 'Sized', 'Iterable', 'Container', 'object']
+ Traceback (most recent call last):
+ ...
+ TypeError: Cannot create a consistent method resolution
+ order (MRO) for bases Sized, Set
-The functions implemented via ``functools.singledispatch``
-are smarter when there are conflicting implementations and are
-able to solve more potential conflicts. Just to have an idea
-of what I am talking about, here is a situation with a conflict:
+Sometimes it is impossible to find the right implementation. Here is a
+situation with a type conflict. First of all, let us register
.. code-block:: python
- >>> _ = collections.Iterable.register(WithLength)
>>> @get_length.register(collections.Iterable)
... def get_length_iterable(obj):
... raise TypeError('Cannot get the length of an iterable')
- >>> get_length(WithLength())
- Traceback (most recent call last):
- ...
- RuntimeError: Ambiguous dispatch for WithLength instance: Sized or Iterable?
-Since ``WithLength`` is both a (virtual) subclass
+
+Since ``SomeSet`` is now both a (virtual) subclass
of ``collections.Iterable`` and of ``collections.Sized``, which are
not related by subclassing, it is impossible
to decide which implementation should be taken. Consistently with
the *refuse the temptation to guess* philosophy, an error is raised.
-``functools.singledispatch`` would work exactly the same in this case.
+
+ >>> get_length(SomeSet())
+ Traceback (most recent call last):
+ ...
+ TypeError: Cannot create a consistent method resolution
+ order (MRO) for bases Iterable, Sized, Set
+
+``functools.singledispatch`` would raise a similar error in this case.
Finally let me notice that the decorator module implementation does
not use any cache, whereas the one in ``singledispatch`` has a cache.
@@ -1474,9 +1453,6 @@ def get_length_sized(obj):
return len(obj)
-class SomeSet2(SomeSet, collections.Set):
- def __contains__(self, a):
- return True
-
- def __iter__(self):
- yield 1
+@get_length.register(collections.Set)
+def get_length_set(obj):
+ return 1
diff --git a/src/tests/test.py b/src/tests/test.py
index cbb8373..af5998b 100644
--- a/src/tests/test.py
+++ b/src/tests/test.py
@@ -20,7 +20,7 @@ def assertRaises(etype):
except etype:
pass
else:
- raise Exception('Expected %s' % etype)
+ raise Exception('Expected %s' % etype.__name__)
class DocumentationTestCase(unittest.TestCase):
@@ -243,16 +243,6 @@ class TestSingleDispatch(unittest.TestCase):
self.assertEqual(g(s), "concrete-set")
self.assertEqual(g(f), "frozen-set")
self.assertEqual(g(t), "tuple")
- if hasattr(c, 'ChainMap'):
- self.assertEqual(
- [abc.__name__ for abc in g.vancestors[0]],
- ['ChainMap', 'MutableMapping', 'MutableSequence', 'MutableSet',
- 'Mapping', 'Sequence', 'Set', 'Sized'])
- else:
- self.assertEqual(
- [abc.__name__ for abc in g.vancestors[0]],
- ['MutableMapping', 'MutableSequence', 'MutableSet',
- 'Mapping', 'Sequence', 'Set', 'Sized'])
def test_mro_conflicts(self):
c = collections
@@ -272,13 +262,12 @@ class TestSingleDispatch(unittest.TestCase):
g.register(c.Set)(lambda arg: "set")
self.assertEqual(g(o), "sized")
c.Iterable.register(O)
- self.assertEqual(g(o), "sized") # because it's explicitly in __mro__
+ self.assertEqual(g(o), "sized")
c.Container.register(O)
- self.assertEqual(g(o), "sized") # see above: Sized is in __mro__
- c.Set.register(O)
self.assertEqual(g(o), "sized")
- # could be set because c.Set is a subclass of
- # c.Sized and c.Container
+ c.Set.register(O)
+ with assertRaises(TypeError): # was ok
+ self.assertEqual(g(o), "set")
class P(object):
pass
@@ -288,8 +277,8 @@ class TestSingleDispatch(unittest.TestCase):
self.assertEqual(g(p), "iterable")
c.Container.register(P)
- with assertRaises(RuntimeError):
- g(p)
+ #with assertRaises(RuntimeError):
+ self.assertEqual(g(p), "iterable")
class Q(c.Sized):
def __len__(self):
@@ -297,9 +286,9 @@ class TestSingleDispatch(unittest.TestCase):
q = Q()
self.assertEqual(g(q), "sized")
c.Iterable.register(Q)
- self.assertEqual(g(q), "sized") # because it's explicitly in __mro__
- c.Set.register(Q)
self.assertEqual(g(q), "sized")
+ c.Set.register(Q)
+ # self.assertEqual(g(q), "sized")
# could be because c.Set is a subclass of
# c.Sized and c.Iterable
@@ -318,8 +307,8 @@ class TestSingleDispatch(unittest.TestCase):
# this ABC is implicitly registered on defaultdict which makes all of
# MutableMapping's bases implicit as well from defaultdict's
# perspective.
- with assertRaises(RuntimeError):
- h(c.defaultdict(lambda: 0))
+ #with assertRaises(RuntimeError):
+ h(c.defaultdict(lambda: 0))
class R(c.defaultdict):
pass
@@ -337,10 +326,9 @@ class TestSingleDispatch(unittest.TestCase):
def i_sequence(arg):
return "sequence"
r = R()
- with assertRaises(RuntimeError): # not for standardlib
- self.assertEqual(i(r), "sequence")
+ self.assertEqual(i(r), "mapping") # was sequence
- class S:
+ class S(object):
pass
class T(S, c.Sized):
@@ -351,7 +339,7 @@ class TestSingleDispatch(unittest.TestCase):
c.Container.register(T)
self.assertEqual(h(t), "sized") # because it's explicitly in the MRO
- class U:
+ class U(object):
def __len__(self):
return 0
u = U()
@@ -361,9 +349,8 @@ class TestSingleDispatch(unittest.TestCase):
# from the existence of __len__()
c.Container.register(U)
- # There is no preference for registered versus inferred ABCs.
- with assertRaises(RuntimeError):
- h(u)
+ # There is preference for registered versus inferred ABCs.
+ self.assertEqual(h(u), "sized") # was conflict
class V(c.Sized, S):
def __len__(self):