<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>