<div dir="ltr">Any chance you could move your definitions for "generic function" and "single dispatch" to the glossary and just link to them here?<br><div class="gmail_extra"><br><br><div class="gmail_quote">

On Wed, Jun 5, 2013 at 6:20 AM, lukasz.langa <span dir="ltr"><<a href="mailto:python-checkins@python.org" target="_blank">python-checkins@python.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

<a href="http://hg.python.org/cpython/rev/dfcb64f51f7b" target="_blank">http://hg.python.org/cpython/rev/dfcb64f51f7b</a><br>
changeset:   84039:dfcb64f51f7b<br>
user:        Łukasz Langa <<a href="mailto:lukasz@langa.pl">lukasz@langa.pl</a>><br>
date:        Wed Jun 05 12:20:24 2013 +0200<br>
summary:<br>
  Add reference implementation for PEP 443<br>
<br>
PEP accepted: <a href="http://mail.python.org/pipermail/python-dev/2013-June/126734.html" target="_blank">http://mail.python.org/pipermail/python-dev/2013-June/126734.html</a><br>
<br>
files:<br>
  Doc/library/functools.rst  |  110 +++++++<br>
  Lib/functools.py           |  128 ++++++++-<br>
  Lib/pkgutil.py             |   52 +---<br>
  Lib/test/test_functools.py |  374 ++++++++++++++++++++++++-<br>
  Misc/NEWS                  |    3 +<br>
  Modules/Setup.dist         |    2 +-<br>
  6 files changed, 614 insertions(+), 55 deletions(-)<br>
<br>
<br>
diff --git a/Doc/library/functools.rst b/Doc/library/functools.rst<br>
--- a/Doc/library/functools.rst<br>
+++ b/Doc/library/functools.rst<br>
@@ -6,6 +6,7 @@<br>
 .. moduleauthor:: Peter Harris <<a href="mailto:scav@blueyonder.co.uk">scav@blueyonder.co.uk</a>><br>
 .. moduleauthor:: Raymond Hettinger <<a href="mailto:python@rcn.com">python@rcn.com</a>><br>
 .. moduleauthor:: Nick Coghlan <<a href="mailto:ncoghlan@gmail.com">ncoghlan@gmail.com</a>><br>
+.. moduleauthor:: Łukasz Langa <<a href="mailto:lukasz@langa.pl">lukasz@langa.pl</a>><br>
 .. sectionauthor:: Peter Harris <<a href="mailto:scav@blueyonder.co.uk">scav@blueyonder.co.uk</a>><br>
<br>
 **Source code:** :source:`Lib/functools.py`<br>
@@ -186,6 +187,115 @@<br>
    *sequence* contains only one item, the first item is returned.<br>
