diff options
author | Mark Harfouche <mark.harfouche@gmail.com> | 2019-08-22 09:14:09 -0400 |
---|---|---|
committer | Sebastian Berg <sebastian@sipsolutions.net> | 2019-08-22 08:14:09 -0500 |
commit | 3ca0eb1136102ff01bcc171f53c106326fa4445b (patch) | |
tree | 5c9269af8045f7f37e328a13a2e04963bcfae8ed | |
parent | b6a3ab58a9283de32b891072169498487914ee62 (diff) | |
download | numpy-3ca0eb1136102ff01bcc171f53c106326fa4445b.tar.gz |
MAINT: Lazy import testing on python >=3.7 (#14097)
On new python versions, the module level `__getattr__` can be used to import testing and
Tester only when needed (at no speed cost, except for the first time import). Since most users
never use testing, this avoids an expensive import.
-rw-r--r-- | numpy/__init__.py | 34 | ||||
-rw-r--r-- | numpy/tests/test_public_api.py | 23 |
2 files changed, 54 insertions, 3 deletions
diff --git a/numpy/__init__.py b/numpy/__init__.py index ba88c733f..ae297597e 100644 --- a/numpy/__init__.py +++ b/numpy/__init__.py @@ -166,6 +166,8 @@ else: # now that numpy modules are imported, can initialize limits core.getlimits._register_known_types() + __all__.extend(['bool', 'int', 'float', 'complex', 'object', 'unicode', + 'str']) __all__.extend(['__version__', 'show_config']) __all__.extend(core.__all__) __all__.extend(_mat.__all__) @@ -182,9 +184,35 @@ else: oldnumeric = 'removed' numarray = 'removed' - # We don't actually use this ourselves anymore, but I'm not 100% sure that - # no-one else in the world is using it (though I hope not) - from .testing import Tester + if sys.version_info[:2] >= (3, 7): + # Importing Tester requires importing all of UnitTest which is not a + # cheap import Since it is mainly used in test suits, we lazy import it + # here to save on the order of 10 ms of import time for most users + # + # The previous way Tester was imported also had a side effect of adding + # the full `numpy.testing` namespace + # + # module level getattr is only supported in 3.7 onwards + # https://www.python.org/dev/peps/pep-0562/ + def __getattr__(attr): + if attr == 'testing': + import numpy.testing as testing + return testing + elif attr == 'Tester': + from .testing import Tester + return Tester + else: + raise AttributeError( + "module %s has no attribute $s".format(__name__, attr)) + + + def __dir__(): + return __all__ + ['Tester', 'testing'] + + else: + # We don't actually use this ourselves anymore, but I'm not 100% sure that + # no-one else in the world is using it (though I hope not) + from .testing import Tester # Pytest testing from numpy._pytesttester import PytestTester diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index 807c98652..df2fc4802 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -1,6 +1,7 @@ from __future__ import division, absolute_import, print_function import sys +import subprocess import numpy as np import pytest @@ -69,6 +70,28 @@ def test_numpy_namespace(): assert bad_results == whitelist +@pytest.mark.parametrize('name', ['testing', 'Tester']) +def test_import_lazy_import(name): + """Make sure we can actually the the modules we lazy load. + + While not exported as part of the public API, it was accessible. With the + use of __getattr__ and __dir__, this isn't always true It can happen that + an infinite recursion may happen. + + This is the only way I found that would force the failure to appear on the + badly implemented code. + + We also test for the presence of the lazily imported modules in dir + + """ + exe = (sys.executable, '-c', "import numpy; numpy." + name) + result = subprocess.check_output(exe) + assert not result + + # Make sure they are still in the __dir__ + assert name in dir(np) + + def test_numpy_linalg(): bad_results = check_dir(np.linalg) assert bad_results == {} |