Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Don't import big modules (Numpy, Pandas) until it's really necessary #259

Merged
merged 22 commits into from
Jul 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2b25909
Nsview: Don't import Numpy until it's really necessary
ccordoba12 Nov 15, 2020
545e83e
Nsview: Don't import Pandas until's really necessary
ccordoba12 Nov 15, 2020
7a2126f
Nsview: Import Pillow only when it's necessary
ccordoba12 Nov 15, 2020
373f8bb
Nsview: Import bs4 only when it's necessary
ccordoba12 Nov 15, 2020
0369db0
Kernel: Move nsview imports to the top of the tile
ccordoba12 Nov 15, 2020
b19bcbd
Testing: Fix tests
ccordoba12 Nov 15, 2020
566c413
Iofuncs: Use delayed pandas module
ccordoba12 Nov 15, 2020
abcd231
Iofuncs: Don't import Numpy and Pillow when it's not necessary
ccordoba12 Nov 16, 2020
659ffb5
Iofunc: Don't import scipy if it's not necessary
ccordoba12 Nov 16, 2020
dc3dcad
Delayed mods: Fix errors when accessing second level attributes
ccordoba12 Nov 16, 2020
b7b7f92
Delayed mods: Improve organization and add notes
ccordoba12 Nov 16, 2020
9663c24
Reorganize imports related to delayed modules
ccordoba12 Nov 16, 2020
318a404
Testing: Fix conda installation
ccordoba12 Nov 16, 2020
3e3d033
Merge branch '2.x' into delayed-modules
ccordoba12 Jun 12, 2021
3292155
Rename delayedmods to lazymodules
ccordoba12 Jun 12, 2021
509a79a
Simplify implementation of lazy modules and add tests for it
ccordoba12 Jun 13, 2021
7782973
Testing: Remove unnecessary variable
ccordoba12 Jun 13, 2021
c6e6f28
Move Matplotlib utilities to its own module
ccordoba12 Jun 13, 2021
748543b
Fix lazy module for PIL
ccordoba12 Jun 13, 2021
0753528
Rename lazy module class
ccordoba12 Jun 13, 2021
6c7a9d8
Simplify lazy modules imports
ccordoba12 Jun 13, 2021
1362995
Merge branch '2.x' into delayed-modules
ccordoba12 Jul 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions spyder_kernels/console/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
from spyder_kernels.py3compat import TEXT_TYPES, to_text_string
from spyder_kernels.comms.frontendcomm import FrontendComm
from spyder_kernels.py3compat import PY3, input
from spyder_kernels.utils.misc import (
from spyder_kernels.utils.iofuncs import iofunctions
from spyder_kernels.utils.mpl import (
MPL_BACKENDS_FROM_SPYDER, MPL_BACKENDS_TO_SPYDER, INLINE_FIGURE_FORMATS)
from spyder_kernels.utils.nsview import get_remote_data, make_remote_view


# Excluded variables from the Variable Explorer (i.e. they are not
Expand Down Expand Up @@ -157,7 +159,6 @@ def get_namespace_view(self):
* 'numpy_type' is its Numpy type (if any) computed with
`get_numpy_type_string`.
"""
from spyder_kernels.utils.nsview import make_remote_view

settings = self.namespace_view_settings
if settings:
Expand All @@ -172,8 +173,6 @@ def get_var_properties(self):
Get some properties of the variables in the current
namespace
"""
from spyder_kernels.utils.nsview import get_remote_data

settings = self.namespace_view_settings
if settings:
ns = self._get_current_namespace()
Expand Down Expand Up @@ -234,11 +233,9 @@ def load_data(self, filename, ext, overwrite=False):
In the other hand, with 'overwrite=False', a new variable will be
created with a sufix starting with 000 i.e 'var000' (default behavior).
"""
from spyder_kernels.utils.iofuncs import iofunctions
from spyder_kernels.utils.misc import fix_reference_name

glbs = self._mglobals()

load_func = iofunctions.load_funcs[ext]
data, error_message = load_func(filename)

Expand All @@ -261,9 +258,6 @@ def load_data(self, filename, ext, overwrite=False):

def save_namespace(self, filename):
"""Save namespace into filename"""
from spyder_kernels.utils.nsview import get_remote_data
from spyder_kernels.utils.iofuncs import iofunctions

ns = self._get_current_namespace()
settings = self.namespace_view_settings
data = get_remote_data(ns, settings, mode='picklable',
Expand Down
5 changes: 3 additions & 2 deletions spyder_kernels/console/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
import site

# Local imports
from spyder_kernels.utils.misc import (
MPL_BACKENDS_FROM_SPYDER, INLINE_FIGURE_FORMATS, is_module_installed)
from spyder_kernels.utils.misc import is_module_installed
from spyder_kernels.utils.mpl import (
MPL_BACKENDS_FROM_SPYDER, INLINE_FIGURE_FORMATS)


PY2 = sys.version[0] == '2'
Expand Down
211 changes: 95 additions & 116 deletions spyder_kernels/utils/iofuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,10 @@
import copy
import glob

# Third party imports
# - If pandas fails to import here (for any reason), Spyder
# will crash at startup (e.g. see Issue 2300)
# - This also prevents Spyder to start IPython kernels
# (see Issue 2456)
try:
import pandas as pd
except:
pd = None #analysis:ignore

# Local imports
from spyder_kernels.py3compat import getcwd, pickle, PY2, to_text_string
from spyder_kernels.utils.lazymodules import (
FakeObject, numpy as np, pandas as pd, PIL, scipy as sp)


class MatlabStruct(dict):
Expand Down Expand Up @@ -110,8 +102,6 @@ def get_matlab_value(val):
From the oct2py project, see
https://pythonhosted.org/oct2py/conversions.html
"""
import numpy as np

# Extract each item of a list.
if isinstance(val, list):
return [get_matlab_value(v) for v in val]
Expand Down Expand Up @@ -156,113 +146,102 @@ def get_matlab_value(val):
return val


try:
import numpy as np
def load_matlab(filename):
if sp.io is FakeObject:
return None, ''

try:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
import scipy.io as spio
except AttributeError:
# Python 2.5: warnings.catch_warnings was introduced in Python 2.6
import scipy.io as spio # analysis:ignore
except:
spio = None

if spio is None:
load_matlab = None
save_matlab = None
else:
def load_matlab(filename):
try:
out = spio.loadmat(filename, struct_as_record=True)
data = dict()
for (key, value) in out.items():
data[key] = get_matlab_value(value)
return data, None
except Exception as error:
return None, str(error)

def save_matlab(data, filename):
try:
spio.savemat(filename, data, oned_as='row')
except Exception as error:
return str(error)
except:
load_matlab = None
save_matlab = None
out = sp.io.loadmat(filename, struct_as_record=True)
data = dict()
for (key, value) in out.items():
data[key] = get_matlab_value(value)
return data, None
except Exception as error:
return None, str(error)


try:
import numpy as np # analysis:ignore
def save_matlab(data, filename):
if sp.io is FakeObject:
return

def load_array(filename):
try:
name = osp.splitext(osp.basename(filename))[0]
data = np.load(filename)
if isinstance(data, np.lib.npyio.NpzFile):
return dict(data), None
elif hasattr(data, 'keys'):
return data, None
else:
return {name: data}, None
except Exception as error:
return None, str(error)

def __save_array(data, basename, index):
"""Save numpy array"""
fname = basename + '_%04d.npy' % index
np.save(fname, data)
return fname
except:
load_array = None


try:
from spyder.pil_patch import Image

if sys.byteorder == 'little':
_ENDIAN = '<'
else:
_ENDIAN = '>'
DTYPES = {
"1": ('|b1', None),
"L": ('|u1', None),
"I": ('%si4' % _ENDIAN, None),
"F": ('%sf4' % _ENDIAN, None),
"I;16": ('|u2', None),
"I;16S": ('%si2' % _ENDIAN, None),
"P": ('|u1', None),
"RGB": ('|u1', 3),
"RGBX": ('|u1', 4),
"RGBA": ('|u1', 4),
"CMYK": ('|u1', 4),
"YCbCr": ('|u1', 4),
}
def __image_to_array(filename):
img = Image.open(filename)
try:
dtype, extra = DTYPES[img.mode]
except KeyError:
raise RuntimeError("%s mode is not supported" % img.mode)
shape = (img.size[1], img.size[0])
if extra is not None:
shape += (extra,)
return np.array(img.getdata(), dtype=np.dtype(dtype)).reshape(shape)
try:
sp.io.savemat(filename, data, oned_as='row')
except Exception as error:
return str(error)

def load_image(filename):
try:
name = osp.splitext(osp.basename(filename))[0]
return {name: __image_to_array(filename)}, None
except Exception as error:
return None, str(error)
except:
load_image = None

def load_array(filename):
if np.load is FakeObject:
return None, ''

try:
name = osp.splitext(osp.basename(filename))[0]
data = np.load(filename)
if isinstance(data, np.lib.npyio.NpzFile):
return dict(data), None
elif hasattr(data, 'keys'):
return data, None
else:
return {name: data}, None
except Exception as error:
return None, str(error)


def __save_array(data, basename, index):
"""Save numpy array"""
fname = basename + '_%04d.npy' % index
np.save(fname, data)
return fname


if sys.byteorder == 'little':
_ENDIAN = '<'
else:
_ENDIAN = '>'

DTYPES = {
"1": ('|b1', None),
"L": ('|u1', None),
"I": ('%si4' % _ENDIAN, None),
"F": ('%sf4' % _ENDIAN, None),
"I;16": ('|u2', None),
"I;16S": ('%si2' % _ENDIAN, None),
"P": ('|u1', None),
"RGB": ('|u1', 3),
"RGBX": ('|u1', 4),
"RGBA": ('|u1', 4),
"CMYK": ('|u1', 4),
"YCbCr": ('|u1', 4),
}


def __image_to_array(filename):
img = PIL.Image.open(filename)
try:
dtype, extra = DTYPES[img.mode]
except KeyError:
raise RuntimeError("%s mode is not supported" % img.mode)
shape = (img.size[1], img.size[0])
if extra is not None:
shape += (extra,)
return np.array(img.getdata(), dtype=np.dtype(dtype)).reshape(shape)


def load_image(filename):
if PIL.Image is FakeObject or np.array is FakeObject:
return None, ''

try:
name = osp.splitext(osp.basename(filename))[0]
return {name: __image_to_array(filename)}, None
except Exception as error:
return None, str(error)


def load_pickle(filename):
"""Load a pickle file as a dictionary"""
try:
if pd:
if pd.read_pickle is not FakeObject:
return pd.read_pickle(filename), None
else:
with open(filename, 'rb') as fid:
Expand Down Expand Up @@ -315,13 +294,13 @@ def save_dictionary(data, filename):
raise RuntimeError('No supported objects to save')

saved_arrays = {}
if load_array is not None:
if np.ndarray is not FakeObject:
# Saving numpy arrays with np.save
arr_fname = osp.splitext(filename)[0]
for name in list(data.keys()):
try:
if isinstance(data[name],
np.ndarray) and data[name].size > 0:
if (isinstance(data[name], np.ndarray) and
data[name].size > 0):
# Save arrays at data root
fname = __save_array(data[name], arr_fname,
len(saved_arrays))
Expand All @@ -335,8 +314,8 @@ def save_dictionary(data, filename):
iterator = iter(list(data[name].items()))
to_remove = []
for index, value in iterator:
if isinstance(value,
np.ndarray) and value.size > 0:
if (isinstance(value, np.ndarray) and
value.size > 0):
fname = __save_array(value, arr_fname,
len(saved_arrays))
saved_arrays[(name, index)] = (
Expand Down Expand Up @@ -407,12 +386,12 @@ def load_dictionary(filename):
with open(pickle_filename, 'rb') as fdesc:
data = pickle.loads(fdesc.read())
saved_arrays = {}
if load_array is not None:
if np.load is not FakeObject:
# Loading numpy arrays saved with np.save
try:
saved_arrays = data.pop('__saved_arrays__')
for (name, index), fname in list(saved_arrays.items()):
arr = np.load( osp.join(tmp_folder, fname) )
arr = np.load(osp.join(tmp_folder, fname))
if index is None:
data[name] = arr
elif isinstance(data[name], dict):
Expand Down
Loading