summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Harfouche <mark.harfouche@gmail.com>2019-08-22 09:14:09 -0400
committerSebastian Berg <sebastian@sipsolutions.net>2019-08-22 08:14:09 -0500
commit3ca0eb1136102ff01bcc171f53c106326fa4445b (patch)
tree5c9269af8045f7f37e328a13a2e04963bcfae8ed
parentb6a3ab58a9283de32b891072169498487914ee62 (diff)
downloadnumpy-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__.py34
-rw-r--r--numpy/tests/test_public_api.py23
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 == {}