From 773d2c5ab90919489b75c3994b73d70118c19b83 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Tue, 8 Jul 2014 14:53:51 -0400 Subject: [PATCH 1/6] Quick proof-of-concept to cythonize `Dispatcher.__call__/resolve` --- multipledispatch/_dispatcher.pyx | 73 ++++++++++++++++++++++++++++++++ multipledispatch/dispatcher.py | 12 ++++++ setup.py | 12 ++++++ 3 files changed, 97 insertions(+) create mode 100644 multipledispatch/_dispatcher.pyx diff --git a/multipledispatch/_dispatcher.pyx b/multipledispatch/_dispatcher.pyx new file mode 100644 index 0000000..ad2dce8 --- /dev/null +++ b/multipledispatch/_dispatcher.pyx @@ -0,0 +1,73 @@ +import cython + +from cpython.dict cimport PyDict_GetItem, PyDict_SetItem +from cpython.object cimport PyObject_Call +from cpython.ref cimport PyObject, Py_INCREF +from cpython.tuple cimport PyTuple_GET_ITEM, PyTuple_GET_SIZE, PyTuple_New, PyTuple_SET_ITEM + + +@cython.binding(True) +def __call__(self, *args, **kwargs): + cdef object val + cdef Py_ssize_t i, N + N = PyTuple_GET_SIZE(args) + types = PyTuple_New(N) + for i in range(N): + val = type(PyTuple_GET_ITEM(args, i)) + Py_INCREF(val) + PyTuple_SET_ITEM(types, i, val) + + obj = PyDict_GetItem(self._cache, types) + if obj is not NULL: + return PyObject_Call(obj, args, kwargs) + else: + val = self.resolve(types) + PyDict_SetItem(self._cache, types, val) + return PyObject_Call(val, args, kwargs) + + +@cython.binding(True) +def resolve(self, types): + """ Deterimine appropriate implementation for this type signature + + This method is internal. Users should call this object as a function. + Implementation resolution occurs within the ``__call__`` method. + + >>> from multipledispatch import dispatch + >>> @dispatch(int) + ... def inc(x): + ... return x + 1 + + >>> implementation = inc.resolve((int,)) + >>> implementation(3) + 4 + + >>> inc.resolve((float,)) + Traceback (most recent call last): + ... + NotImplementedError: Could not find signature for inc: + + See Also: + ``multipledispatch.conflict`` - module to determine resolution order + """ + cdef PyObject *obj = PyDict_GetItem(self.funcs, types) + if obj is not NULL: + return obj + + n = len(types) + for signature in self.ordering: + if len(signature) == n and all(map(issubclass, types, signature)): + result = self.funcs[signature] + return result + raise NotImplementedError('Could not find signature for %s: <%s>' % + (self.name, str_signature(types))) + + +# TODO: import from elsewhere +cdef object str_signature(sig): + """ String representation of type signature + + >>> str_signature((int, float)) + 'int, float' + """ + return ', '.join([cls.__name__ for cls in sig]) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 6fd3728..2228f3c 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -2,6 +2,12 @@ from warnings import warn from .utils import expand_tuples +try: + from . import _dispatcher + _USE_FAST = True +except ImportError: + _USE_FAST = False + def ambiguity_warn(dispatcher, ambiguities): """ Raise warning when ambiguity is detected @@ -221,6 +227,7 @@ def str_signature(sig): """ return ', '.join(cls.__name__ for cls in sig) + def warning_text(name, amb): """ The text for ambiguity warnings """ text = "\nAmbiguities exist in dispatched function %s\n\n"%(name) @@ -231,3 +238,8 @@ def warning_text(name, amb): text += '\n\n'.join(['@dispatch(' + str_signature(super_signature(s)) + ')\ndef %s(...)'%name for s in amb]) return text + + +if _USE_FAST: + Dispatcher.__call__ = _dispatcher.__call__ + Dispatcher.resolve = _dispatcher.resolve diff --git a/setup.py b/setup.py index cdf8e02..0e7507c 100755 --- a/setup.py +++ b/setup.py @@ -2,11 +2,23 @@ from os.path import exists from setuptools import setup +from setuptools.extension import Extension import multipledispatch +# XXX Quick hack to add this is. We will make Cython and C compiler optional +from Cython.Build import cythonize +suffix = '.pyx' +ext_modules = [] +for modname in ['_dispatcher']: + ext_modules.append(Extension('multipledispatch.' + modname, + ['multipledispatch/' + modname + suffix])) +ext_modules = cythonize(ext_modules) + + setup(name='multipledispatch', version=multipledispatch.__version__, description='Multiple dispatch', + ext_modules=ext_modules, url='http://github.com/mrocklin/multipledispatch/', author='Matthew Rocklin', author_email='mrocklin@gmail.com', From a4692740bc85953d56eff58d1e30c1ff2364317d Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Tue, 8 Jul 2014 15:01:53 -0400 Subject: [PATCH 2/6] Forgot to have TravisCI use Cython --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a0b7108..30ff3ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ python: install: - pip install coverage --use-mirrors + - pip install cython --use-mirrors + - python setup.py build_ext --inplace script: - nosetests --with-doctest From 3ab42e5ee7db9cdde8e39004e4d7191848b9d6d5 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Thu, 10 Jul 2014 12:34:01 -0400 Subject: [PATCH 3/6] Faster Cythonized Dispatcher Dispatcher is now derived from a C-extension base class. This improves performance two ways: 1. Being called via `__call__` is much faster 2. Attribute lookup (i.e., `_cache`, `funcs`, etc.) is much faster I also added an optimization in the Cython version of Dispatcher when dispatching for a single positional argument by not constructing a tuple for the key for `_cache`. I do, however, pass a tuple to `resolve`. Cythonized versions should not be used in PyPy. Hopefully the check works. TravisCI script was modified to test with and without using Cython, and it verifies if the C core was used when expected. Hopefully this works too. --- .travis.yml | 13 ++- multipledispatch/_dispatcher.pxd | 10 ++ multipledispatch/_dispatcher.pyx | 146 +++++++++++++++---------- multipledispatch/dispatcher.py | 176 ++++++++++++++++--------------- 4 files changed, 202 insertions(+), 143 deletions(-) create mode 100644 multipledispatch/_dispatcher.pxd diff --git a/.travis.yml b/.travis.yml index 30ff3ae..119897d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,20 @@ python: - "3.3" - "pypy" +env: + - USE_CYTHON=True + - USE_CYTHON=False + install: - pip install coverage --use-mirrors - - pip install cython --use-mirrors - - python setup.py build_ext --inplace - + - if [[ $USE_CYTHON == 'True' ]]; then + - pip install cython --use-mirrors + - python setup.py build_ext --inplace + - fi script: - nosetests --with-doctest + - if [[ $TRAVIS_PYTHON_VERSION == 'pypy' ]]; then USE_CYTHON=False ; fi + - python -c 'import multipledispatch ; assert multipledispatch.dispatcher.USE_CYTHON == '$USE_CYTHON after_success: - if [[ $TRAVIS_PYTHON_VERSION != 'pypy' ]]; then pip install coveralls --use-mirrors ; coveralls ; fi diff --git a/multipledispatch/_dispatcher.pxd b/multipledispatch/_dispatcher.pxd new file mode 100644 index 0000000..532ed50 --- /dev/null +++ b/multipledispatch/_dispatcher.pxd @@ -0,0 +1,10 @@ +cdef class DispatcherBase: + cdef public object name + cdef public object funcs + cdef public object ordering + cdef public object _cache + + +cdef class MethodDispatcherBase(DispatcherBase): + cdef public object obj + cdef public object cls diff --git a/multipledispatch/_dispatcher.pyx b/multipledispatch/_dispatcher.pyx index ad2dce8..ecc7464 100644 --- a/multipledispatch/_dispatcher.pyx +++ b/multipledispatch/_dispatcher.pyx @@ -1,73 +1,107 @@ -import cython +from .conflict import ordering +from .dispatcher import str_signature from cpython.dict cimport PyDict_GetItem, PyDict_SetItem from cpython.object cimport PyObject_Call from cpython.ref cimport PyObject, Py_INCREF -from cpython.tuple cimport PyTuple_GET_ITEM, PyTuple_GET_SIZE, PyTuple_New, PyTuple_SET_ITEM - - -@cython.binding(True) -def __call__(self, *args, **kwargs): - cdef object val - cdef Py_ssize_t i, N - N = PyTuple_GET_SIZE(args) - types = PyTuple_New(N) - for i in range(N): - val = type(PyTuple_GET_ITEM(args, i)) - Py_INCREF(val) - PyTuple_SET_ITEM(types, i, val) - - obj = PyDict_GetItem(self._cache, types) - if obj is not NULL: - return PyObject_Call(obj, args, kwargs) - else: - val = self.resolve(types) +from cpython.tuple cimport (PyTuple_GET_ITEM, PyTuple_GET_SIZE, PyTuple_New, + PyTuple_SET_ITEM) + + +cdef class DispatcherBase: + def __call__(self, *args, **kwargs): + cdef PyObject *obj + cdef object val + cdef Py_ssize_t i, N = PyTuple_GET_SIZE(args) + if N == 1: + types = type(PyTuple_GET_ITEM(args, 0)) + else: + types = PyTuple_New(N) + for i in range(N): + val = type(PyTuple_GET_ITEM(args, i)) + Py_INCREF(val) + PyTuple_SET_ITEM(types, i, val) + + obj = PyDict_GetItem(self._cache, types) + if obj is not NULL: + return PyObject_Call(obj, args, kwargs) + elif N == 1: + # *Always* pass a tuple to `self.resolve` + val = self.resolve((types,)) + else: + val = self.resolve(types) PyDict_SetItem(self._cache, types, val) return PyObject_Call(val, args, kwargs) + def resolve(self, types): + """ Deterimine appropriate implementation for this type signature -@cython.binding(True) -def resolve(self, types): - """ Deterimine appropriate implementation for this type signature + This method is internal. Users should call this object as a function. + Implementation resolution occurs within the ``__call__`` method. - This method is internal. Users should call this object as a function. - Implementation resolution occurs within the ``__call__`` method. + >>> from multipledispatch import dispatch + >>> @dispatch(int) + ... def inc(x): + ... return x + 1 - >>> from multipledispatch import dispatch - >>> @dispatch(int) - ... def inc(x): - ... return x + 1 + >>> implementation = inc.resolve((int,)) + >>> implementation(3) + 4 - >>> implementation = inc.resolve((int,)) - >>> implementation(3) - 4 + >>> inc.resolve((float,)) + Traceback (most recent call last): + ... + NotImplementedError: Could not find signature for inc: - >>> inc.resolve((float,)) - Traceback (most recent call last): - ... - NotImplementedError: Could not find signature for inc: + See Also: + ``multipledispatch.conflict`` - module to determine resolution order + """ + cdef PyObject *obj = PyDict_GetItem(self.funcs, types) + if obj is not NULL: + return obj - See Also: - ``multipledispatch.conflict`` - module to determine resolution order - """ - cdef PyObject *obj = PyDict_GetItem(self.funcs, types) - if obj is not NULL: - return obj + n = len(types) + for signature in self.ordering: + if len(signature) == n and all(map(issubclass, types, signature)): + result = self.funcs[signature] + return result + raise NotImplementedError('Could not find signature for %s: <%s>' % + (self.name, str_signature(types))) - n = len(types) - for signature in self.ordering: - if len(signature) == n and all(map(issubclass, types, signature)): - result = self.funcs[signature] - return result - raise NotImplementedError('Could not find signature for %s: <%s>' % - (self.name, str_signature(types))) + def __reduce__(self): + return (type(self), (self.name,), self.funcs) + def __setstate__(self, state): + self.funcs = state + self._cache = {} + self.ordering = ordering(self.funcs) -# TODO: import from elsewhere -cdef object str_signature(sig): - """ String representation of type signature - >>> str_signature((int, float)) - 'int, float' - """ - return ', '.join([cls.__name__ for cls in sig]) +cdef class MethodDispatcherBase(DispatcherBase): + def __get__(self, instance, owner): + self.obj = instance + self.cls = owner + return self + + def __call__(self, *args, **kwargs): + # types = tuple([type(arg) for arg in args]) + # func = self.resolve(types) + # return func(self.obj, *args, **kwargs) + + cdef PyObject *obj + cdef object val + cdef Py_ssize_t i, N = PyTuple_GET_SIZE(args) + types = PyTuple_New(N) + selfargs = PyTuple_New(N + 1) + Py_INCREF(self.obj) + PyTuple_SET_ITEM(selfargs, 0, self.obj) + for i in range(N): + val = PyTuple_GET_ITEM(args, i) + Py_INCREF(val) + PyTuple_SET_ITEM(selfargs, i + 1, val) + val = type(val) + Py_INCREF(val) + PyTuple_SET_ITEM(types, i, val) + + val = self.resolve(types) + return PyObject_Call(val, selfargs, kwargs) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 2228f3c..c9da9d5 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -2,11 +2,96 @@ from warnings import warn from .utils import expand_tuples + +def str_signature(sig): + """ String representation of type signature + + >>> str_signature((int, float)) + 'int, float' + """ + return ', '.join(cls.__name__ for cls in sig) + + try: - from . import _dispatcher - _USE_FAST = True + import platform + USE_CYTHON = platform.python_implementation() != 'PyPy' + if USE_CYTHON: + from ._dispatcher import DispatcherBase, MethodDispatcherBase except ImportError: - _USE_FAST = False + USE_CYTHON = False + + +if not USE_CYTHON: + class DispatcherBase(object): + __slots__ = 'name', 'funcs', 'ordering', '_cache' + + def __call__(self, *args, **kwargs): + types = tuple([type(arg) for arg in args]) + try: + func = self._cache[types] + except KeyError: + func = self.resolve(types) + self._cache[types] = func + return func(*args, **kwargs) + + def resolve(self, types): + """ Deterimine appropriate implementation for this type signature + + This method is internal. Users should call this object as a function. + Implementation resolution occurs within the ``__call__`` method. + + >>> from multipledispatch import dispatch + >>> @dispatch(int) + ... def inc(x): + ... return x + 1 + + >>> implementation = inc.resolve((int,)) + >>> implementation(3) + 4 + + >>> inc.resolve((float,)) + Traceback (most recent call last): + ... + NotImplementedError: Could not find signature for inc: + + See Also: + ``multipledispatch.conflict`` - module to determine resolution order + """ + + if types in self.funcs: + return self.funcs[types] + + n = len(types) + for signature in self.ordering: + if len(signature) == n and all(map(issubclass, types, signature)): + result = self.funcs[signature] + return result + raise NotImplementedError('Could not find signature for %s: <%s>' % + (self.name, str_signature(types))) + + def __getstate__(self): + return {'name': self.name, + 'funcs': self.funcs} + + def __setstate__(self, d): + self.name = d['name'] + self.funcs = d['funcs'] + self.ordering = ordering(self.funcs) + self._cache = dict() + + + class MethodDispatcherBase(DispatcherBase): + __slots__ = 'obj', 'cls' + + def __get__(self, instance, owner): + self.obj = instance + self.cls = owner + return self + + def __call__(self, *args, **kwargs): + types = tuple([type(arg) for arg in args]) + func = self.resolve(types) + return func(self.obj, *args, **kwargs) def ambiguity_warn(dispatcher, ambiguities): @@ -26,7 +111,7 @@ def ambiguity_warn(dispatcher, ambiguities): warn(warning_text(dispatcher.name, ambiguities), AmbiguityWarning) -class Dispatcher(object): +class Dispatcher(DispatcherBase): """ Dispatch methods based on type signature Use ``dispatch`` to add implementations @@ -48,7 +133,7 @@ class Dispatcher(object): >>> f(3.0) 2.0 """ - __slots__ = 'name', 'funcs', 'ordering', '_cache' + __slots__ = () def __init__(self, name): self.name = name @@ -118,65 +203,10 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): on_ambiguity(self, amb) self._cache.clear() - def __call__(self, *args, **kwargs): - types = tuple([type(arg) for arg in args]) - try: - func = self._cache[types] - except KeyError: - func = self.resolve(types) - self._cache[types] = func - return func(*args, **kwargs) - def __str__(self): return "" % self.name __repr__ = __str__ - def resolve(self, types): - """ Deterimine appropriate implementation for this type signature - - This method is internal. Users should call this object as a function. - Implementation resolution occurs within the ``__call__`` method. - - >>> from multipledispatch import dispatch - >>> @dispatch(int) - ... def inc(x): - ... return x + 1 - - >>> implementation = inc.resolve((int,)) - >>> implementation(3) - 4 - - >>> inc.resolve((float,)) - Traceback (most recent call last): - ... - NotImplementedError: Could not find signature for inc: - - See Also: - ``multipledispatch.conflict`` - module to determine resolution order - """ - - if types in self.funcs: - return self.funcs[types] - - n = len(types) - for signature in self.ordering: - if len(signature) == n and all(map(issubclass, types, signature)): - result = self.funcs[signature] - return result - raise NotImplementedError('Could not find signature for %s: <%s>' % - (self.name, str_signature(types))) - - def __getstate__(self): - return {'name': self.name, - 'funcs': self.funcs} - - def __setstate__(self, d): - self.name = d['name'] - self.funcs = d['funcs'] - self.ordering = ordering(self.funcs) - self._cache = dict() - - @property def __doc__(self): doc = " Multiply dispatched method: %s\n\n" % self.name @@ -202,30 +232,13 @@ def __doc__(self): return doc -class MethodDispatcher(Dispatcher): +class MethodDispatcher(MethodDispatcherBase, Dispatcher): """ Dispatch methods based on type signature See Also: Dispatcher """ - def __get__(self, instance, owner): - self.obj = instance - self.cls = owner - return self - - def __call__(self, *args, **kwargs): - types = tuple([type(arg) for arg in args]) - func = self.resolve(types) - return func(self.obj, *args, **kwargs) - - -def str_signature(sig): - """ String representation of type signature - - >>> str_signature((int, float)) - 'int, float' - """ - return ', '.join(cls.__name__ for cls in sig) + __slots__ = () def warning_text(name, amb): @@ -238,8 +251,3 @@ def warning_text(name, amb): text += '\n\n'.join(['@dispatch(' + str_signature(super_signature(s)) + ')\ndef %s(...)'%name for s in amb]) return text - - -if _USE_FAST: - Dispatcher.__call__ = _dispatcher.__call__ - Dispatcher.resolve = _dispatcher.resolve From 919037f016f3768bfe099bb4eb332c6c09bdf219 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Thu, 10 Jul 2014 17:40:24 -0400 Subject: [PATCH 4/6] Update "setup.py" so Cython is optional and C compilation fails gracefully --- .travis.yml | 2 +- setup.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 119897d..9085384 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: - pip install coverage --use-mirrors - if [[ $USE_CYTHON == 'True' ]]; then - pip install cython --use-mirrors - - python setup.py build_ext --inplace + - python setup.py build_ext --inplace --with-cython - fi script: - nosetests --with-doctest diff --git a/setup.py b/setup.py index 0e7507c..8e37fc5 100755 --- a/setup.py +++ b/setup.py @@ -1,23 +1,113 @@ #!/usr/bin/env python +""" Build and install ``multipledispatch`` with or without Cython or C compiler +Building with Cython must be specified with the "--with-cython" option such as: + + $ python setup.py build_ext --inplace --with-cython + +Not using Cython by default makes contributing to ``multipledispatch`` easy, +because Cython and a C compiler are not required for development or usage. + +During installation, C extension modules will be automatically built with a +C compiler if possible, but will fail gracefully if there is an error during +compilation. Use of C extensions significantly improves the performance of +``multipledispatch``, but a pure Python implementation will be used if the +extension modules are unavailable. + +""" +import sys +import warnings from os.path import exists -from setuptools import setup -from setuptools.extension import Extension +from setuptools import setup, Extension +from distutils.command.build_ext import build_ext +from distutils.errors import (CCompilerError, DistutilsExecError, + DistutilsPlatformError) import multipledispatch -# XXX Quick hack to add this is. We will make Cython and C compiler optional -from Cython.Build import cythonize -suffix = '.pyx' +try: + from Cython.Build import cythonize + has_cython = True +except ImportError: + has_cython = False + +use_cython = False +if '--without-cython' in sys.argv: + sys.argv.remove('--without-cython') + +if '--with-cython' in sys.argv: + use_cython = True + sys.argv.remove('--with-cython') + if use_cython and not has_cython: + raise RuntimeError('ERROR: Cython not found. Exiting.\n ' + 'Install Cython or don\'t use "--with-cython"') + +suffix = '.pyx' if use_cython else '.c' ext_modules = [] for modname in ['_dispatcher']: ext_modules.append(Extension('multipledispatch.' + modname, ['multipledispatch/' + modname + suffix])) -ext_modules = cythonize(ext_modules) +if use_cython: + ext_modules = cythonize(ext_modules) + + +build_exceptions = (CCompilerError, DistutilsExecError, DistutilsPlatformError, + IOError, SystemError) + + +class build_ext_may_fail(build_ext): + """ Allow compilation of extensions modules to fail, but warn if they do""" + + warning_message = """ +********************************************************************* +WARNING: %s + could not be compiled. See the output above for details. + +Compiled C extension modules are not required for `multipledispatch` +to run, but they do result in significant speed improvements. +Proceeding to build `multipledispatch` as a pure Python package. + +If you are using Linux, you probably need to install GCC or the +Python development package. + +Debian and Ubuntu users should issue the following command: + + $ sudo apt-get install build-essential python-dev + +RedHat, CentOS, and Fedora users should issue the following command: + + $ sudo yum install gcc python-devel + +********************************************************************* +""" + + def run(self): + try: + build_ext.run(self) + except build_exceptions: + self.warn_failed() + + def build_extension(self, ext): + try: + build_ext.build_extension(self, ext) + except build_exceptions: + self.warn_failed(name=ext.name) + + def warn_failed(self, name=None): + if name is None: + name = 'Extension modules' + else: + name = 'The "%s" extension module' % name + exc = sys.exc_info()[1] + sys.stdout.write('%s\n' % str(exc)) + warnings.warn(self.warning_message % name) + +cmdclass = {} if use_cython else {'build_ext': build_ext_may_fail} setup(name='multipledispatch', version=multipledispatch.__version__, description='Multiple dispatch', + cmdclass=cmdclass, ext_modules=ext_modules, url='http://github.com/mrocklin/multipledispatch/', author='Matthew Rocklin', @@ -25,6 +115,7 @@ license='BSD', keywords='dispatch', packages=['multipledispatch'], + package_data={'multipledispatch': ['*.pxd', '*.pyx']}, long_description=(open('README.md').read() if exists('README.md') else ''), zip_safe=False) From 1a9faf2640addb603b1d55d7aacfeba901ae3d4f Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 16 Jul 2014 20:08:37 -0400 Subject: [PATCH 5/6] Only use C extension dispatchers when using CPython. I don't know how well C extensions are supported in other implementations of Python. Hence, let's choose to do the conservative thing and only use Cythonized versions of dispatchers for CPython. Also added a Makefile for convenient building and testing. --- .gitignore | 3 +++ Makefile | 7 +++++++ multipledispatch/dispatcher.py | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index 0d20b64..c3cd320 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ *.pyc +*.swp +*.so +*.html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..565c9e4 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +PYTHON ?= python + +inplace: + $(PYTHON) setup.py build_ext --inplace --with-cython + +test: inplace + nosetests -s --with-doctest multipledispatch/ diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index c9da9d5..a29bc1e 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -14,7 +14,7 @@ def str_signature(sig): try: import platform - USE_CYTHON = platform.python_implementation() != 'PyPy' + USE_CYTHON = platform.python_implementation() == 'CPython' if USE_CYTHON: from ._dispatcher import DispatcherBase, MethodDispatcherBase except ImportError: From e63ae924b9a623c6bf599d4521b1c8e55b7b5973 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Thu, 17 Jul 2014 17:40:29 -0400 Subject: [PATCH 6/6] Use `_cache` for method dispatching too --- multipledispatch/_dispatcher.pyx | 17 +++++++++-------- multipledispatch/dispatcher.py | 6 +++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/multipledispatch/_dispatcher.pyx b/multipledispatch/_dispatcher.pyx index ecc7464..7cf287f 100644 --- a/multipledispatch/_dispatcher.pyx +++ b/multipledispatch/_dispatcher.pyx @@ -84,15 +84,11 @@ cdef class MethodDispatcherBase(DispatcherBase): return self def __call__(self, *args, **kwargs): - # types = tuple([type(arg) for arg in args]) - # func = self.resolve(types) - # return func(self.obj, *args, **kwargs) - cdef PyObject *obj cdef object val cdef Py_ssize_t i, N = PyTuple_GET_SIZE(args) - types = PyTuple_New(N) - selfargs = PyTuple_New(N + 1) + types = PyTuple_New(N) # = tuple([type(arg) for arg in args]) + selfargs = PyTuple_New(N + 1) # = (self.obj,) + args Py_INCREF(self.obj) PyTuple_SET_ITEM(selfargs, 0, self.obj) for i in range(N): @@ -103,5 +99,10 @@ cdef class MethodDispatcherBase(DispatcherBase): Py_INCREF(val) PyTuple_SET_ITEM(types, i, val) - val = self.resolve(types) - return PyObject_Call(val, selfargs, kwargs) + obj = PyDict_GetItem(self._cache, types) + if obj is not NULL: + return PyObject_Call(obj, selfargs, kwargs) + else: + val = self.resolve(types) + PyDict_SetItem(self._cache, types, val) + return PyObject_Call(val, selfargs, kwargs) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index a29bc1e..877751b 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -90,7 +90,11 @@ def __get__(self, instance, owner): def __call__(self, *args, **kwargs): types = tuple([type(arg) for arg in args]) - func = self.resolve(types) + try: + func = self._cache[types] + except KeyError: + func = self.resolve(types) + self._cache[types] = func return func(self.obj, *args, **kwargs)