├── .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 | --------------------------------------------------------------------------------