├── .hgtags
├── .travis.yml
├── MANIFEST.in
├── README.rst
├── setup.py
├── singledispatch.py
├── singledispatch_helpers.py
├── test_singledispatch.py
└── tox.ini
/.hgtags:
--------------------------------------------------------------------------------
1 | dcc42011a4c8155724336b38b8246095cb892bab 3.4.0.0
2 | f248e5ab864cfcf95297c973def0db7c4175ded5 3.4.0.1
3 | 15b7355db5b290f0aef79b6685863b8ad84d2e98 3.4.0.2
4 | 77b6ac7e8b7d52e859cc2874728fa9bee710edac 3.4.0.3
5 | 5a988cecf8979c847e8840394ffea2a261ae6382 3.4.0.3
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | before_install:
4 | - pip install tox
5 |
6 | script: tox
7 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | singledispatch
3 | ==============
4 |
5 | .. image:: https://secure.travis-ci.org/ambv/singledispatch.png
6 | :target: https://secure.travis-ci.org/ambv/singledispatch
7 |
8 | `PEP 443 `_ proposed to expose
9 | a mechanism in the ``functools`` standard library module in Python 3.4
10 | that provides a simple form of generic programming known as
11 | single-dispatch generic functions.
12 |
13 | This library is a backport of this functionality to Python 2.6 - 3.3.
14 |
15 | To define a generic function, decorate it with the ``@singledispatch``
16 | decorator. Note that the dispatch happens on the type of the first
17 | argument, create your function accordingly::
18 |
19 | >>> from singledispatch import singledispatch
20 | >>> @singledispatch
21 | ... def fun(arg, verbose=False):
22 | ... if verbose:
23 | ... print("Let me just say,", end=" ")
24 | ... print(arg)
25 |
26 | To add overloaded implementations to the function, use the
27 | ``register()`` attribute of the generic function. It is a decorator,
28 | taking a type parameter and decorating a function implementing the
29 | operation for that type::
30 |
31 | >>> @fun.register(int)
32 | ... def _(arg, verbose=False):
33 | ... if verbose:
34 | ... print("Strength in numbers, eh?", end=" ")
35 | ... print(arg)
36 | ...
37 | >>> @fun.register(list)
38 | ... def _(arg, verbose=False):
39 | ... if verbose:
40 | ... print("Enumerate this:")
41 | ... for i, elem in enumerate(arg):
42 | ... print(i, elem)
43 |
44 | To enable registering lambdas and pre-existing functions, the
45 | ``register()`` attribute can be used in a functional form::
46 |
47 | >>> def nothing(arg, verbose=False):
48 | ... print("Nothing.")
49 | ...
50 | >>> fun.register(type(None), nothing)
51 |
52 | The ``register()`` attribute returns the undecorated function which
53 | enables decorator stacking, pickling, as well as creating unit tests for
54 | each variant independently::
55 |
56 | >>> @fun.register(float)
57 | ... @fun.register(Decimal)
58 | ... def fun_num(arg, verbose=False):
59 | ... if verbose:
60 | ... print("Half of your number:", end=" ")
61 | ... print(arg / 2)
62 | ...
63 | >>> fun_num is fun
64 | False
65 |
66 | When called, the generic function dispatches on the type of the first
67 | argument::
68 |
69 | >>> fun("Hello, world.")
70 | Hello, world.
71 | >>> fun("test.", verbose=True)
72 | Let me just say, test.
73 | >>> fun(42, verbose=True)
74 | Strength in numbers, eh? 42
75 | >>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
76 | Enumerate this:
77 | 0 spam
78 | 1 spam
79 | 2 eggs
80 | 3 spam
81 | >>> fun(None)
82 | Nothing.
83 | >>> fun(1.23)
84 | 0.615
85 |
86 | Where there is no registered implementation for a specific type, its
87 | method resolution order is used to find a more generic implementation.
88 | The original function decorated with ``@singledispatch`` is registered
89 | for the base ``object`` type, which means it is used if no better
90 | implementation is found.
91 |
92 | To check which implementation will the generic function choose for
93 | a given type, use the ``dispatch()`` attribute::
94 |
95 | >>> fun.dispatch(float)
96 |
97 | >>> fun.dispatch(dict) # note: default implementation
98 |
99 |
100 | To access all registered implementations, use the read-only ``registry``
101 | attribute::
102 |
103 | >>> fun.registry.keys()
104 | dict_keys([, , ,
105 | , ,
106 | ])
107 | >>> fun.registry[float]
108 |
109 | >>> fun.registry[object]
110 |
111 |
112 | The vanilla documentation is available at
113 | http://docs.python.org/3/library/functools.html#functools.singledispatch.
114 |
115 |
116 | Versioning
117 | ----------
118 |
119 | This backport is intended to keep 100% compatibility with the vanilla
120 | release in Python 3.4+. To help maintaining a version you want and
121 | expect, a versioning scheme is used where:
122 |
123 | * the first three numbers indicate the version of Python 3.x from which the
124 | backport is done
125 |
126 | * a backport release number is provided after the last dot
127 |
128 | For example, ``3.4.0.0`` is the **first** release of ``singledispatch``
129 | compatible with the library found in Python **3.4.0**.
130 |
131 | A single exception from the 100% compatibility principle is that bugs
132 | fixed before releasing another minor Python 3.x.y version **will be
133 | included** in the backport releases done in the mean time. This rule
134 | applies to bugs only.
135 |
136 |
137 | Maintenance
138 | -----------
139 |
140 | This backport is maintained on BitBucket by Łukasz Langa, one of the
141 | members of the core CPython team:
142 |
143 | * `singledispatch Mercurial repository `_
144 |
145 | * `singledispatch issue tracker `_
146 |
147 |
148 | Change Log
149 | ----------
150 |
151 | 3.4.0.3
152 | ~~~~~~~
153 |
154 | Should now install flawlessly on PyPy as well. Thanks to Ryan Petrello
155 | for finding and fixing the ``setup.py`` issue.
156 |
157 | 3.4.0.2
158 | ~~~~~~~
159 |
160 | Updated to the reference implementation as of 02-July-2013.
161 |
162 | * more predictable dispatch order when abstract base classes are in use:
163 | abstract base classes are now inserted into the MRO of the argument's
164 | class where their functionality is introduced, i.e. issubclass(cls,
165 | abc) returns True for the class itself but returns False for all its
166 | direct base classes. Implicit ABCs for a given class (either
167 | registered or inferred from the presence of a special method like
168 | __len__) are inserted directly after the last ABC explicitly listed in
169 | the MRO of said class. This also means there are less "ambiguous
170 | dispatch" exceptions raised.
171 |
172 | * better test coverage and improved docstrings
173 |
174 | 3.4.0.1
175 | ~~~~~~~
176 |
177 | Updated to the reference implementation as of 31-May-2013.
178 |
179 | * better performance
180 |
181 | * fixed a corner case with PEP 435 enums
182 |
183 | * calls to `dispatch()` also cached
184 |
185 | * dispatching algorithm now now a module-level routine called `_find_impl()`
186 | with a simplified implementation and proper documentation
187 |
188 | * `dispatch()` now handles all caching-related activities
189 |
190 | * terminology more consistent: "overload" -> "implementation"
191 |
192 | 3.4.0.0
193 | ~~~~~~~
194 |
195 | * the first public release compatible with 3.4.0
196 |
197 |
198 | Conversion Process
199 | ------------------
200 |
201 | This section is technical and should bother you only if you are
202 | wondering how this backport is produced. If the implementation details
203 | of this backport are not important for you, feel free to ignore the
204 | following content.
205 |
206 | ``singledispatch`` is converted using `six
207 | `_ so that a single codebase can be
208 | used for all compatible Python versions. Because a fully automatic
209 | conversion was not doable, I took the following branching approach:
210 |
211 | * the ``upstream`` branch holds unchanged files synchronized from the
212 | upstream CPython repository. The synchronization is currently done by
213 | manually copying the required code parts and stating from which
214 | CPython changeset they come from. The tests should pass on Python 3.4
215 | on this branch.
216 |
217 | * the ``default`` branch holds the manually translated version and this
218 | is where all tests are run for all supported Python versions using
219 | Tox.
220 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """This library brings functools.singledispatch from Python 3.4 to Python 2.6-3.3."""
5 |
6 | # Copyright (C) 2013 by Łukasz Langa
7 | #
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy
9 | # of this software and associated documentation files (the "Software"), to deal
10 | # in the Software without restriction, including without limitation the rights
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | # copies of the Software, and to permit persons to whom the Software is
13 | # furnished to do so, subject to the following conditions:
14 |
15 | # The above copyright notice and this permission notice shall be included in
16 | # all copies or substantial portions of the Software.
17 |
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | # THE SOFTWARE.
25 |
26 | import os
27 | import sys
28 | import codecs
29 | from setuptools import setup
30 |
31 | with codecs.open(
32 | os.path.join(os.path.dirname(__file__), 'README.rst'), 'r', 'utf8',
33 | ) as ld_file:
34 | long_description = ld_file.read()
35 | # We let it die a horrible tracebacking death if reading the file fails.
36 | # We couldn't sensibly recover anyway: we need the long description.
37 |
38 | install_requires = ['six']
39 | if sys.version_info[:2] < (2, 7):
40 | install_requires.append('ordereddict')
41 |
42 | setup (
43 | name = 'singledispatch',
44 | version = '3.4.0.3',
45 | author = 'Łukasz Langa',
46 | author_email = 'lukasz@langa.pl',
47 | description = __doc__,
48 | long_description = long_description,
49 | url = 'http://docs.python.org/3/library/functools.html'
50 | '#functools.singledispatch',
51 | keywords = 'single dispatch generic functions singledispatch '
52 | 'genericfunctions decorator backport',
53 | platforms = ['any'],
54 | license = 'MIT',
55 | py_modules = ('singledispatch', 'singledispatch_helpers'),
56 | zip_safe = True,
57 | install_requires = install_requires,
58 | classifiers = [
59 | 'Development Status :: 5 - Production/Stable',
60 | 'Intended Audience :: Developers',
61 | 'License :: OSI Approved :: MIT License',
62 | 'Natural Language :: English',
63 | 'Operating System :: OS Independent',
64 | 'Programming Language :: Python',
65 | 'Programming Language :: Python :: 2',
66 | 'Programming Language :: Python :: 2.6',
67 | 'Programming Language :: Python :: 2.7',
68 | 'Programming Language :: Python :: 3',
69 | 'Programming Language :: Python :: 3.2',
70 | 'Programming Language :: Python :: 3.3',
71 | 'Programming Language :: Python :: 3.4',
72 | 'Topic :: Software Development :: Libraries',
73 | 'Topic :: Software Development :: Libraries :: Python Modules',
74 | ]
75 | )
76 |
--------------------------------------------------------------------------------
/singledispatch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from __future__ import absolute_import
5 | from __future__ import division
6 | from __future__ import print_function
7 | from __future__ import unicode_literals
8 |
9 | __all__ = ['singledispatch']
10 |
11 | from functools import update_wrapper
12 | from weakref import WeakKeyDictionary
13 | from singledispatch_helpers import MappingProxyType, get_cache_token
14 |
15 | ################################################################################
16 | ### singledispatch() - single-dispatch generic function decorator
17 | ################################################################################
18 |
19 | def _c3_merge(sequences):
20 | """Merges MROs in *sequences* to a single MRO using the C3 algorithm.
21 |
22 | Adapted from http://www.python.org/download/releases/2.3/mro/.
23 |
24 | """
25 | result = []
26 | while True:
27 | sequences = [s for s in sequences if s] # purge empty sequences
28 | if not sequences:
29 | return result
30 | for s1 in sequences: # find merge candidates among seq heads
31 | candidate = s1[0]
32 | for s2 in sequences:
33 | if candidate in s2[1:]:
34 | candidate = None
35 | break # reject the current head, it appears later
36 | else:
37 | break
38 | if not candidate:
39 | raise RuntimeError("Inconsistent hierarchy")
40 | result.append(candidate)
41 | # remove the chosen candidate
42 | for seq in sequences:
43 | if seq[0] == candidate:
44 | del seq[0]
45 |
46 | def _c3_mro(cls, abcs=None):
47 | """Computes the method resolution order using extended C3 linearization.
48 |
49 | If no *abcs* are given, the algorithm works exactly like the built-in C3
50 | linearization used for method resolution.
51 |
52 | If given, *abcs* is a list of abstract base classes that should be inserted
53 | into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
54 | result. The algorithm inserts ABCs where their functionality is introduced,
55 | i.e. issubclass(cls, abc) returns True for the class itself but returns
56 | False for all its direct base classes. Implicit ABCs for a given class
57 | (either registered or inferred from the presence of a special method like
58 | __len__) are inserted directly after the last ABC explicitly listed in the
59 | MRO of said class. If two implicit ABCs end up next to each other in the
60 | resulting MRO, their ordering depends on the order of types in *abcs*.
61 |
62 | """
63 | for i, base in enumerate(reversed(cls.__bases__)):
64 | if hasattr(base, '__abstractmethods__'):
65 | boundary = len(cls.__bases__) - i
66 | break # Bases up to the last explicit ABC are considered first.
67 | else:
68 | boundary = 0
69 | abcs = list(abcs) if abcs else []
70 | explicit_bases = list(cls.__bases__[:boundary])
71 | abstract_bases = []
72 | other_bases = list(cls.__bases__[boundary:])
73 | for base in abcs:
74 | if issubclass(cls, base) and not any(
75 | issubclass(b, base) for b in cls.__bases__
76 | ):
77 | # If *cls* is the class that introduces behaviour described by
78 | # an ABC *base*, insert said ABC to its MRO.
79 | abstract_bases.append(base)
80 | for base in abstract_bases:
81 | abcs.remove(base)
82 | explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
83 | abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
84 | other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
85 | return _c3_merge(
86 | [[cls]] +
87 | explicit_c3_mros + abstract_c3_mros + other_c3_mros +
88 | [explicit_bases] + [abstract_bases] + [other_bases]
89 | )
90 |
91 | def _compose_mro(cls, types):
92 | """Calculates the method resolution order for a given class *cls*.
93 |
94 | Includes relevant abstract base classes (with their respective bases) from
95 | the *types* iterable. Uses a modified C3 linearization algorithm.
96 |
97 | """
98 | bases = set(cls.__mro__)
99 | # Remove entries which are already present in the __mro__ or unrelated.
100 | def is_related(typ):
101 | return (typ not in bases and hasattr(typ, '__mro__')
102 | and issubclass(cls, typ))
103 | types = [n for n in types if is_related(n)]
104 | # Remove entries which are strict bases of other entries (they will end up
105 | # in the MRO anyway.
106 | def is_strict_base(typ):
107 | for other in types:
108 | if typ != other and typ in other.__mro__:
109 | return True
110 | return False
111 | types = [n for n in types if not is_strict_base(n)]
112 | # Subclasses of the ABCs in *types* which are also implemented by
113 | # *cls* can be used to stabilize ABC ordering.
114 | type_set = set(types)
115 | mro = []
116 | for typ in types:
117 | found = []
118 | for sub in typ.__subclasses__():
119 | if sub not in bases and issubclass(cls, sub):
120 | found.append([s for s in sub.__mro__ if s in type_set])
121 | if not found:
122 | mro.append(typ)
123 | continue
124 | # Favor subclasses with the biggest number of useful bases
125 | found.sort(key=len, reverse=True)
126 | for sub in found:
127 | for subcls in sub:
128 | if subcls not in mro:
129 | mro.append(subcls)
130 | return _c3_mro(cls, abcs=mro)
131 |
132 | def _find_impl(cls, registry):
133 | """Returns the best matching implementation from *registry* for type *cls*.
134 |
135 | Where there is no registered implementation for a specific type, its method
136 | resolution order is used to find a more generic implementation.
137 |
138 | Note: if *registry* does not contain an implementation for the base
139 | *object* type, this function may return None.
140 |
141 | """
142 | mro = _compose_mro(cls, registry.keys())
143 | match = None
144 | for t in mro:
145 | if match is not None:
146 | # If *match* is an implicit ABC but there is another unrelated,
147 | # equally matching implicit ABC, refuse the temptation to guess.
148 | if (t in registry and t not in cls.__mro__
149 | and match not in cls.__mro__
150 | and not issubclass(match, t)):
151 | raise RuntimeError("Ambiguous dispatch: {0} or {1}".format(
152 | match, t))
153 | break
154 | if t in registry:
155 | match = t
156 | return registry.get(match)
157 |
158 | def singledispatch(func):
159 | """Single-dispatch generic function decorator.
160 |
161 | Transforms a function into a generic function, which can have different
162 | behaviours depending upon the type of its first argument. The decorated
163 | function acts as the default implementation, and additional
164 | implementations can be registered using the register() attribute of the
165 | generic function.
166 |
167 | """
168 | registry = {}
169 | dispatch_cache = WeakKeyDictionary()
170 | def ns(): pass
171 | ns.cache_token = None
172 |
173 | def dispatch(cls):
174 | """generic_func.dispatch(cls) ->
175 |
176 | Runs the dispatch algorithm to return the best available implementation
177 | for the given *cls* registered on *generic_func*.
178 |
179 | """
180 | if ns.cache_token is not None:
181 | current_token = get_cache_token()
182 | if ns.cache_token != current_token:
183 | dispatch_cache.clear()
184 | ns.cache_token = current_token
185 | try:
186 | impl = dispatch_cache[cls]
187 | except KeyError:
188 | try:
189 | impl = registry[cls]
190 | except KeyError:
191 | impl = _find_impl(cls, registry)
192 | dispatch_cache[cls] = impl
193 | return impl
194 |
195 | def register(cls, func=None):
196 | """generic_func.register(cls, func) -> func
197 |
198 | Registers a new implementation for the given *cls* on a *generic_func*.
199 |
200 | """
201 | if func is None:
202 | return lambda f: register(cls, f)
203 | registry[cls] = func
204 | if ns.cache_token is None and hasattr(cls, '__abstractmethods__'):
205 | ns.cache_token = get_cache_token()
206 | dispatch_cache.clear()
207 | return func
208 |
209 | def wrapper(*args, **kw):
210 | return dispatch(args[0].__class__)(*args, **kw)
211 |
212 | registry[object] = func
213 | wrapper.register = register
214 | wrapper.dispatch = dispatch
215 | wrapper.registry = MappingProxyType(registry)
216 | wrapper._clear_cache = dispatch_cache.clear
217 | update_wrapper(wrapper, func)
218 | return wrapper
219 |
220 |
--------------------------------------------------------------------------------
/singledispatch_helpers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from __future__ import absolute_import
5 | from __future__ import division
6 | from __future__ import print_function
7 | from __future__ import unicode_literals
8 |
9 | from abc import ABCMeta
10 | from collections import MutableMapping
11 | import sys
12 | try:
13 | from collections import UserDict
14 | except ImportError:
15 | from UserDict import UserDict
16 | try:
17 | from collections import OrderedDict
18 | except ImportError:
19 | from ordereddict import OrderedDict
20 | try:
21 | from thread import get_ident
22 | except ImportError:
23 | try:
24 | from _thread import get_ident
25 | except ImportError:
26 | from _dummy_thread import get_ident
27 |
28 |
29 | def recursive_repr(fillvalue='...'):
30 | 'Decorator to make a repr function return fillvalue for a recursive call'
31 |
32 | def decorating_function(user_function):
33 | repr_running = set()
34 |
35 | def wrapper(self):
36 | key = id(self), get_ident()
37 | if key in repr_running:
38 | return fillvalue
39 | repr_running.add(key)
40 | try:
41 | result = user_function(self)
42 | finally:
43 | repr_running.discard(key)
44 | return result
45 |
46 | # Can't use functools.wraps() here because of bootstrap issues
47 | wrapper.__module__ = getattr(user_function, '__module__')
48 | wrapper.__doc__ = getattr(user_function, '__doc__')
49 | wrapper.__name__ = getattr(user_function, '__name__')
50 | wrapper.__annotations__ = getattr(user_function, '__annotations__', {})
51 | return wrapper
52 |
53 | return decorating_function
54 |
55 |
56 | class ChainMap(MutableMapping):
57 | ''' A ChainMap groups multiple dicts (or other mappings) together
58 | to create a single, updateable view.
59 |
60 | The underlying mappings are stored in a list. That list is public and can
61 | accessed or updated using the *maps* attribute. There is no other state.
62 |
63 | Lookups search the underlying mappings successively until a key is found.
64 | In contrast, writes, updates, and deletions only operate on the first
65 | mapping.
66 |
67 | '''
68 |
69 | def __init__(self, *maps):
70 | '''Initialize a ChainMap by setting *maps* to the given mappings.
71 | If no mappings are provided, a single empty dictionary is used.
72 |
73 | '''
74 | self.maps = list(maps) or [{}] # always at least one map
75 |
76 | def __missing__(self, key):
77 | raise KeyError(key)
78 |
79 | def __getitem__(self, key):
80 | for mapping in self.maps:
81 | try:
82 | return mapping[key] # can't use 'key in mapping' with defaultdict
83 | except KeyError:
84 | pass
85 | return self.__missing__(key) # support subclasses that define __missing__
86 |
87 | def get(self, key, default=None):
88 | return self[key] if key in self else default
89 |
90 | def __len__(self):
91 | return len(set().union(*self.maps)) # reuses stored hash values if possible
92 |
93 | def __iter__(self):
94 | return iter(set().union(*self.maps))
95 |
96 | def __contains__(self, key):
97 | return any(key in m for m in self.maps)
98 |
99 | @recursive_repr()
100 | def __repr__(self):
101 | return '{0.__class__.__name__}({1})'.format(
102 | self, ', '.join(map(repr, self.maps)))
103 |
104 | @classmethod
105 | def fromkeys(cls, iterable, *args):
106 | 'Create a ChainMap with a single dict created from the iterable.'
107 | return cls(dict.fromkeys(iterable, *args))
108 |
109 | def copy(self):
110 | 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
111 | return self.__class__(self.maps[0].copy(), *self.maps[1:])
112 |
113 | __copy__ = copy
114 |
115 | def new_child(self): # like Django's Context.push()
116 | 'New ChainMap with a new dict followed by all previous maps.'
117 | return self.__class__({}, *self.maps)
118 |
119 | @property
120 | def parents(self): # like Django's Context.pop()
121 | 'New ChainMap from maps[1:].'
122 | return self.__class__(*self.maps[1:])
123 |
124 | def __setitem__(self, key, value):
125 | self.maps[0][key] = value
126 |
127 | def __delitem__(self, key):
128 | try:
129 | del self.maps[0][key]
130 | except KeyError:
131 | raise KeyError('Key not found in the first mapping: {!r}'.format(key))
132 |
133 | def popitem(self):
134 | 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
135 | try:
136 | return self.maps[0].popitem()
137 | except KeyError:
138 | raise KeyError('No keys found in the first mapping.')
139 |
140 | def pop(self, key, *args):
141 | 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
142 | try:
143 | return self.maps[0].pop(key, *args)
144 | except KeyError:
145 | raise KeyError('Key not found in the first mapping: {!r}'.format(key))
146 |
147 | def clear(self):
148 | 'Clear maps[0], leaving maps[1:] intact.'
149 | self.maps[0].clear()
150 |
151 |
152 | class MappingProxyType(UserDict):
153 | def __init__(self, data):
154 | UserDict.__init__(self)
155 | self.data = data
156 |
157 |
158 | def get_cache_token():
159 | return ABCMeta._abc_invalidation_counter
160 |
161 |
162 |
163 | class Support(object):
164 | def dummy(self):
165 | pass
166 |
167 | def cpython_only(self, func):
168 | if 'PyPy' in sys.version:
169 | return self.dummy
170 | return func
171 |
--------------------------------------------------------------------------------
/test_singledispatch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from __future__ import absolute_import
5 | from __future__ import division
6 | from __future__ import print_function
7 | from __future__ import unicode_literals
8 |
9 | import collections
10 | import decimal
11 | from itertools import permutations
12 | import singledispatch as functools
13 | from singledispatch_helpers import Support
14 | try:
15 | from collections import ChainMap
16 | except ImportError:
17 | from singledispatch_helpers import ChainMap
18 | collections.ChainMap = ChainMap
19 | try:
20 | from collections import OrderedDict
21 | except ImportError:
22 | from singledispatch_helpers import OrderedDict
23 | collections.OrderedDict = OrderedDict
24 | try:
25 | import unittest2 as unittest
26 | except ImportError:
27 | import unittest
28 |
29 |
30 | support = Support()
31 | for _prefix in ('collections.abc', '_abcoll'):
32 | if _prefix in repr(collections.Container):
33 | abcoll_prefix = _prefix
34 | break
35 | else:
36 | abcoll_prefix = '?'
37 | del _prefix
38 |
39 |
40 | class TestSingleDispatch(unittest.TestCase):
41 | def test_simple_overloads(self):
42 | @functools.singledispatch
43 | def g(obj):
44 | return "base"
45 | def g_int(i):
46 | return "integer"
47 | g.register(int, g_int)
48 | self.assertEqual(g("str"), "base")
49 | self.assertEqual(g(1), "integer")
50 | self.assertEqual(g([1,2,3]), "base")
51 |
52 | def test_mro(self):
53 | @functools.singledispatch
54 | def g(obj):
55 | return "base"
56 | class A(object):
57 | pass
58 | class C(A):
59 | pass
60 | class B(A):
61 | pass
62 | class D(C, B):
63 | pass
64 | def g_A(a):
65 | return "A"
66 | def g_B(b):
67 | return "B"
68 | g.register(A, g_A)
69 | g.register(B, g_B)
70 | self.assertEqual(g(A()), "A")
71 | self.assertEqual(g(B()), "B")
72 | self.assertEqual(g(C()), "A")
73 | self.assertEqual(g(D()), "B")
74 |
75 | def test_register_decorator(self):
76 | @functools.singledispatch
77 | def g(obj):
78 | return "base"
79 | @g.register(int)
80 | def g_int(i):
81 | return "int %s" % (i,)
82 | self.assertEqual(g(""), "base")
83 | self.assertEqual(g(12), "int 12")
84 | self.assertIs(g.dispatch(int), g_int)
85 | self.assertIs(g.dispatch(object), g.dispatch(str))
86 | # Note: in the assert above this is not g.
87 | # @singledispatch returns the wrapper.
88 |
89 | def test_wrapping_attributes(self):
90 | @functools.singledispatch
91 | def g(obj):
92 | "Simple test"
93 | return "Test"
94 | self.assertEqual(g.__name__, "g")
95 | self.assertEqual(g.__doc__, "Simple test")
96 |
97 | @unittest.skipUnless(decimal, 'requires _decimal')
98 | @support.cpython_only
99 | def test_c_classes(self):
100 | @functools.singledispatch
101 | def g(obj):
102 | return "base"
103 | @g.register(decimal.DecimalException)
104 | def _(obj):
105 | return obj.args
106 | subn = decimal.Subnormal("Exponent < Emin")
107 | rnd = decimal.Rounded("Number got rounded")
108 | self.assertEqual(g(subn), ("Exponent < Emin",))
109 | self.assertEqual(g(rnd), ("Number got rounded",))
110 | @g.register(decimal.Subnormal)
111 | def _(obj):
112 | return "Too small to care."
113 | self.assertEqual(g(subn), "Too small to care.")
114 | self.assertEqual(g(rnd), ("Number got rounded",))
115 |
116 | def test_compose_mro(self):
117 | # None of the examples in this test depend on haystack ordering.
118 | c = collections
119 | mro = functools._compose_mro
120 | bases = [c.Sequence, c.MutableMapping, c.Mapping, c.Set]
121 | for haystack in permutations(bases):
122 | m = mro(dict, haystack)
123 | self.assertEqual(m, [dict, c.MutableMapping, c.Mapping, c.Sized,
124 | c.Iterable, c.Container, object])
125 | bases = [c.Container, c.Mapping, c.MutableMapping, c.OrderedDict]
126 | for haystack in permutations(bases):
127 | m = mro(c.ChainMap, haystack)
128 | self.assertEqual(m, [c.ChainMap, c.MutableMapping, c.Mapping,
129 | c.Sized, c.Iterable, c.Container, object])
130 |
131 | # If there's a generic function with implementations registered for
132 | # both Sized and Container, passing a defaultdict to it results in an
133 | # ambiguous dispatch which will cause a RuntimeError (see
134 | # test_mro_conflicts).
135 | bases = [c.Container, c.Sized, str]
136 | for haystack in permutations(bases):
137 | m = mro(c.defaultdict, [c.Sized, c.Container, str])
138 | self.assertEqual(m, [c.defaultdict, dict, c.Sized, c.Container,
139 | object])
140 |
141 | # MutableSequence below is registered directly on D. In other words, it
142 | # preceeds MutableMapping which means single dispatch will always
143 | # choose MutableSequence here.
144 | class D(c.defaultdict):
145 | pass
146 | c.MutableSequence.register(D)
147 | bases = [c.MutableSequence, c.MutableMapping]
148 | for haystack in permutations(bases):
149 | m = mro(D, bases)
150 | self.assertEqual(m, [D, c.MutableSequence, c.Sequence,
151 | c.defaultdict, dict, c.MutableMapping,
152 | c.Mapping, c.Sized, c.Iterable, c.Container,
153 | object])
154 |
155 | # Container and Callable are registered on different base classes and
156 | # a generic function supporting both should always pick the Callable
157 | # implementation if a C instance is passed.
158 | class C(c.defaultdict):
159 | def __call__(self):
160 | pass
161 | bases = [c.Sized, c.Callable, c.Container, c.Mapping]
162 | for haystack in permutations(bases):
163 | m = mro(C, haystack)
164 | self.assertEqual(m, [C, c.Callable, c.defaultdict, dict, c.Mapping,
165 | c.Sized, c.Iterable, c.Container, object])
166 |
167 | def test_register_abc(self):
168 | c = collections
169 | d = {"a": "b"}
170 | l = [1, 2, 3]
171 | s = set([object(), None])
172 | f = frozenset(s)
173 | t = (1, 2, 3)
174 | @functools.singledispatch
175 | def g(obj):
176 | return "base"
177 | self.assertEqual(g(d), "base")
178 | self.assertEqual(g(l), "base")
179 | self.assertEqual(g(s), "base")
180 | self.assertEqual(g(f), "base")
181 | self.assertEqual(g(t), "base")
182 | g.register(c.Sized, lambda obj: "sized")
183 | self.assertEqual(g(d), "sized")
184 | self.assertEqual(g(l), "sized")
185 | self.assertEqual(g(s), "sized")
186 | self.assertEqual(g(f), "sized")
187 | self.assertEqual(g(t), "sized")
188 | g.register(c.MutableMapping, lambda obj: "mutablemapping")
189 | self.assertEqual(g(d), "mutablemapping")
190 | self.assertEqual(g(l), "sized")
191 | self.assertEqual(g(s), "sized")
192 | self.assertEqual(g(f), "sized")
193 | self.assertEqual(g(t), "sized")
194 | g.register(c.ChainMap, lambda obj: "chainmap")
195 | self.assertEqual(g(d), "mutablemapping") # irrelevant ABCs registered
196 | self.assertEqual(g(l), "sized")
197 | self.assertEqual(g(s), "sized")
198 | self.assertEqual(g(f), "sized")
199 | self.assertEqual(g(t), "sized")
200 | g.register(c.MutableSequence, lambda obj: "mutablesequence")
201 | self.assertEqual(g(d), "mutablemapping")
202 | self.assertEqual(g(l), "mutablesequence")
203 | self.assertEqual(g(s), "sized")
204 | self.assertEqual(g(f), "sized")
205 | self.assertEqual(g(t), "sized")
206 | g.register(c.MutableSet, lambda obj: "mutableset")
207 | self.assertEqual(g(d), "mutablemapping")
208 | self.assertEqual(g(l), "mutablesequence")
209 | self.assertEqual(g(s), "mutableset")
210 | self.assertEqual(g(f), "sized")
211 | self.assertEqual(g(t), "sized")
212 | g.register(c.Mapping, lambda obj: "mapping")
213 | self.assertEqual(g(d), "mutablemapping") # not specific enough
214 | self.assertEqual(g(l), "mutablesequence")
215 | self.assertEqual(g(s), "mutableset")
216 | self.assertEqual(g(f), "sized")
217 | self.assertEqual(g(t), "sized")
218 | g.register(c.Sequence, lambda obj: "sequence")
219 | self.assertEqual(g(d), "mutablemapping")
220 | self.assertEqual(g(l), "mutablesequence")
221 | self.assertEqual(g(s), "mutableset")
222 | self.assertEqual(g(f), "sized")
223 | self.assertEqual(g(t), "sequence")
224 | g.register(c.Set, lambda obj: "set")
225 | self.assertEqual(g(d), "mutablemapping")
226 | self.assertEqual(g(l), "mutablesequence")
227 | self.assertEqual(g(s), "mutableset")
228 | self.assertEqual(g(f), "set")
229 | self.assertEqual(g(t), "sequence")
230 | g.register(dict, lambda obj: "dict")
231 | self.assertEqual(g(d), "dict")
232 | self.assertEqual(g(l), "mutablesequence")
233 | self.assertEqual(g(s), "mutableset")
234 | self.assertEqual(g(f), "set")
235 | self.assertEqual(g(t), "sequence")
236 | g.register(list, lambda obj: "list")
237 | self.assertEqual(g(d), "dict")
238 | self.assertEqual(g(l), "list")
239 | self.assertEqual(g(s), "mutableset")
240 | self.assertEqual(g(f), "set")
241 | self.assertEqual(g(t), "sequence")
242 | g.register(set, lambda obj: "concrete-set")
243 | self.assertEqual(g(d), "dict")
244 | self.assertEqual(g(l), "list")
245 | self.assertEqual(g(s), "concrete-set")
246 | self.assertEqual(g(f), "set")
247 | self.assertEqual(g(t), "sequence")
248 | g.register(frozenset, lambda obj: "frozen-set")
249 | self.assertEqual(g(d), "dict")
250 | self.assertEqual(g(l), "list")
251 | self.assertEqual(g(s), "concrete-set")
252 | self.assertEqual(g(f), "frozen-set")
253 | self.assertEqual(g(t), "sequence")
254 | g.register(tuple, lambda obj: "tuple")
255 | self.assertEqual(g(d), "dict")
256 | self.assertEqual(g(l), "list")
257 | self.assertEqual(g(s), "concrete-set")
258 | self.assertEqual(g(f), "frozen-set")
259 | self.assertEqual(g(t), "tuple")
260 |
261 | def test_c3_abc(self):
262 | c = collections
263 | mro = functools._c3_mro
264 | class A(object):
265 | pass
266 | class B(A):
267 | def __len__(self):
268 | return 0 # implies Sized
269 | #@c.Container.register
270 | class C(object):
271 | pass
272 | c.Container.register(C)
273 | class D(object):
274 | pass # unrelated
275 | class X(D, C, B):
276 | def __call__(self):
277 | pass # implies Callable
278 | expected = [X, c.Callable, D, C, c.Container, B, c.Sized, A, object]
279 | for abcs in permutations([c.Sized, c.Callable, c.Container]):
280 | self.assertEqual(mro(X, abcs=abcs), expected)
281 | # unrelated ABCs don't appear in the resulting MRO
282 | many_abcs = [c.Mapping, c.Sized, c.Callable, c.Container, c.Iterable]
283 | self.assertEqual(mro(X, abcs=many_abcs), expected)
284 |
285 | def test_mro_conflicts(self):
286 | c = collections
287 | @functools.singledispatch
288 | def g(arg):
289 | return "base"
290 | class O(c.Sized):
291 | def __len__(self):
292 | return 0
293 | o = O()
294 | self.assertEqual(g(o), "base")
295 | g.register(c.Iterable, lambda arg: "iterable")
296 | g.register(c.Container, lambda arg: "container")
297 | g.register(c.Sized, lambda arg: "sized")
298 | g.register(c.Set, lambda arg: "set")
299 | self.assertEqual(g(o), "sized")
300 | c.Iterable.register(O)
301 | self.assertEqual(g(o), "sized") # because it's explicitly in __mro__
302 | c.Container.register(O)
303 | self.assertEqual(g(o), "sized") # see above: Sized is in __mro__
304 | c.Set.register(O)
305 | self.assertEqual(g(o), "set") # because c.Set is a subclass of
306 | # c.Sized and c.Container
307 | class P(object):
308 | pass
309 | p = P()
310 | self.assertEqual(g(p), "base")
311 | c.Iterable.register(P)
312 | self.assertEqual(g(p), "iterable")
313 | c.Container.register(P)
314 | with self.assertRaises(RuntimeError) as re_one:
315 | g(p)
316 | self.assertIn(
317 | str(re_one.exception),
318 | (("Ambiguous dispatch: "
319 | "or ").format(prefix=abcoll_prefix),
320 | ("Ambiguous dispatch: "
321 | "or ").format(prefix=abcoll_prefix)),
322 | )
323 | class Q(c.Sized):
324 | def __len__(self):
325 | return 0
326 | q = Q()
327 | self.assertEqual(g(q), "sized")
328 | c.Iterable.register(Q)
329 | self.assertEqual(g(q), "sized") # because it's explicitly in __mro__
330 | c.Set.register(Q)
331 | self.assertEqual(g(q), "set") # because c.Set is a subclass of
332 | # c.Sized and c.Iterable
333 | @functools.singledispatch
334 | def h(arg):
335 | return "base"
336 | @h.register(c.Sized)
337 | def _(arg):
338 | return "sized"
339 | @h.register(c.Container)
340 | def _(arg):
341 | return "container"
342 | # Even though Sized and Container are explicit bases of MutableMapping,
343 | # this ABC is implicitly registered on defaultdict which makes all of
344 | # MutableMapping's bases implicit as well from defaultdict's
345 | # perspective.
346 | with self.assertRaises(RuntimeError) as re_two:
347 | h(c.defaultdict(lambda: 0))
348 | self.assertIn(
349 | str(re_two.exception),
350 | (("Ambiguous dispatch: "
351 | "or ").format(prefix=abcoll_prefix),
352 | ("Ambiguous dispatch: "
353 | "or ").format(prefix=abcoll_prefix)),
354 | )
355 | class R(c.defaultdict):
356 | pass
357 | c.MutableSequence.register(R)
358 | @functools.singledispatch
359 | def i(arg):
360 | return "base"
361 | @i.register(c.MutableMapping)
362 | def _(arg):
363 | return "mapping"
364 | @i.register(c.MutableSequence)
365 | def _(arg):
366 | return "sequence"
367 | r = R()
368 | self.assertEqual(i(r), "sequence")
369 | class S(object):
370 | pass
371 | class T(S, c.Sized):
372 | def __len__(self):
373 | return 0
374 | t = T()
375 | self.assertEqual(h(t), "sized")
376 | c.Container.register(T)
377 | self.assertEqual(h(t), "sized") # because it's explicitly in the MRO
378 | class U(object):
379 | def __len__(self):
380 | return 0
381 | u = U()
382 | self.assertEqual(h(u), "sized") # implicit Sized subclass inferred
383 | # from the existence of __len__()
384 | c.Container.register(U)
385 | # There is no preference for registered versus inferred ABCs.
386 | with self.assertRaises(RuntimeError) as re_three:
387 | h(u)
388 | self.assertIn(
389 | str(re_three.exception),
390 | (("Ambiguous dispatch: "
391 | "or ").format(prefix=abcoll_prefix),
392 | ("Ambiguous dispatch: "
393 | "or ").format(prefix=abcoll_prefix)),
394 | )
395 | class V(c.Sized, S):
396 | def __len__(self):
397 | return 0
398 | @functools.singledispatch
399 | def j(arg):
400 | return "base"
401 | @j.register(S)
402 | def _(arg):
403 | return "s"
404 | @j.register(c.Container)
405 | def _(arg):
406 | return "container"
407 | v = V()
408 | self.assertEqual(j(v), "s")
409 | c.Container.register(V)
410 | self.assertEqual(j(v), "container") # because it ends up right after
411 | # Sized in the MRO
412 |
413 | def test_cache_invalidation(self):
414 | try:
415 | from collections import UserDict
416 | except ImportError:
417 | from UserDict import UserDict
418 | class TracingDict(UserDict):
419 | def __init__(self, *args, **kwargs):
420 | UserDict.__init__(self, *args, **kwargs)
421 | self.set_ops = []
422 | self.get_ops = []
423 | def __getitem__(self, key):
424 | result = self.data[key]
425 | self.get_ops.append(key)
426 | return result
427 | def __setitem__(self, key, value):
428 | self.set_ops.append(key)
429 | self.data[key] = value
430 | def clear(self):
431 | self.data.clear()
432 | _orig_wkd = functools.WeakKeyDictionary
433 | td = TracingDict()
434 | functools.WeakKeyDictionary = lambda: td
435 | c = collections
436 | @functools.singledispatch
437 | def g(arg):
438 | return "base"
439 | d = {}
440 | l = []
441 | self.assertEqual(len(td), 0)
442 | self.assertEqual(g(d), "base")
443 | self.assertEqual(len(td), 1)
444 | self.assertEqual(td.get_ops, [])
445 | self.assertEqual(td.set_ops, [dict])
446 | self.assertEqual(td.data[dict], g.registry[object])
447 | self.assertEqual(g(l), "base")
448 | self.assertEqual(len(td), 2)
449 | self.assertEqual(td.get_ops, [])
450 | self.assertEqual(td.set_ops, [dict, list])
451 | self.assertEqual(td.data[dict], g.registry[object])
452 | self.assertEqual(td.data[list], g.registry[object])
453 | self.assertEqual(td.data[dict], td.data[list])
454 | self.assertEqual(g(l), "base")
455 | self.assertEqual(g(d), "base")
456 | self.assertEqual(td.get_ops, [list, dict])
457 | self.assertEqual(td.set_ops, [dict, list])
458 | g.register(list, lambda arg: "list")
459 | self.assertEqual(td.get_ops, [list, dict])
460 | self.assertEqual(len(td), 0)
461 | self.assertEqual(g(d), "base")
462 | self.assertEqual(len(td), 1)
463 | self.assertEqual(td.get_ops, [list, dict])
464 | self.assertEqual(td.set_ops, [dict, list, dict])
465 | self.assertEqual(td.data[dict],
466 | functools._find_impl(dict, g.registry))
467 | self.assertEqual(g(l), "list")
468 | self.assertEqual(len(td), 2)
469 | self.assertEqual(td.get_ops, [list, dict])
470 | self.assertEqual(td.set_ops, [dict, list, dict, list])
471 | self.assertEqual(td.data[list],
472 | functools._find_impl(list, g.registry))
473 | class X(object):
474 | pass
475 | c.MutableMapping.register(X) # Will not invalidate the cache,
476 | # not using ABCs yet.
477 | self.assertEqual(g(d), "base")
478 | self.assertEqual(g(l), "list")
479 | self.assertEqual(td.get_ops, [list, dict, dict, list])
480 | self.assertEqual(td.set_ops, [dict, list, dict, list])
481 | g.register(c.Sized, lambda arg: "sized")
482 | self.assertEqual(len(td), 0)
483 | self.assertEqual(g(d), "sized")
484 | self.assertEqual(len(td), 1)
485 | self.assertEqual(td.get_ops, [list, dict, dict, list])
486 | self.assertEqual(td.set_ops, [dict, list, dict, list, dict])
487 | self.assertEqual(g(l), "list")
488 | self.assertEqual(len(td), 2)
489 | self.assertEqual(td.get_ops, [list, dict, dict, list])
490 | self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
491 | self.assertEqual(g(l), "list")
492 | self.assertEqual(g(d), "sized")
493 | self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict])
494 | self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
495 | g.dispatch(list)
496 | g.dispatch(dict)
497 | self.assertEqual(td.get_ops, [list, dict, dict, list, list, dict,
498 | list, dict])
499 | self.assertEqual(td.set_ops, [dict, list, dict, list, dict, list])
500 | c.MutableSet.register(X) # Will invalidate the cache.
501 | self.assertEqual(len(td), 2) # Stale cache.
502 | self.assertEqual(g(l), "list")
503 | self.assertEqual(len(td), 1)
504 | g.register(c.MutableMapping, lambda arg: "mutablemapping")
505 | self.assertEqual(len(td), 0)
506 | self.assertEqual(g(d), "mutablemapping")
507 | self.assertEqual(len(td), 1)
508 | self.assertEqual(g(l), "list")
509 | self.assertEqual(len(td), 2)
510 | g.register(dict, lambda arg: "dict")
511 | self.assertEqual(g(d), "dict")
512 | self.assertEqual(g(l), "list")
513 | g._clear_cache()
514 | self.assertEqual(len(td), 0)
515 | functools.WeakKeyDictionary = _orig_wkd
516 |
517 |
518 | if __name__ == '__main__':
519 | unittest.main()
520 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py26,py27,py32,py33,py34,pypy
3 |
4 | [testenv]
5 | commands =
6 | {envbindir}/python test_singledispatch.py
7 |
8 | [testenv:py26]
9 | basepython = python2.6
10 | deps =
11 | ordereddict
12 | unittest2
13 |
--------------------------------------------------------------------------------