diff options
| author | Michele Simionato <michele.simionato@gmail.com> | 2015-07-23 07:03:48 +0200 |
|---|---|---|
| committer | Michele Simionato <michele.simionato@gmail.com> | 2015-07-23 07:03:48 +0200 |
| commit | 80072c50a765547736c03810cfb3e4f5afcb928d (patch) | |
| tree | 72e254b8b8b8f542286c4f30d7dd121f9512eca9 /src/tests | |
| parent | 14b603738eed475281a38350bfaf0df5bca5f3c8 (diff) | |
| download | python-decorator-git-80072c50a765547736c03810cfb3e4f5afcb928d.tar.gz | |
Changed the implementation of generic functions
Diffstat (limited to 'src/tests')
| -rw-r--r-- | src/tests/documentation.py | 80 | ||||
| -rw-r--r-- | src/tests/test.py | 45 |
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): |