<br>
<br>
+.. decorator:: singledispatch(default)<br>
+<br>
+   Transforms a function into a single-dispatch generic function.  A **generic<br>
+   function** is composed of multiple functions implementing the same operation<br>
+   for different types. Which implementation should be used during a call is<br>
+   determined by the dispatch algorithm.  When the implementation is chosen<br>
+   based on the type of a single argument, this is known as **single<br>
+   dispatch**.<br>
+<br>
+   To define a generic function, decorate it with the ``@singledispatch``<br>
+   decorator. Note that the dispatch happens on the type of the first argument,<br>
+   create your function accordingly::<br>
+<br>
+     >>> from functools import singledispatch<br>
+     >>> @singledispatch<br>
+     ... def fun(arg, verbose=False):<br>
+     ...     if verbose:<br>
+     ...         print("Let me just say,", end=" ")<br>
+     ...     print(arg)<br>
+<br>
+   To add overloaded implementations to the function, use the :func:`register`<br>
+   attribute of the generic function.  It is a decorator, taking a type<br>
+   parameter and decorating a function implementing the operation for that<br>
+   type::<br>
+<br>
+     >>> @fun.register(int)<br>
+     ... def _(arg, verbose=False):<br>
+     ...     if verbose:<br>
+     ...         print("Strength in numbers, eh?", end=" ")<br>
+     ...     print(arg)<br>
+     ...<br>
+     >>> @fun.register(list)<br>
+     ... def _(arg, verbose=False):<br>
+     ...     if verbose:<br>
+     ...         print("Enumerate this:")<br>
+     ...     for i, elem in enumerate(arg):<br>
+     ...         print(i, elem)<br>
+<br>
+   To enable registering lambdas and pre-existing functions, the<br>
+   :func:`register` attribute can be used in a functional form::<br>
+<br>
+     >>> def nothing(arg, verbose=False):<br>
+     ...     print("Nothing.")<br>
+     ...<br>
+     >>> fun.register(type(None), nothing)<br>
+<br>
+   The :func:`register` attribute returns the undecorated function which<br>
+   enables decorator stacking, pickling, as well as creating unit tests for<br>
+   each variant independently::<br>
+<br>
+     >>> @fun.register(float)<br>
+     ... @fun.register(Decimal)<br>
+     ... def fun_num(arg, verbose=False):<br>
+     ...     if verbose:<br>
+     ...         print("Half of your number:", end=" ")<br>
+     ...     print(arg / 2)<br>
+     ...<br>
+     >>> fun_num is fun<br>
+     False<br>
+<br>
+   When called, the generic function dispatches on the type of the first<br>
+   argument::<br>
+<br>
+     >>> fun("Hello, world.")<br>
+     Hello, world.<br>
+     >>> fun("test.", verbose=True)<br>
+     Let me just say, test.<br>
+     >>> fun(42, verbose=True)<br>
+     Strength in numbers, eh? 42<br>
+     >>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)<br>
+     Enumerate this:<br>
+     0 spam<br>
+     1 spam<br>
+     2 eggs<br>
+     3 spam<br>
+     >>> fun(None)<br>
+     Nothing.<br>
+     >>> fun(1.23)<br>
+     0.615<br>
+<br>
+   Where there is no registered implementation for a specific type, its<br>
+   method resolution order is used to find a more generic implementation.<br>
+   The original function decorated with ``@singledispatch`` is registered<br>
+   for the base ``object`` type, which means it is used if no better<br>
+   implementation is found.<br>
+<br>
+   To check which implementation will the generic function choose for<br>
+   a given type, use the ``dispatch()`` attribute::<br>
+<br>
+     >>> fun.dispatch(float)<br>
+     <function fun_num at 0x1035a2840><br>
+     >>> fun.dispatch(dict)    # note: default implementation<br>
+     <function fun at 0x103fe0000><br>
+<br>
+   To access all registered implementations, use the read-only ``registry``<br>
+   attribute::<br>
+<br>
+    >>> fun.registry.keys()<br>
+    dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,<br>
+              <class 'decimal.Decimal'>, <class 'list'>,<br>
+              <class 'float'>])<br>
+    >>> fun.registry[float]<br>
+    <function fun_num at 0x1035a2840><br>
+    >>> fun.registry[object]<br>
+    <function fun at 0x103fe0000><br>
+<br>
+   .. versionadded:: 3.4<br>
+<br>
+<br>
 .. function:: update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)<br>
<br>
    Update a *wrapper* function to look like the *wrapped* function. The optional<br>
diff --git a/Lib/functools.py b/Lib/functools.py<br>
--- a/Lib/functools.py<br>
+++ b/Lib/functools.py<br>
@@ -3,19 +3,24 @@<br>
 # Python module wrapper for _functools C module<br>
 # to allow utilities written in Python to be added<br>
 # to the functools module.<br>
-# Written by Nick Coghlan <ncoghlan at <a href="http://gmail.com" target="_blank">gmail.com</a>><br>
-# and Raymond Hettinger <python at <a href="http://rcn.com" target="_blank">rcn.com</a>><br>
-#   Copyright (C) 2006-2010 Python Software Foundation.<br>
+# Written by Nick Coghlan <ncoghlan at <a href="http://gmail.com" target="_blank">gmail.com</a>>,<br>
+# Raymond Hettinger <python at <a href="http://rcn.com" target="_blank">rcn.com</a>>,<br>
+# and Łukasz Langa <lukasz at <a href="http://langa.pl" target="_blank">langa.pl</a>>.<br>
+#   Copyright (C) 2006-2013 Python Software Foundation.<br>
 # See C source code for _functools credits/copyright<br>
<br>
 __all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',<br>
-           'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']<br>
+           'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial',<br>
+           'singledispatch']<br>
<br>
 try:<br>
     from _functools import reduce<br>
 except ImportError:<br>
     pass<br>
+from abc import get_cache_token<br>
 from collections import namedtuple<br>
+from types import MappingProxyType<br>
+from weakref import WeakKeyDictionary<br>
 try:<br>
     from _thread import RLock<br>
 except:<br>
@@ -354,3 +359,118 @@<br>
         return update_wrapper(wrapper, user_function)<br>
<br>
     return decorating_function<br>
