From e5bf61eee1c62dbb5a2103e68cf73936e4ac3e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Wed, 23 Nov 2016 23:11:04 +0100 Subject: [PATCH] [MRG+1] Dropping python 2.6 support (#7890) * Remove Python 2.6 support Some details about some slightly orthogonal changes: * Note about cheking safely for nan is likely not valid any more (commit introducing it is c80ca91b) * scipy.linalg.qr econ parameter removed since scipy 0.9 in favour of mode='economic' * Remove unnecessary libgfortran in conda create command * Putative fix by setting the random seed * Revert unintended change * Reinstate previous logic for checking for NaNs * Reinstate change in error message Error messages from Python 2.7 assertRegexp does not contain the function name, in contrast with Python 3 assertRegex --- .travis.yml | 2 +- README.rst | 2 +- build_tools/travis/install.sh | 10 +- doc/developers/advanced_installation.rst | 2 +- doc/developers/contributing.rst | 2 +- doc/install.rst | 2 +- setup.py | 1 - sklearn/ensemble/tests/test_base.py | 2 +- sklearn/externals/funcsigs.py | 7 +- sklearn/externals/odict.py | 266 ------------------ sklearn/gaussian_process/gaussian_process.py | 9 +- .../tests/test_coordinate_descent.py | 6 - sklearn/metrics/classification.py | 4 - sklearn/metrics/pairwise.py | 2 +- sklearn/model_selection/tests/test_split.py | 2 +- sklearn/neighbors/tests/test_approximate.py | 3 +- sklearn/preprocessing/data.py | 2 +- sklearn/tests/test_base.py | 5 - sklearn/utils/fixes.py | 43 --- sklearn/utils/testing.py | 89 +----- sklearn/utils/tests/test_estimator_checks.py | 2 +- sklearn/utils/tests/test_testing.py | 40 +-- 22 files changed, 40 insertions(+), 463 deletions(-) delete mode 100644 sklearn/externals/odict.py diff --git a/.travis.yml b/.travis.yml index 5677901f66695..0cc03d1029602 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ env: - DISTRIB="ubuntu" PYTHON_VERSION="2.7" CYTHON_VERSION="0.23.4" COVERAGE=true # This environment tests the oldest supported anaconda env - - DISTRIB="conda" PYTHON_VERSION="2.6" INSTALL_MKL="false" + - DISTRIB="conda" PYTHON_VERSION="2.7" INSTALL_MKL="false" NUMPY_VERSION="1.6.2" SCIPY_VERSION="0.11.0" CYTHON_VERSION="0.23" # This environment tests the newest supported anaconda env # It also runs tests requiring Pandas. diff --git a/README.rst b/README.rst index 75ee8c7c1d64c..9996bab1110fd 100644 --- a/README.rst +++ b/README.rst @@ -48,7 +48,7 @@ Dependencies scikit-learn requires: -- Python (>= 2.6 or >= 3.3) +- Python (>= 2.7 or >= 3.3) - NumPy (>= 1.6.1) - SciPy (>= 0.9) diff --git a/build_tools/travis/install.sh b/build_tools/travis/install.sh index def59e35f1d7c..acd72fbd3b4c1 100755 --- a/build_tools/travis/install.sh +++ b/build_tools/travis/install.sh @@ -53,23 +53,17 @@ if [[ "$DISTRIB" == "conda" ]]; then if [[ "$INSTALL_MKL" == "true" ]]; then conda create -n testenv --yes python=$PYTHON_VERSION pip nose \ numpy=$NUMPY_VERSION scipy=$SCIPY_VERSION numpy scipy \ - libgfortran mkl flake8 \ + mkl flake8 cython=$CYTHON_VERSION \ ${PANDAS_VERSION+pandas=$PANDAS_VERSION} else conda create -n testenv --yes python=$PYTHON_VERSION pip nose \ numpy=$NUMPY_VERSION scipy=$SCIPY_VERSION \ - libgfortran nomkl \ + nomkl cython=$CYTHON_VERSION \ ${PANDAS_VERSION+pandas=$PANDAS_VERSION} fi source activate testenv - # Temporary work around for Python 2.6 because cython >= 0.23 is - # required for building scikit-learn but python 2.6 and cython - # 0.23 are not compatible in conda. Remove the next line and - # install cython via conda when Python 2.6 support is removed. - pip install cython==$CYTHON_VERSION - # Install nose-timer via pip pip install nose-timer diff --git a/doc/developers/advanced_installation.rst b/doc/developers/advanced_installation.rst index b4f6b482f9aa2..5344cbaa9fb86 100644 --- a/doc/developers/advanced_installation.rst +++ b/doc/developers/advanced_installation.rst @@ -35,7 +35,7 @@ Installing an official release Scikit-learn requires: -- Python (>= 2.6 or >= 3.3), +- Python (>= 2.7 or >= 3.3), - NumPy (>= 1.6.1), - SciPy (>= 0.9). diff --git a/doc/developers/contributing.rst b/doc/developers/contributing.rst index 4f406e50c6d63..c06773fae5f39 100644 --- a/doc/developers/contributing.rst +++ b/doc/developers/contributing.rst @@ -702,7 +702,7 @@ Python 3.x support All scikit-learn code should work unchanged in both Python 2.[67] and 3.2 or newer. Since Python 3.x is not backwards compatible, that may require changes to code and it certainly requires testing -on both 2.6 or 2.7, and 3.2 or newer. +on both 2.7 and 3.2 or newer. For most numerical algorithms, Python 3.x support is easy: just remember that ``print`` is a function and diff --git a/doc/install.rst b/doc/install.rst index 4ca21b9436d9d..78008cc6a6069 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -15,7 +15,7 @@ Installing the latest release Scikit-learn requires: -- Python (>= 2.6 or >= 3.3), +- Python (>= 2.7 or >= 3.3), - NumPy (>= 1.6.1), - SciPy (>= 0.9). diff --git a/setup.py b/setup.py index 1a10d70f3ba5d..3817c8773fed4 100755 --- a/setup.py +++ b/setup.py @@ -200,7 +200,6 @@ def setup_package(): 'Operating System :: Unix', 'Operating System :: MacOS', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', diff --git a/sklearn/ensemble/tests/test_base.py b/sklearn/ensemble/tests/test_base.py index bed9a3a5e8122..6b81dbf67466d 100644 --- a/sklearn/ensemble/tests/test_base.py +++ b/sklearn/ensemble/tests/test_base.py @@ -16,7 +16,7 @@ from sklearn.ensemble import BaggingClassifier from sklearn.ensemble.base import _set_random_states from sklearn.linear_model import Perceptron -from sklearn.externals.odict import OrderedDict +from collections import OrderedDict from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.pipeline import Pipeline from sklearn.feature_selection import SelectFromModel diff --git a/sklearn/externals/funcsigs.py b/sklearn/externals/funcsigs.py index 413e310a81710..4e684690b309c 100644 --- a/sklearn/externals/funcsigs.py +++ b/sklearn/externals/funcsigs.py @@ -2,7 +2,7 @@ """Function signature objects for callables Back port of Python 3.3's function signature tools from the inspect module, -modified to be compatible with Python 2.6, 2.7 and 3.2+. +modified to be compatible with Python 2.7 and 3.2+. """ from __future__ import absolute_import, division, print_function import itertools @@ -10,10 +10,7 @@ import re import types -try: - from collections import OrderedDict -except ImportError: - from .odict import OrderedDict +from collections import OrderedDict __version__ = "0.4" diff --git a/sklearn/externals/odict.py b/sklearn/externals/odict.py deleted file mode 100644 index 28808634b97e3..0000000000000 --- a/sklearn/externals/odict.py +++ /dev/null @@ -1,266 +0,0 @@ -# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy. -# Passes Python2.7's test suite and incorporates all the latest updates. -# Copyright 2009 Raymond Hettinger -# http://code.activestate.com/recipes/576693/ -"Ordered dictionary" - -try: - from thread import get_ident as _get_ident -except ImportError: - try: - from dummy_thread import get_ident as _get_ident - except ImportError: - # Ensure that this module is still importable under Python3 to avoid - # crashing code-inspecting tools like nose. - from _dummy_thread import get_ident as _get_ident - -try: - from _abcoll import KeysView, ValuesView, ItemsView -except ImportError: - pass - - -class OrderedDict(dict): - 'Dictionary that remembers insertion order' - # An inherited dict maps keys to values. - # The inherited dict provides __getitem__, __len__, __contains__, and get. - # The remaining methods are order-aware. - # Big-O running times for all methods are the same as for regular dictionaries. - - # The internal self.__map dictionary maps keys to links in a doubly linked list. - # The circular doubly linked list starts and ends with a sentinel element. - # The sentinel element never gets deleted (this simplifies the algorithm). - # Each link is stored as a list of length three: [PREV, NEXT, KEY]. - - def __init__(self, *args, **kwds): - '''Initialize an ordered dictionary. Signature is the same as for - regular dictionaries, but keyword arguments are not recommended - because their insertion order is arbitrary. - - ''' - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__root - except AttributeError: - self.__root = root = [] # sentinel node - root[:] = [root, root, None] - self.__map = {} - self.__update(*args, **kwds) - - def __setitem__(self, key, value, dict_setitem=dict.__setitem__): - 'od.__setitem__(i, y) <==> od[i]=y' - # Setting a new item creates a new link which goes at the end of the linked - # list, and the inherited dictionary is updated with the new key/value pair. - if key not in self: - root = self.__root - last = root[0] - last[1] = root[0] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) - - def __delitem__(self, key, dict_delitem=dict.__delitem__): - 'od.__delitem__(y) <==> del od[y]' - # Deleting an existing item uses self.__map to find the link which is - # then removed by updating the links in the predecessor and successor nodes. - dict_delitem(self, key) - link_prev, link_next, key = self.__map.pop(key) - link_prev[1] = link_next - link_next[0] = link_prev - - def __iter__(self): - 'od.__iter__() <==> iter(od)' - root = self.__root - curr = root[1] - while curr is not root: - yield curr[2] - curr = curr[1] - - def __reversed__(self): - 'od.__reversed__() <==> reversed(od)' - root = self.__root - curr = root[0] - while curr is not root: - yield curr[2] - curr = curr[0] - - def clear(self): - 'od.clear() -> None. Remove all items from od.' - try: - for node in self.__map.itervalues(): - del node[:] - root = self.__root - root[:] = [root, root, None] - self.__map.clear() - except AttributeError: - pass - dict.clear(self) - - def popitem(self, last=True): - '''od.popitem() -> (k, v), return and remove a (key, value) pair. - Pairs are returned in LIFO order if last is true or FIFO order if false. - - ''' - if not self: - raise KeyError('dictionary is empty') - root = self.__root - if last: - link = root[0] - link_prev = link[0] - link_prev[1] = root - root[0] = link_prev - else: - link = root[1] - link_next = link[1] - root[1] = link_next - link_next[0] = root - key = link[2] - del self.__map[key] - value = dict.pop(self, key) - return key, value - - # -- the following methods do not depend on the internal structure -- - - def keys(self): - 'od.keys() -> list of keys in od' - return list(self) - - def values(self): - 'od.values() -> list of values in od' - return [self[key] for key in self] - - def items(self): - 'od.items() -> list of (key, value) pairs in od' - return [(key, self[key]) for key in self] - - def iterkeys(self): - 'od.iterkeys() -> an iterator over the keys in od' - return iter(self) - - def itervalues(self): - 'od.itervalues -> an iterator over the values in od' - for k in self: - yield self[k] - - def iteritems(self): - 'od.iteritems -> an iterator over the (key, value) items in od' - for k in self: - yield (k, self[k]) - - def update(*args, **kwds): - '''od.update(E, **F) -> None. Update od from dict/iterable E and F. - - If E is a dict instance, does: for k in E: od[k] = E[k] - If E has a .keys() method, does: for k in E.keys(): od[k] = E[k] - Or if E is an iterable of items, does: for k, v in E: od[k] = v - In either case, this is followed by: for k, v in F.items(): od[k] = v - - ''' - if len(args) > 2: - raise TypeError('update() takes at most 2 positional ' - 'arguments (%d given)' % (len(args),)) - elif not args: - raise TypeError('update() takes at least 1 argument (0 given)') - self = args[0] - # Make progressively weaker assumptions about "other" - other = () - if len(args) == 2: - other = args[1] - if isinstance(other, dict): - for key in other: - self[key] = other[key] - elif hasattr(other, 'keys'): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value - for key, value in kwds.items(): - self[key] = value - - __update = update # let subclasses override update without breaking __init__ - - __marker = object() - - def pop(self, key, default=__marker): - '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - - ''' - if key in self: - result = self[key] - del self[key] - return result - if default is self.__marker: - raise KeyError(key) - return default - - def setdefault(self, key, default=None): - 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od' - if key in self: - return self[key] - self[key] = default - return default - - def __repr__(self, _repr_running={}): - 'od.__repr__() <==> repr(od)' - call_key = id(self), _get_ident() - if call_key in _repr_running: - return '...' - _repr_running[call_key] = 1 - try: - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - finally: - del _repr_running[call_key] - - def __reduce__(self): - 'Return state information for pickling' - items = [[k, self[k]] for k in self] - inst_dict = vars(self).copy() - for k in vars(OrderedDict()): - inst_dict.pop(k, None) - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def copy(self): - 'od.copy() -> a shallow copy of od' - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S - and values equal to v (which defaults to None). - - ''' - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive - while comparison to a regular mapping is order-insensitive. - - ''' - if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other - - # -- the following methods are only used in Python 2.7 -- - - def viewkeys(self): - "od.viewkeys() -> a set-like object providing a view on od's keys" - return KeysView(self) - - def viewvalues(self): - "od.viewvalues() -> an object providing a view on od's values" - return ValuesView(self) - - def viewitems(self): - "od.viewitems() -> a set-like object providing a view on od's items" - return ItemsView(self) diff --git a/sklearn/gaussian_process/gaussian_process.py b/sklearn/gaussian_process/gaussian_process.py index c521cb5b52f43..eb15e7c936f46 100644 --- a/sklearn/gaussian_process/gaussian_process.py +++ b/sklearn/gaussian_process/gaussian_process.py @@ -618,14 +618,7 @@ def reduced_likelihood_function(self, theta=None): # Get generalized least squares solution Ft = linalg.solve_triangular(C, F, lower=True) - try: - Q, G = linalg.qr(Ft, econ=True) - except: - #/usr/lib/python2.6/dist-packages/scipy/linalg/decomp.py:1177: - # DeprecationWarning: qr econ argument will be removed after scipy - # 0.7. The economy transform will then be available through the - # mode='economic' argument. - Q, G = linalg.qr(Ft, mode='economic') + Q, G = linalg.qr(Ft, mode='economic') sv = linalg.svd(G, compute_uv=False) rcondG = sv[-1] / sv[0] diff --git a/sklearn/linear_model/tests/test_coordinate_descent.py b/sklearn/linear_model/tests/test_coordinate_descent.py index feb66f63f2fb8..507208b24bad5 100644 --- a/sklearn/linear_model/tests/test_coordinate_descent.py +++ b/sklearn/linear_model/tests/test_coordinate_descent.py @@ -32,12 +32,6 @@ from sklearn.utils import check_array -def check_warnings(): - if version_info < (2, 6): - raise SkipTest("Testing for warnings is not supported in versions \ - older than Python 2.6") - - def test_lasso_zero(): # Check that the lasso can handle zero data without crashing X = [[0], [0], [0]] diff --git a/sklearn/metrics/classification.py b/sklearn/metrics/classification.py index ee07fa634d080..43e4d1554f213 100644 --- a/sklearn/metrics/classification.py +++ b/sklearn/metrics/classification.py @@ -430,10 +430,6 @@ def jaccard_similarity_score(y_true, y_pred, normalize=True, pred_or_true = count_nonzero(y_true + y_pred, axis=1) pred_and_true = count_nonzero(y_true.multiply(y_pred), axis=1) score = pred_and_true / pred_or_true - - # If there is no label, it results in a Nan instead, we set - # the jaccard to 1: lim_{x->0} x/x = 1 - # Note with py2.6 and np 1.3: we can't check safely for nan. score[pred_or_true == 0.0] = 1.0 else: score = y_true == y_pred diff --git a/sklearn/metrics/pairwise.py b/sklearn/metrics/pairwise.py index e7599b65fb0fc..2258f070018d2 100644 --- a/sklearn/metrics/pairwise.py +++ b/sklearn/metrics/pairwise.py @@ -10,6 +10,7 @@ # License: BSD 3 clause import itertools +from functools import partial import numpy as np from scipy.spatial import distance @@ -19,7 +20,6 @@ from ..utils import check_array from ..utils import gen_even_slices from ..utils import gen_batches -from ..utils.fixes import partial from ..utils.extmath import row_norms, safe_sparse_dot from ..preprocessing import normalize from ..externals.joblib import Parallel diff --git a/sklearn/model_selection/tests/test_split.py b/sklearn/model_selection/tests/test_split.py index 660b0b1781935..fba323492be85 100644 --- a/sklearn/model_selection/tests/test_split.py +++ b/sklearn/model_selection/tests/test_split.py @@ -7,7 +7,7 @@ from scipy import stats from scipy.misc import comb from itertools import combinations -from sklearn.utils.fixes import combinations_with_replacement +from itertools import combinations_with_replacement from sklearn.utils.testing import assert_true from sklearn.utils.testing import assert_false diff --git a/sklearn/neighbors/tests/test_approximate.py b/sklearn/neighbors/tests/test_approximate.py index 792eccda44161..4d87ad6796a2a 100644 --- a/sklearn/neighbors/tests/test_approximate.py +++ b/sklearn/neighbors/tests/test_approximate.py @@ -229,7 +229,8 @@ def test_radius_neighbors_boundary_handling(): # Build a LSHForest model with hyperparameter values that always guarantee # exact results on this toy dataset. - lsfh = LSHForest(min_hash_match=0, n_candidates=n_points).fit(X) + lsfh = LSHForest(min_hash_match=0, n_candidates=n_points, + random_state=42).fit(X) # define a query aligned with the first axis query = [[1., 0.]] diff --git a/sklearn/preprocessing/data.py b/sklearn/preprocessing/data.py index a633bb44251e2..307e2f47e7d1a 100644 --- a/sklearn/preprocessing/data.py +++ b/sklearn/preprocessing/data.py @@ -9,6 +9,7 @@ from itertools import chain, combinations import numbers import warnings +from itertools import combinations_with_replacement as combinations_w_r import numpy as np from scipy import sparse @@ -19,7 +20,6 @@ from ..utils import deprecated from ..utils.extmath import row_norms from ..utils.extmath import _incremental_mean_and_var -from ..utils.fixes import combinations_with_replacement as combinations_w_r from ..utils.fixes import bincount from ..utils.sparsefuncs_fast import (inplace_csr_row_normalize_l1, inplace_csr_row_normalize_l2) diff --git a/sklearn/tests/test_base.py b/sklearn/tests/test_base.py index d0df21c780466..9983dbdc486bd 100644 --- a/sklearn/tests/test_base.py +++ b/sklearn/tests/test_base.py @@ -180,11 +180,6 @@ def test_clone_sparse_matrices(): getattr(sp, name) for name in dir(sp) if name.endswith('_matrix')] - PY26 = sys.version_info[:2] == (2, 6) - if PY26: - # sp.dok_matrix can not be deepcopied in Python 2.6 - sparse_matrix_classes.remove(sp.dok_matrix) - for cls in sparse_matrix_classes: sparse_matrix = cls(np.eye(5)) clf = MyEstimator(empty=sparse_matrix) diff --git a/sklearn/utils/fixes.py b/sklearn/utils/fixes.py index c7bc8f3078d6b..35dcb9b7ee0e2 100644 --- a/sklearn/utils/fixes.py +++ b/sklearn/utils/fixes.py @@ -204,30 +204,6 @@ def partition(a, kth, axis=-1, kind='introselect', order=None): return np.sort(a, axis=axis, order=order) -try: - from itertools import combinations_with_replacement -except ImportError: - # Backport of itertools.combinations_with_replacement for Python 2.6, - # from Python 3.4 documentation (http://tinyurl.com/comb-w-r), copyright - # Python Software Foundation (https://docs.python.org/3/license.html) - def combinations_with_replacement(iterable, r): - # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC - pool = tuple(iterable) - n = len(pool) - if not n and r: - return - indices = [0] * r - yield tuple(pool[i] for i in indices) - while True: - for i in reversed(range(r)): - if indices[i] != n - 1: - break - else: - return - indices[i:] = [indices[i] + 1] * (r - i) - yield tuple(pool[i] for i in indices) - - if np_version < (1, 7): # Prior to 1.7.0, np.frombuffer wouldn't work for empty first arg. def frombuffer_empty(buf, dtype): @@ -291,25 +267,6 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): from scipy.sparse.linalg import lsqr as sparse_lsqr -if sys.version_info < (2, 7, 0): - # partial cannot be pickled in Python 2.6 - # http://bugs.python.org/issue1398 - class partial(object): - def __init__(self, func, *args, **keywords): - functools.update_wrapper(self, func) - self.func = func - self.args = args - self.keywords = keywords - - def __call__(self, *args, **keywords): - args = self.args + args - kwargs = self.keywords.copy() - kwargs.update(keywords) - return self.func(*args, **kwargs) -else: - from functools import partial - - def parallel_helper(obj, methodname, *args, **kwargs): """Helper to workaround Python 2 limitations of pickling instance methods""" return getattr(obj, methodname)(*args, **kwargs) diff --git a/sklearn/utils/testing.py b/sklearn/utils/testing.py index 1146b4f645048..ce5465ad1f0a8 100644 --- a/sklearn/utils/testing.py +++ b/sklearn/utils/testing.py @@ -77,85 +77,27 @@ assert_true = _dummy.assertTrue assert_false = _dummy.assertFalse assert_raises = _dummy.assertRaises - -try: - SkipTest = unittest.case.SkipTest -except AttributeError: - # Python <= 2.6, we stil need nose here - from nose import SkipTest +SkipTest = unittest.case.SkipTest +assert_dict_equal = _dummy.assertDictEqual +assert_in = _dummy.assertIn +assert_not_in = _dummy.assertNotIn +assert_less = _dummy.assertLess +assert_greater = _dummy.assertGreater +assert_less_equal = _dummy.assertLessEqual +assert_greater_equal = _dummy.assertGreaterEqual -try: - assert_dict_equal = _dummy.assertDictEqual - assert_in = _dummy.assertIn - assert_not_in = _dummy.assertNotIn -except AttributeError: - # Python <= 2.6 - - assert_dict_equal = assert_equal - - def assert_in(x, container): - assert_true(x in container, msg="%r in %r" % (x, container)) - - def assert_not_in(x, container): - assert_false(x in container, msg="%r in %r" % (x, container)) - try: assert_raises_regex = _dummy.assertRaisesRegex except AttributeError: - # for Python 2.6 - def assert_raises_regex(expected_exception, expected_regexp, - callable_obj=None, *args, **kwargs): - """Helper function to check for message patterns in exceptions.""" - not_raised = False - try: - callable_obj(*args, **kwargs) - not_raised = True - except expected_exception as e: - error_message = str(e) - if not re.compile(expected_regexp).search(error_message): - raise AssertionError("Error message should match pattern " - "%r. %r does not." % - (expected_regexp, error_message)) - if not_raised: - raise AssertionError("%s not raised by %s" % - (expected_exception.__name__, - callable_obj.__name__)) - + # Python 2.7 + assert_raises_regex = _dummy.assertRaisesRegexp # assert_raises_regexp is deprecated in Python 3.4 in favor of # assert_raises_regex but lets keep the backward compat in scikit-learn with # the old name for now assert_raises_regexp = assert_raises_regex -def _assert_less(a, b, msg=None): - message = "%r is not lower than %r" % (a, b) - if msg is not None: - message += ": " + msg - assert a < b, message - - -def _assert_greater(a, b, msg=None): - message = "%r is not greater than %r" % (a, b) - if msg is not None: - message += ": " + msg - assert a > b, message - - -def assert_less_equal(a, b, msg=None): - message = "%r is not lower than or equal to %r" % (a, b) - if msg is not None: - message += ": " + msg - assert a <= b, message - - -def assert_greater_equal(a, b, msg=None): - message = "%r is not greater than or equal to %r" % (a, b) - if msg is not None: - message += ": " + msg - assert a >= b, message - - def assert_warns(warning_class, func, *args, **kw): """Test that a certain warning occurs. @@ -273,9 +215,6 @@ def assert_warns_message(warning_class, message, func, *args, **kw): # To remove when we support numpy 1.7 def assert_no_warnings(func, *args, **kw): - # XXX: once we may depend on python >= 2.6, this can be replaced by the - - # warnings module context manager. # very important to avoid uncontrolled state propagation clean_warning_registry() with warnings.catch_warnings(record=True) as w: @@ -384,12 +323,8 @@ def __exit__(self, *exc_info): clean_warning_registry() # be safe and not propagate state + chaos -try: - assert_less = _dummy.assertLess - assert_greater = _dummy.assertGreater -except AttributeError: - assert_less = _assert_less - assert_greater = _assert_greater +assert_less = _dummy.assertLess +assert_greater = _dummy.assertGreater def _assert_allclose(actual, desired, rtol=1e-7, atol=0, diff --git a/sklearn/utils/tests/test_estimator_checks.py b/sklearn/utils/tests/test_estimator_checks.py index de7e1e2f2ac1b..d937a2fbfbae9 100644 --- a/sklearn/utils/tests/test_estimator_checks.py +++ b/sklearn/utils/tests/test_estimator_checks.py @@ -84,7 +84,7 @@ def test_check_estimator(): msg = "object has no attribute 'fit'" assert_raises_regex(AttributeError, msg, check_estimator, BaseEstimator) # check that fit does input validation - msg = "TypeError not raised by fit" + msg = "TypeError not raised" assert_raises_regex(AssertionError, msg, check_estimator, BaseBadClassifier) # check that predict does input validation (doesn't accept dicts in input) msg = "Estimator doesn't check for NaN and inf in predict" diff --git a/sklearn/utils/tests/test_testing.py b/sklearn/utils/tests/test_testing.py index 17e9209dbce85..10657682e5cf1 100644 --- a/sklearn/utils/tests/test_testing.py +++ b/sklearn/utils/tests/test_testing.py @@ -4,8 +4,8 @@ from sklearn.utils.testing import ( assert_raises, - _assert_less, - _assert_greater, + assert_less, + assert_greater, assert_less_equal, assert_greater_equal, assert_warns, @@ -18,33 +18,15 @@ from sklearn.tree import DecisionTreeClassifier from sklearn.discriminant_analysis import LinearDiscriminantAnalysis -try: - from nose.tools import assert_less - - def test_assert_less(): - # Check that the nose implementation of assert_less gives the - # same thing as the scikit's - assert_less(0, 1) - _assert_less(0, 1) - assert_raises(AssertionError, assert_less, 1, 0) - assert_raises(AssertionError, _assert_less, 1, 0) - -except ImportError: - pass - -try: - from nose.tools import assert_greater - - def test_assert_greater(): - # Check that the nose implementation of assert_less gives the - # same thing as the scikit's - assert_greater(1, 0) - _assert_greater(1, 0) - assert_raises(AssertionError, assert_greater, 0, 1) - assert_raises(AssertionError, _assert_greater, 0, 1) - -except ImportError: - pass + +def test_assert_less(): + assert_less(0, 1) + assert_raises(AssertionError, assert_less, 1, 0) + + +def test_assert_greater(): + assert_greater(1, 0) + assert_raises(AssertionError, assert_greater, 0, 1) def test_assert_less_equal():