From b29ed4059dbac588f3a7ffb1006507a2b83b244b Mon Sep 17 00:00:00 2001 From: Charles Harris Date: Wed, 20 Jul 2016 11:09:36 -0600 Subject: [PATCH] BUG: Make sure numpy globals keep identity after reload. Reloading currently causes problems because global classes defined in numpy/__init__.py change their identity (a is b) after reload. The solution taken here is to move those classes to a new non-reloadable module numpy/_globals and import them into numpy from there. Classes moved are ModuleDeprecationWarning, VisibleDeprecationWarning, and _NoValue. Closes #7844. --- numpy/__init__.py | 58 +++++--------------------------- numpy/_globals.py | 62 +++++++++++++++++++++++++++++++++++ numpy/tests/test_reloading.py | 14 ++++++-- 3 files changed, 81 insertions(+), 53 deletions(-) create mode 100644 numpy/_globals.py diff --git a/numpy/__init__.py b/numpy/__init__.py index 076f61065ff8..6220269bdcee 100644 --- a/numpy/__init__.py +++ b/numpy/__init__.py @@ -109,56 +109,8 @@ import sys import warnings -# Disallow reloading numpy. Doing that does nothing to change previously -# loaded modules, which would need to be reloaded separately, but it does -# change the identity of the warnings and sentinal classes defined below -# with dire consequences when checking for identity. -if '_is_loaded' in globals(): - raise RuntimeError('Reloading numpy is not supported') -_is_loaded = True - - -# Define some global warnings and the _NoValue sentinal. Defining them here -# means that their identity will change if numpy is reloaded, hence if that is -# to be allowed they should be moved into their own, non-reloadable module. -# Note that these should be defined (or imported) before the other imports. -class ModuleDeprecationWarning(DeprecationWarning): - """Module deprecation warning. - - The nose tester turns ordinary Deprecation warnings into test failures. - That makes it hard to deprecate whole modules, because they get - imported by default. So this is a special Deprecation warning that the - nose tester will let pass without making tests fail. - - """ - pass - - -class VisibleDeprecationWarning(UserWarning): - """Visible deprecation warning. - - By default, python will not show deprecation warnings, so this class - can be used when a very visible warning is helpful, for example because - the usage is most likely a user bug. - - """ - pass - - -class _NoValue: - """Special keyword value. - - This class may be used as the default value assigned to a deprecated - keyword in order to check if it has been given a user defined value. - """ - pass - - -# oldnumeric and numarray were removed in 1.9. In case some packages import -# but do not use them, we define them here for backward compatibility. -oldnumeric = 'removed' -numarray = 'removed' - +from ._globals import ModuleDeprecationWarning, VisibleDeprecationWarning +from ._globals import _NoValue # We first need to detect if we're being called as part of the numpy setup # procedure itself in a reliable manner. @@ -177,6 +129,7 @@ class _NoValue: its source directory; please exit the numpy source tree, and relaunch your python interpreter from there.""" raise ImportError(msg) + from .version import git_revision as __git_revision__ from .version import version as __version__ @@ -236,3 +189,8 @@ def pkgload(*packages, **options): warnings.filterwarnings("ignore", message="numpy.dtype size changed") warnings.filterwarnings("ignore", message="numpy.ufunc size changed") warnings.filterwarnings("ignore", message="numpy.ndarray size changed") + + # oldnumeric and numarray were removed in 1.9. In case some packages import + # but do not use them, we define them here for backward compatibility. + oldnumeric = 'removed' + numarray = 'removed' diff --git a/numpy/_globals.py b/numpy/_globals.py new file mode 100644 index 000000000000..64a84da96bd2 --- /dev/null +++ b/numpy/_globals.py @@ -0,0 +1,62 @@ +""" +Module defining global singleton classes. + +This module raises a RuntimeError if an attempt to reload it is made. In that +way the identities of the classes defined here are fixed and will remain so +even if numpy itself is reloaded. In particular, a function like the following +will still work correctly after numpy is reloaded:: + + def foo(arg=np._NoValue): + if arg is np._NoValue: + ... + +That was not the case when the singleton classes were defined in the numpy +``__init__.py`` file. See gh-7844 for a discussion of the reload problem that +motivated this module. + +""" +from __future__ import division, absolute_import, print_function + + +__ALL__ = [ + 'ModuleDeprecationWarning', 'VisibleDeprecationWarning', '_NoValue' + ] + + +# Disallow reloading this module so as to preserve the identities of the +# classes defined here. +if '_is_loaded' in globals(): + raise RuntimeError('Reloading numpy._globals is not allowed') +_is_loaded = True + + +class ModuleDeprecationWarning(DeprecationWarning): + """Module deprecation warning. + + The nose tester turns ordinary Deprecation warnings into test failures. + That makes it hard to deprecate whole modules, because they get + imported by default. So this is a special Deprecation warning that the + nose tester will let pass without making tests fail. + + """ + pass + + +class VisibleDeprecationWarning(UserWarning): + """Visible deprecation warning. + + By default, python will not show deprecation warnings, so this class + can be used when a very visible warning is helpful, for example because + the usage is most likely a user bug. + + """ + pass + + +class _NoValue: + """Special keyword value. + + This class may be used as the default value assigned to a deprecated + keyword in order to check if it has been given a user defined value. + """ + pass diff --git a/numpy/tests/test_reloading.py b/numpy/tests/test_reloading.py index 141e11f6cfb0..ca651c8746fd 100644 --- a/numpy/tests/test_reloading.py +++ b/numpy/tests/test_reloading.py @@ -2,7 +2,6 @@ import sys -import numpy as np from numpy.testing import assert_raises, assert_, run_module_suite if sys.version_info[:2] >= (3, 4): @@ -10,13 +9,22 @@ else: from imp import reload -def test_reloading_exception(): +def test_numpy_reloading(): # gh-7844. Also check that relevant globals retain their identity. + import numpy as np + import numpy._globals + _NoValue = np._NoValue VisibleDeprecationWarning = np.VisibleDeprecationWarning ModuleDeprecationWarning = np.ModuleDeprecationWarning - assert_raises(RuntimeError, reload, np) + reload(np) + assert_(_NoValue is np._NoValue) + assert_(ModuleDeprecationWarning is np.ModuleDeprecationWarning) + assert_(VisibleDeprecationWarning is np.VisibleDeprecationWarning) + + assert_raises(RuntimeError, reload, numpy._globals) + reload(np) assert_(_NoValue is np._NoValue) assert_(ModuleDeprecationWarning is np.ModuleDeprecationWarning) assert_(VisibleDeprecationWarning is np.VisibleDeprecationWarning)