+<br>
+<br>
+################################################################################<br>
+### singledispatch() - single-dispatch generic function decorator<br>
+################################################################################<br>
+<br>
+def _compose_mro(cls, haystack):<br>
+    """Calculates the MRO for a given class `cls`, including relevant abstract<br>
+    base classes from `haystack`.<br>
+<br>
+    """<br>
+    bases = set(cls.__mro__)<br>
+    mro = list(cls.__mro__)<br>
+    for needle in haystack:<br>
+        if (needle in bases or not hasattr(needle, '__mro__')<br>
+                            or not issubclass(cls, needle)):<br>
+            continue   # either present in the __mro__ already or unrelated<br>
+        for index, base in enumerate(mro):<br>
+            if not issubclass(base, needle):<br>
+                break<br>
+        if base in bases and not issubclass(needle, base):<br>
+            # Conflict resolution: put classes present in __mro__ and their<br>
+            # subclasses first. See test_mro_conflicts() in test_functools.py<br>
+            # for examples.<br>
+            index += 1<br>
+        mro.insert(index, needle)<br>
+    return mro<br>
+<br>
+def _find_impl(cls, registry):<br>
+    """Returns the best matching implementation for the given class `cls` in<br>
+    `registry`. Where there is no registered implementation for a specific<br>
+    type, its method resolution order is used to find a more generic<br>
+    implementation.<br>
+<br>
+    Note: if `registry` does not contain an implementation for the base<br>
+    `object` type, this function may return None.<br>
+<br>
+    """<br>
+    mro = _compose_mro(cls, registry.keys())<br>
+    match = None<br>
+    for t in mro:<br>
+        if match is not None:<br>
+            # If `match` is an ABC but there is another unrelated, equally<br>
+            # matching ABC. Refuse the temptation to guess.<br>
+            if (t in registry and not issubclass(match, t)<br>
+                              and match not in cls.__mro__):<br>
+                raise RuntimeError("Ambiguous dispatch: {} or {}".format(<br>
+                    match, t))<br>
+            break<br>
+        if t in registry:<br>
+            match = t<br>
+    return registry.get(match)<br>
+<br>
+def singledispatch(func):<br>
+    """Single-dispatch generic function decorator.<br>
+<br>
+    Transforms a function into a generic function, which can have different<br>
+    behaviours depending upon the type of its first argument. The decorated<br>
+    function acts as the default implementation, and additional<br>
+    implementations can be registered using the 'register()' attribute of<br>
+    the generic function.<br>
+<br>
+    """<br>
+    registry = {}<br>
+    dispatch_cache = WeakKeyDictionary()<br>
+    cache_token = None<br>
+<br>
+    def dispatch(typ):<br>
+        """generic_func.dispatch(type) -> <function implementation><br>
+<br>
+        Runs the dispatch algorithm to return the best available implementation<br>
+        for the given `type` registered on `generic_func`.<br>
+<br>
+        """<br>
+        nonlocal cache_token<br>
+        if cache_token is not None:<br>
+            current_token = get_cache_token()<br>
+            if cache_token != current_token:<br>
+                dispatch_cache.clear()<br>
+                cache_token = current_token<br>
+        try:<br>
+            impl = dispatch_cache[typ]<br>
+        except KeyError:<br>
+            try:<br>
+                impl = registry[typ]<br>
+            except KeyError:<br>
+                impl = _find_impl(typ, registry)<br>
+            dispatch_cache[typ] = impl<br>
+        return impl<br>
+<br>
+    def register(typ, func=None):<br>
+        """generic_func.register(type, func) -> func<br>
+<br>
+        Registers a new implementation for the given `type` on a `generic_func`.<br>
+<br>
+        """<br>
+        nonlocal cache_token<br>
+        if func is None:<br>
+            return lambda f: register(typ, f)<br>
+        registry[typ] = func<br>
+        if cache_token is None and hasattr(typ, '__abstractmethods__'):<br>
+            cache_token = get_cache_token()<br>
+        dispatch_cache.clear()<br>
+        return func<br>
+<br>
+    def wrapper(*args, **kw):<br>
+        return dispatch(args[0].__class__)(*args, **kw)<br>
+<br>
+    registry[object] = func<br>
+    wrapper.register = register<br>
+    wrapper.dispatch = dispatch<br>
+    wrapper.registry = MappingProxyType(registry)<br>
+    wrapper._clear_cache = dispatch_cache.clear<br>
+    update_wrapper(wrapper, func)<br>
+    return wrapper<br>
diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py<br>
--- a/Lib/pkgutil.py<br>
+++ b/Lib/pkgutil.py<br>
@@ -1,12 +1,13 @@<br>
 """Utilities to support packages."""<br>
<br>
+from functools import singledispatch as simplegeneric<br>
+import imp<br>
+import importlib<br>
 import os<br>
+import os.path<br>
 import sys<br>
-import importlib<br>
-import imp<br>
-import os.path<br>
+from types import ModuleType<br>
 from warnings import warn<br>
-from types import ModuleType<br>
<br>
 __all__ = [<br>
     'get_importer', 'iter_importers', 'get_loader', 'find_loader',<br>
@@ -27,46 +28,6 @@<br>
     return marshal.load(stream)<br>
<br>
<br>
-def simplegeneric(func):<br>
-    """Make a trivial single-dispatch generic function"""<br>
-    registry = {}<br>
-    def wrapper(*args, **kw):<br>
-        ob = args[0]<br>
-        try:<br>
-            cls = ob.__class__<br>
-        except AttributeError:<br>
-            cls = type(ob)<br>
-        try:<br>
-            mro = cls.__mro__<br>
-        except AttributeError:<br>
-            try:<br>
-                class cls(cls, object):<br>
-                    pass<br>
-                mro = cls.__mro__[1:]<br>
-            except TypeError:<br>
-                mro = object,   # must be an ExtensionClass or some such  :(<br>
-        for t in mro:<br>
-            if t in registry:<br>
-                return registry[t](*args, **kw)<br>
-        else:<br>
-            return func(*args, **kw)<br>
-    try:<br>
-        wrapper.__name__ = func.__name__<br>
-    except (TypeError, AttributeError):<br>
-        pass    # Python 2.3 doesn't allow functions to be renamed<br>
-<br>
-    def register(typ, func=None):<br>
-        if func is None:<br>
-            return lambda f: register(typ, f)<br>
-        registry[typ] = func<br>
-        return func<br>
-<br>
-    wrapper.__dict__ = func.__dict__<br>
-    wrapper.__doc__ = func.__doc__<br>
-    wrapper.register = register<br>
-    return wrapper<br>
-<br>
-<br>
 def walk_packages(path=None, prefix='', onerror=None):<br>
     """Yields (module_loader, name, ispkg) for all modules recursively<br>
     on path, or, if path is None, all accessible modules.<br>
@@ -148,13 +109,12 @@<br>
                 yield i, name, ispkg<br>
<br>
<br>
-#@simplegeneric<br>
+@simplegeneric<br>
 def iter_importer_modules(importer, prefix=''):<br>
     if not hasattr(importer, 'iter_modules'):<br>
         return []<br>
     return importer.iter_modules(prefix)<br>
<br>
-iter_importer_modules = simplegeneric(iter_importer_modules)<br>
<br>
 # Implement a file walker for the normal importlib path hook<br>
 def _iter_file_finder_modules(importer, prefix=''):<br>
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py<br>
--- a/Lib/test/test_functools.py<br>
+++ b/Lib/test/test_functools.py<br>
@@ -1,24 +1,30 @@<br>
 import collections<br>
-import sys<br>
-import unittest<br>
-from test import support<br>
-from weakref import proxy<br>
+from itertools import permutations<br>
 import pickle<br>
 from random import choice<br>
+import sys<br>
+from test import support<br>
+import unittest<br>
+from weakref import proxy<br>
<br>
 import functools<br>
<br>
 py_functools = support.import_fresh_module('functools', blocked=['_functools'])<br>
 c_functools = support.import_fresh_module('functools', fresh=['_functools'])<br>
<br>
+decimal = support.import_fresh_module('decimal', fresh=['_decimal'])<br>
+<br>
+<br>
 def capture(*args, **kw):<br>
     """capture all positional and keyword arguments"""<br>
     return args, kw<br>
<br>
+<br>
 def signature(part):<br>
     """ return the signature of a partial object """<br>
     return (part.func, part.args, part.keywords, part.__dict__)<br>
<br>
+<br>
 class TestPartial:<br>
<br>
     def test_basic_examples(self):<br>
@@ -138,6 +144,7 @@<br>
         join = self.partial(''.join)<br>
         self.assertEqual(join(data), '0123456789')<br>
<br>
+<br>
 @unittest.skipUnless(c_functools, 'requires the C _functools module')<br>
 class TestPartialC(TestPartial, unittest.TestCase):<br>
     if c_functools:<br>
@@ -194,18 +201,22 @@<br>
                 "new style getargs format but argument is not a tuple",<br>
                 f.__setstate__, BadSequence())<br>
<br>
+<br>
 class TestPartialPy(TestPartial, unittest.TestCase):<br>
     partial = staticmethod(py_functools.partial)<br>
<br>
+<br>
 if c_functools:<br>
     class PartialSubclass(c_functools.partial):<br>
         pass<br>
<br>
+<br>
 @unittest.skipUnless(c_functools, 'requires the C _functools module')<br>
 class TestPartialCSubclass(TestPartialC):<br>
     if c_functools:<br>
         partial = PartialSubclass<br>
<br>
+<br>
 class TestUpdateWrapper(unittest.TestCase):<br>
<br>
     def check_wrapper(self, wrapper, wrapped,<br>
@@ -312,6 +323,7 @@<br>
         self.assertTrue(wrapper.__doc__.startswith('max('))<br>
         self.assertEqual(wrapper.__annotations__, {})<br>
<br>
+<br>
 class TestWraps(TestUpdateWrapper):<br>
<br>
     def _default_update(self):<br>
@@ -372,6 +384,7 @@<br>
         self.assertEqual(wrapper.attr, 'This is a different test')<br>
         self.assertEqual(wrapper.dict_attr, f.dict_attr)<br>
<br>
+<br>
 class TestReduce(unittest.TestCase):<br>
     func = functools.reduce<br>
<br>
@@ -452,6 +465,7 @@<br>
         d = {"one": 1, "two": 2, "three": 3}<br>
         self.assertEqual(self.func(add, d), "".join(d.keys()))<br>
<br>
+<br>
 class TestCmpToKey:<br>
<br>
     def test_cmp_to_key(self):<br>
@@ -534,14 +548,17 @@<br>
         self.assertRaises(TypeError, hash, k)<br>
         self.assertNotIsInstance(k, collections.Hashable)<br>
<br>
+<br>
 @unittest.skipUnless(c_functools, 'requires the C _functools module')<br>
 class TestCmpToKeyC(TestCmpToKey, unittest.TestCase):<br>
     if c_functools:<br>
         cmp_to_key = c_functools.cmp_to_key<br>
<br>
+<br>
 class TestCmpToKeyPy(TestCmpToKey, unittest.TestCase):<br>
     cmp_to_key = staticmethod(py_functools.cmp_to_key)<br>
<br>
+<br>
 class TestTotalOrdering(unittest.TestCase):<br>
<br>
     def test_total_ordering_lt(self):<br>
@@ -642,6 +659,7 @@<br>
         with self.assertRaises(TypeError):<br>
             TestTO(8) <= ()<br>
<br>
+<br>
 class TestLRU(unittest.TestCase):<br>
<br>
     def test_lru(self):<br>
@@ -834,6 +852,353 @@<br>
                          DoubleEq(2))               # Verify the correct return value<br>
<br>
<br>
+class TestSingleDispatch(unittest.TestCase):<br>
+    def test_simple_overloads(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        def g_int(i):<br>
+            return "integer"<br>
+        g.register(int, g_int)<br>
+        self.assertEqual(g("str"), "base")<br>
+        self.assertEqual(g(1), "integer")<br>
+        self.assertEqual(g([1,2,3]), "base")<br>
+<br>
+    def test_mro(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        class C:<br>
+            pass<br>
+        class D(C):<br>
+            pass<br>
+        def g_C(c):<br>
+            return "C"<br>
+        g.register(C, g_C)<br>
+        self.assertEqual(g(C()), "C")<br>
+        self.assertEqual(g(D()), "C")<br>
+<br>
+    def test_classic_classes(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        class C:<br>
+            pass<br>
+        class D(C):<br>
+            pass<br>
+        def g_C(c):<br>
+            return "C"<br>
+        g.register(C, g_C)<br>
+        self.assertEqual(g(C()), "C")<br>
+        self.assertEqual(g(D()), "C")<br>
+<br>
+    def test_register_decorator(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        @g.register(int)<br>
+        def g_int(i):<br>
+            return "int %s" % (i,)<br>
+        self.assertEqual(g(""), "base")<br>
+        self.assertEqual(g(12), "int 12")<br>
+        self.assertIs(g.dispatch(int), g_int)<br>
+        self.assertIs(g.dispatch(object), g.dispatch(str))<br>
+        # Note: in the assert above this is not g.<br>
+        # @singledispatch returns the wrapper.<br>
+<br>
+    def test_wrapping_attributes(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            "Simple test"<br>
+            return "Test"<br>
+        self.assertEqual(g.__name__, "g")<br>
+        self.assertEqual(g.__doc__, "Simple test")<br>
+<br>
+    @unittest.skipUnless(decimal, 'requires _decimal')<br>
+    @support.cpython_only<br>
+    def test_c_classes(self):<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        @g.register(decimal.DecimalException)<br>
+        def _(obj):<br>
+            return obj.args<br>
+        subn = decimal.Subnormal("Exponent < Emin")<br>
+        rnd = decimal.Rounded("Number got rounded")<br>
+        self.assertEqual(g(subn), ("Exponent < Emin",))<br>
+        self.assertEqual(g(rnd), ("Number got rounded",))<br>
+        @g.register(decimal.Subnormal)<br>
+        def _(obj):<br>
+            return "Too small to care."<br>
+        self.assertEqual(g(subn), "Too small to care.")<br>
+        self.assertEqual(g(rnd), ("Number got rounded",))<br>
+<br>
+    def test_compose_mro(self):<br>
+        c = collections<br>
+        mro = functools._compose_mro<br>
+        bases = [c.Sequence, c.MutableMapping, c.Mapping, c.Set]<br>
+        for haystack in permutations(bases):<br>
+            m = mro(dict, haystack)<br>
+            self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, object])<br>
+        bases = [c.Container, c.Mapping, c.MutableMapping, c.OrderedDict]<br>
+        for haystack in permutations(bases):<br>
+            m = mro(c.ChainMap, haystack)<br>
+            self.assertEqual(m, [c.ChainMap, c.MutableMapping, c.Mapping,<br>
+                                 c.Sized, c.Iterable, c.Container, object])<br>
+        # Note: The MRO order below depends on haystack ordering.<br>
+        m = mro(c.defaultdict, [c.Sized, c.Container, str])<br>
+        self.assertEqual(m, [c.defaultdict, dict, c.Container, c.Sized, object])<br>
+        m = mro(c.defaultdict, [c.Container, c.Sized, str])<br>
+        self.assertEqual(m, [c.defaultdict, dict, c.Sized, c.Container, object])<br>
+<br>
+    def test_register_abc(self):<br>
+        c = collections<br>
+        d = {"a": "b"}<br>
+        l = [1, 2, 3]<br>
+        s = {object(), None}<br>
+        f = frozenset(s)<br>
+        t = (1, 2, 3)<br>
+        @functools.singledispatch<br>
+        def g(obj):<br>
+            return "base"<br>
+        self.assertEqual(g(d), "base")<br>
+        self.assertEqual(g(l), "base")<br>
+        self.assertEqual(g(s), "base")<br>
+        self.assertEqual(g(f), "base")<br>
+        self.assertEqual(g(t), "base")<br>
+        g.register(c.Sized, lambda obj: "sized")<br>
+        self.assertEqual(g(d), "sized")<br>
+        self.assertEqual(g(l), "sized")<br>
+        self.assertEqual(g(s), "sized")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.MutableMapping, lambda obj: "mutablemapping")<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(g(l), "sized")<br>
+        self.assertEqual(g(s), "sized")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.ChainMap, lambda obj: "chainmap")<br>
+        self.assertEqual(g(d), "mutablemapping")  # irrelevant ABCs registered<br>
+        self.assertEqual(g(l), "sized")<br>
+        self.assertEqual(g(s), "sized")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.MutableSequence, lambda obj: "mutablesequence")<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "sized")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.MutableSet, lambda obj: "mutableset")<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.Mapping, lambda obj: "mapping")<br>
+        self.assertEqual(g(d), "mutablemapping")  # not specific enough<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sized")<br>
+        g.register(c.Sequence, lambda obj: "sequence")<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "sized")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(c.Set, lambda obj: "set")<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "set")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(dict, lambda obj: "dict")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "mutablesequence")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "set")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(list, lambda obj: "list")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(g(s), "mutableset")<br>
+        self.assertEqual(g(f), "set")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(set, lambda obj: "concrete-set")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(g(s), "concrete-set")<br>
+        self.assertEqual(g(f), "set")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(frozenset, lambda obj: "frozen-set")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(g(s), "concrete-set")<br>
+        self.assertEqual(g(f), "frozen-set")<br>
+        self.assertEqual(g(t), "sequence")<br>
+        g.register(tuple, lambda obj: "tuple")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(g(s), "concrete-set")<br>
+        self.assertEqual(g(f), "frozen-set")<br>
+        self.assertEqual(g(t), "tuple")<br>
+<br>
+    def test_mro_conflicts(self):<br>
+        c = collections<br>
+<br>
+        @functools.singledispatch<br>
+        def g(arg):<br>
+            return "base"<br>
+<br>
+        class O(c.Sized):<br>
+            def __len__(self):<br>
+                return 0<br>
+<br>
+        o = O()<br>
+        self.assertEqual(g(o), "base")<br>
+        g.register(c.Iterable, lambda arg: "iterable")<br>
+        g.register(c.Container, lambda arg: "container")<br>
+        g.register(c.Sized, lambda arg: "sized")<br>
+        g.register(c.Set, lambda arg: "set")<br>
+        self.assertEqual(g(o), "sized")<br>
+        c.Iterable.register(O)<br>
+        self.assertEqual(g(o), "sized")   # because it's explicitly in __mro__<br>
+        c.Container.register(O)<br>
+        self.assertEqual(g(o), "sized")   # see above: Sized is in __mro__<br>
+<br>
+        class P:<br>
+            pass<br>
+<br>
+        p = P()<br>
+        self.assertEqual(g(p), "base")<br>
+        c.Iterable.register(P)<br>
+        self.assertEqual(g(p), "iterable")<br>
+        c.Container.register(P)<br>
+        with self.assertRaises(RuntimeError) as re:<br>
+            g(p)<br>
+            self.assertEqual(<br>
+                str(re),<br>
+                ("Ambiguous dispatch: <class 'collections.abc.Container'> "<br>
+                    "or <class 'collections.abc.Iterable'>"),<br>
+            )<br>
+<br>
+        class Q(c.Sized):<br>
+            def __len__(self):<br>
+                return 0<br>
+<br>
+        q = Q()<br>
+        self.assertEqual(g(q), "sized")<br>
+        c.Iterable.register(Q)<br>
+        self.assertEqual(g(q), "sized")   # because it's explicitly in __mro__<br>
+        c.Set.register(Q)<br>
+        self.assertEqual(g(q), "set")     # because c.Set is a subclass of<br>
+                                          # c.Sized which is explicitly in<br>
+                                          # __mro__<br>
+<br>
+    def test_cache_invalidation(self):<br>
+        from collections import UserDict<br>
+        class TracingDict(UserDict):<br>
+            def __init__(self, *args, **kwargs):<br>
+                super(TracingDict, self).__init__(*args, **kwargs)<br>
+                self.set_ops = []<br>
+                self.get_ops = []<br>
+            def __getitem__(self, key):<br>
+                result = self.data[key]<br>
+                self.get_ops.append(key)<br>
+                return result<br>
+            def __setitem__(self, key, value):<br>
+                self.set_ops.append(key)<br>
+                self.data[key] = value<br>
+            def clear(self):<br>
+                self.data.clear()<br>
+        _orig_wkd = functools.WeakKeyDictionary<br>
+        td = TracingDict()<br>
+        functools.WeakKeyDictionary = lambda: td<br>
+        c = collections<br>
+        @functools.singledispatch<br>
+        def g(arg):<br>
+            return "base"<br>
+        d = {}<br>
+        l = []<br>
+        self.assertEqual(len(td), 0)<br>
+        self.assertEqual(g(d), "base")<br>
+        self.assertEqual(len(td), 1)<br>
+        self.assertEqual(td.get_ops, [])<br>
+        self.assertEqual(td.set_ops, [dict])<br>
+        self.assertEqual(td.data[dict], g.registry[object])<br>
+        self.assertEqual(g(l), "base")<br>
+        self.assertEqual(len(td), 2)<br>
+        self.assertEqual(td.get_ops, [])<br>
+        self.assertEqual(td.set_ops, [dict, list])<br>
+        self.assertEqual(td.data[dict], g.registry[object])<br>
+        self.assertEqual(td.data[list], g.registry[object])<br>
+        self.assertEqual(td.data[dict], td.data[list])<br>
+        self.assertEqual(g(l), "base")<br>
+        self.assertEqual(g(d), "base")<br>
+        self.assertEqual(td.get_ops, [list, dict])<br>
+        self.assertEqual(td.set_ops, [dict, list])<br>
+        g.register(list, lambda arg: "list")<br>
+        self.assertEqual(td.get_ops, [list, dict])<br>
+        self.assertEqual(len(td), 0)<br>
+        self.assertEqual(g(d), "base")<br>
+        self.assertEqual(len(td), 1)<br>
+        self.assertEqual(td.get_ops, [list, dict])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict])<br>
+        self.assertEqual(td.data[dict],<br>
+                         functools._find_impl(dict, g.registry))<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(len(td), 2)<br>
+        self.assertEqual(td.get_ops, [list, dict])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list])<br>
+        self.assertEqual(td.data[list],<br>
+                         functools._find_impl(list, g.registry))<br>
+        class X:<br>
+            pass<br>
+        c.MutableMapping.register(X)   # Will not invalidate the cache,<br>
+                                       # not using ABCs yet.<br>
+        self.assertEqual(g(d), "base")<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(td.get_ops, [list, dict, dict, list])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list])<br>
+        g.register(c.Sized, lambda arg: "sized")<br>
+        self.assertEqual(len(td), 0)<br>
+        self.assertEqual(g(d), "sized")<br>
+        self.assertEqual(len(td), 1)<br>
+        self.assertEqual(td.get_ops, [list, dict, dict, list])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list, dict])<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(len(td), 2)<br>
+        self.assertEqual(td.get_ops, [list, dict, dict, list])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(g(d), "sized")<br>
+        self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])<br>
+        g.dispatch(list)<br>
+        g.dispatch(dict)<br>
+        self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict,<br>
+                                      list, dict])<br>
+        self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])<br>
+        c.MutableSet.register(X)       # Will invalidate the cache.<br>
+        self.assertEqual(len(td), 2)   # Stale cache.<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(len(td), 1)<br>
+        g.register(c.MutableMapping, lambda arg: "mutablemapping")<br>
+        self.assertEqual(len(td), 0)<br>
+        self.assertEqual(g(d), "mutablemapping")<br>
+        self.assertEqual(len(td), 1)<br>
+        self.assertEqual(g(l), "list")<br>
+        self.assertEqual(len(td), 2)<br>
+        g.register(dict, lambda arg: "dict")<br>
+        self.assertEqual(g(d), "dict")<br>
+        self.assertEqual(g(l), "list")<br>
+        g._clear_cache()<br>
+        self.assertEqual(len(td), 0)<br>
+        functools.WeakKeyDictionary = _orig_wkd<br>
+<br>
+<br>
 def test_main(verbose=None):<br>
     test_classes = (<br>
         TestPartialC,<br>
@@ -846,6 +1211,7 @@<br>
         TestWraps,<br>
         TestReduce,<br>
         TestLRU,<br>
+        TestSingleDispatch,<br>
     )<br>
     support.run_unittest(*test_classes)<br>
<br>
diff --git a/Misc/NEWS b/Misc/NEWS<br>
--- a/Misc/NEWS<br>
+++ b/Misc/NEWS<br>
@@ -344,6 +344,9 @@<br>
   the default for linking if LDSHARED is not also overriden.  This restores<br>
   Distutils behavior introduced in 3.2.3 and inadvertently dropped in 3.3.0.<br>
<br>
+- Implement PEP 443 "Single-dispatch generic functions".<br>
+<br>
+<br>
 Tests<br>
 -----<br>
<br>
diff --git a/Modules/Setup.dist b/Modules/Setup.dist<br>
--- a/Modules/Setup.dist<br>
+++ b/Modules/Setup.dist<br>
@@ -116,6 +116,7 @@<br>
 _operator _operator.c          # operator.add() and similar goodies<br>
 _collections _collectionsmodule.c # Container types<br>
 itertools itertoolsmodule.c    # Functions creating iterators for efficient looping<br>
+atexit atexitmodule.c      # Register functions to be run at interpreter-shutdown<br>
<br>
 # access to ISO C locale support<br>
 _locale _localemodule.c  # -lintl<br>
@@ -170,7 +171,6 @@<br>
 #_weakref _weakref.c   # basic weak reference support<br>
 #_testcapi _testcapimodule.c    # Python C API test module<br>
 #_random _randommodule.c       # Random number generator<br>
-#atexit atexitmodule.c      # Register functions to be run at interpreter-shutdown<br>
 #_elementtree -I$(srcdir)/Modules/expat -DHAVE_EXPAT_CONFIG_H -DUSE_PYEXPAT_CAPI _elementtree.c        # elementtree accelerator<br>
 #_pickle _pickle.c     # pickle accelerator<br>
 #_datetime _datetimemodule.c   # datetime accelerator<br>
<span class="HOEnZb"><font color="#888888"><br>
--<br>
Repository URL: <a href="http://hg.python.org/cpython" target="_blank">http://hg.python.org/cpython</a><br>
</font></span><br>_______________________________________________<br>
Python-checkins mailing list<br>
<a href="mailto:Python-checkins@python.org">Python-checkins@python.org</a><br>
<a href="http://mail.python.org/mailman/listinfo/python-checkins" target="_blank">http://mail.python.org/mailman/listinfo/python-checkins</a><br>
<br></blockquote></div><br></div></div>