numpy_pickle.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. """Utilities for fast persistence of big data, with optional compression."""
  2. # Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
  3. # Copyright (c) 2009 Gael Varoquaux
  4. # License: BSD Style, 3 clauses.
  5. import io
  6. import os
  7. import pickle
  8. import warnings
  9. from pathlib import Path
  10. from .backports import make_memmap
  11. from .compressor import (
  12. _COMPRESSORS,
  13. LZ4_NOT_INSTALLED_ERROR,
  14. BinaryZlibFile,
  15. BZ2CompressorWrapper,
  16. GzipCompressorWrapper,
  17. LZ4CompressorWrapper,
  18. LZMACompressorWrapper,
  19. XZCompressorWrapper,
  20. ZlibCompressorWrapper,
  21. lz4,
  22. register_compressor,
  23. )
  24. # For compatibility with old versions of joblib, we need ZNDArrayWrapper
  25. # to be visible in the current namespace.
  26. from .numpy_pickle_compat import (
  27. NDArrayWrapper,
  28. ZNDArrayWrapper, # noqa: F401
  29. load_compatibility,
  30. )
  31. from .numpy_pickle_utils import (
  32. BUFFER_SIZE,
  33. Pickler,
  34. Unpickler,
  35. _ensure_native_byte_order,
  36. _read_bytes,
  37. _reconstruct,
  38. _validate_fileobject_and_memmap,
  39. _write_fileobject,
  40. )
  41. # Register supported compressors
  42. register_compressor("zlib", ZlibCompressorWrapper())
  43. register_compressor("gzip", GzipCompressorWrapper())
  44. register_compressor("bz2", BZ2CompressorWrapper())
  45. register_compressor("lzma", LZMACompressorWrapper())
  46. register_compressor("xz", XZCompressorWrapper())
  47. register_compressor("lz4", LZ4CompressorWrapper())
  48. ###############################################################################
  49. # Utility objects for persistence.
  50. # For convenience, 16 bytes are used to be sure to cover all the possible
  51. # dtypes' alignments. For reference, see:
  52. # https://numpy.org/devdocs/dev/alignment.html
  53. NUMPY_ARRAY_ALIGNMENT_BYTES = 16
  54. class NumpyArrayWrapper(object):
  55. """An object to be persisted instead of numpy arrays.
  56. This object is used to hack into the pickle machinery and read numpy
  57. array data from our custom persistence format.
  58. More precisely, this object is used for:
  59. * carrying the information of the persisted array: subclass, shape, order,
  60. dtype. Those ndarray metadata are used to correctly reconstruct the array
  61. with low level numpy functions.
  62. * determining if memmap is allowed on the array.
  63. * reading the array bytes from a file.
  64. * reading the array using memorymap from a file.
  65. * writing the array bytes to a file.
  66. Attributes
  67. ----------
  68. subclass: numpy.ndarray subclass
  69. Determine the subclass of the wrapped array.
  70. shape: numpy.ndarray shape
  71. Determine the shape of the wrapped array.
  72. order: {'C', 'F'}
  73. Determine the order of wrapped array data. 'C' is for C order, 'F' is
  74. for fortran order.
  75. dtype: numpy.ndarray dtype
  76. Determine the data type of the wrapped array.
  77. allow_mmap: bool
  78. Determine if memory mapping is allowed on the wrapped array.
  79. Default: False.
  80. """
  81. def __init__(
  82. self,
  83. subclass,
  84. shape,
  85. order,
  86. dtype,
  87. allow_mmap=False,
  88. numpy_array_alignment_bytes=NUMPY_ARRAY_ALIGNMENT_BYTES,
  89. ):
  90. """Constructor. Store the useful information for later."""
  91. self.subclass = subclass
  92. self.shape = shape
  93. self.order = order
  94. self.dtype = dtype
  95. self.allow_mmap = allow_mmap
  96. # We make numpy_array_alignment_bytes an instance attribute to allow us
  97. # to change our mind about the default alignment and still load the old
  98. # pickles (with the previous alignment) correctly
  99. self.numpy_array_alignment_bytes = numpy_array_alignment_bytes
  100. def safe_get_numpy_array_alignment_bytes(self):
  101. # NumpyArrayWrapper instances loaded from joblib <= 1.1 pickles don't
  102. # have an numpy_array_alignment_bytes attribute
  103. return getattr(self, "numpy_array_alignment_bytes", None)
  104. def write_array(self, array, pickler):
  105. """Write array bytes to pickler file handle.
  106. This function is an adaptation of the numpy write_array function
  107. available in version 1.10.1 in numpy/lib/format.py.
  108. """
  109. # Set buffer size to 16 MiB to hide the Python loop overhead.
  110. buffersize = max(16 * 1024**2 // array.itemsize, 1)
  111. if array.dtype.hasobject:
  112. # We contain Python objects so we cannot write out the data
  113. # directly. Instead, we will pickle it out with version 5 of the
  114. # pickle protocol.
  115. pickle.dump(array, pickler.file_handle, protocol=5)
  116. else:
  117. numpy_array_alignment_bytes = self.safe_get_numpy_array_alignment_bytes()
  118. if numpy_array_alignment_bytes is not None:
  119. current_pos = pickler.file_handle.tell()
  120. pos_after_padding_byte = current_pos + 1
  121. padding_length = numpy_array_alignment_bytes - (
  122. pos_after_padding_byte % numpy_array_alignment_bytes
  123. )
  124. # A single byte is written that contains the padding length in
  125. # bytes
  126. padding_length_byte = int.to_bytes(
  127. padding_length, length=1, byteorder="little"
  128. )
  129. pickler.file_handle.write(padding_length_byte)
  130. if padding_length != 0:
  131. padding = b"\xff" * padding_length
  132. pickler.file_handle.write(padding)
  133. for chunk in pickler.np.nditer(
  134. array,
  135. flags=["external_loop", "buffered", "zerosize_ok"],
  136. buffersize=buffersize,
  137. order=self.order,
  138. ):
  139. pickler.file_handle.write(chunk.tobytes("C"))
  140. def read_array(self, unpickler, ensure_native_byte_order):
  141. """Read array from unpickler file handle.
  142. This function is an adaptation of the numpy read_array function
  143. available in version 1.10.1 in numpy/lib/format.py.
  144. """
  145. if len(self.shape) == 0:
  146. count = 1
  147. else:
  148. # joblib issue #859: we cast the elements of self.shape to int64 to
  149. # prevent a potential overflow when computing their product.
  150. shape_int64 = [unpickler.np.int64(x) for x in self.shape]
  151. count = unpickler.np.multiply.reduce(shape_int64)
  152. # Now read the actual data.
  153. if self.dtype.hasobject:
  154. # The array contained Python objects. We need to unpickle the data.
  155. array = pickle.load(unpickler.file_handle)
  156. else:
  157. numpy_array_alignment_bytes = self.safe_get_numpy_array_alignment_bytes()
  158. if numpy_array_alignment_bytes is not None:
  159. padding_byte = unpickler.file_handle.read(1)
  160. padding_length = int.from_bytes(padding_byte, byteorder="little")
  161. if padding_length != 0:
  162. unpickler.file_handle.read(padding_length)
  163. # This is not a real file. We have to read it the
  164. # memory-intensive way.
  165. # crc32 module fails on reads greater than 2 ** 32 bytes,
  166. # breaking large reads from gzip streams. Chunk reads to
  167. # BUFFER_SIZE bytes to avoid issue and reduce memory overhead
  168. # of the read. In non-chunked case count < max_read_count, so
  169. # only one read is performed.
  170. max_read_count = BUFFER_SIZE // min(BUFFER_SIZE, self.dtype.itemsize)
  171. array = unpickler.np.empty(count, dtype=self.dtype)
  172. for i in range(0, count, max_read_count):
  173. read_count = min(max_read_count, count - i)
  174. read_size = int(read_count * self.dtype.itemsize)
  175. data = _read_bytes(unpickler.file_handle, read_size, "array data")
  176. array[i : i + read_count] = unpickler.np.frombuffer(
  177. data, dtype=self.dtype, count=read_count
  178. )
  179. del data
  180. if self.order == "F":
  181. array.shape = self.shape[::-1]
  182. array = array.transpose()
  183. else:
  184. array.shape = self.shape
  185. if ensure_native_byte_order:
  186. # Detect byte order mismatch and swap as needed.
  187. array = _ensure_native_byte_order(array)
  188. return array
  189. def read_mmap(self, unpickler):
  190. """Read an array using numpy memmap."""
  191. current_pos = unpickler.file_handle.tell()
  192. offset = current_pos
  193. numpy_array_alignment_bytes = self.safe_get_numpy_array_alignment_bytes()
  194. if numpy_array_alignment_bytes is not None:
  195. padding_byte = unpickler.file_handle.read(1)
  196. padding_length = int.from_bytes(padding_byte, byteorder="little")
  197. # + 1 is for the padding byte
  198. offset += padding_length + 1
  199. if unpickler.mmap_mode == "w+":
  200. unpickler.mmap_mode = "r+"
  201. marray = make_memmap(
  202. unpickler.filename,
  203. dtype=self.dtype,
  204. shape=self.shape,
  205. order=self.order,
  206. mode=unpickler.mmap_mode,
  207. offset=offset,
  208. )
  209. # update the offset so that it corresponds to the end of the read array
  210. unpickler.file_handle.seek(offset + marray.nbytes)
  211. if (
  212. numpy_array_alignment_bytes is None
  213. and current_pos % NUMPY_ARRAY_ALIGNMENT_BYTES != 0
  214. ):
  215. message = (
  216. f"The memmapped array {marray} loaded from the file "
  217. f"{unpickler.file_handle.name} is not byte aligned. "
  218. "This may cause segmentation faults if this memmapped array "
  219. "is used in some libraries like BLAS or PyTorch. "
  220. "To get rid of this warning, regenerate your pickle file "
  221. "with joblib >= 1.2.0. "
  222. "See https://github.com/joblib/joblib/issues/563 "
  223. "for more details"
  224. )
  225. warnings.warn(message)
  226. return marray
  227. def read(self, unpickler, ensure_native_byte_order):
  228. """Read the array corresponding to this wrapper.
  229. Use the unpickler to get all information to correctly read the array.
  230. Parameters
  231. ----------
  232. unpickler: NumpyUnpickler
  233. ensure_native_byte_order: bool
  234. If true, coerce the array to use the native endianness of the
  235. host system.
  236. Returns
  237. -------
  238. array: numpy.ndarray
  239. """
  240. # When requested, only use memmap mode if allowed.
  241. if unpickler.mmap_mode is not None and self.allow_mmap:
  242. assert not ensure_native_byte_order, (
  243. "Memmaps cannot be coerced to a given byte order, "
  244. "this code path is impossible."
  245. )
  246. array = self.read_mmap(unpickler)
  247. else:
  248. array = self.read_array(unpickler, ensure_native_byte_order)
  249. # Manage array subclass case
  250. if hasattr(array, "__array_prepare__") and self.subclass not in (
  251. unpickler.np.ndarray,
  252. unpickler.np.memmap,
  253. ):
  254. # We need to reconstruct another subclass
  255. new_array = _reconstruct(self.subclass, (0,), "b")
  256. return new_array.__array_prepare__(array)
  257. else:
  258. return array
  259. ###############################################################################
  260. # Pickler classes
  261. class NumpyPickler(Pickler):
  262. """A pickler to persist big data efficiently.
  263. The main features of this object are:
  264. * persistence of numpy arrays in a single file.
  265. * optional compression with a special care on avoiding memory copies.
  266. Attributes
  267. ----------
  268. fp: file
  269. File object handle used for serializing the input object.
  270. protocol: int, optional
  271. Pickle protocol used. Default is pickle.DEFAULT_PROTOCOL.
  272. """
  273. dispatch = Pickler.dispatch.copy()
  274. def __init__(self, fp, protocol=None):
  275. self.file_handle = fp
  276. self.buffered = isinstance(self.file_handle, BinaryZlibFile)
  277. # By default we want a pickle protocol that only changes with
  278. # the major python version and not the minor one
  279. if protocol is None:
  280. protocol = pickle.DEFAULT_PROTOCOL
  281. Pickler.__init__(self, self.file_handle, protocol=protocol)
  282. # delayed import of numpy, to avoid tight coupling
  283. try:
  284. import numpy as np
  285. except ImportError:
  286. np = None
  287. self.np = np
  288. def _create_array_wrapper(self, array):
  289. """Create and returns a numpy array wrapper from a numpy array."""
  290. order = (
  291. "F" if (array.flags.f_contiguous and not array.flags.c_contiguous) else "C"
  292. )
  293. allow_mmap = not self.buffered and not array.dtype.hasobject
  294. kwargs = {}
  295. try:
  296. self.file_handle.tell()
  297. except io.UnsupportedOperation:
  298. kwargs = {"numpy_array_alignment_bytes": None}
  299. wrapper = NumpyArrayWrapper(
  300. type(array),
  301. array.shape,
  302. order,
  303. array.dtype,
  304. allow_mmap=allow_mmap,
  305. **kwargs,
  306. )
  307. return wrapper
  308. def save(self, obj):
  309. """Subclass the Pickler `save` method.
  310. This is a total abuse of the Pickler class in order to use the numpy
  311. persistence function `save` instead of the default pickle
  312. implementation. The numpy array is replaced by a custom wrapper in the
  313. pickle persistence stack and the serialized array is written right
  314. after in the file. Warning: the file produced does not follow the
  315. pickle format. As such it can not be read with `pickle.load`.
  316. """
  317. if self.np is not None and type(obj) in (
  318. self.np.ndarray,
  319. self.np.matrix,
  320. self.np.memmap,
  321. ):
  322. if type(obj) is self.np.memmap:
  323. # Pickling doesn't work with memmapped arrays
  324. obj = self.np.asanyarray(obj)
  325. # The array wrapper is pickled instead of the real array.
  326. wrapper = self._create_array_wrapper(obj)
  327. Pickler.save(self, wrapper)
  328. # A framer was introduced with pickle protocol 4 and we want to
  329. # ensure the wrapper object is written before the numpy array
  330. # buffer in the pickle file.
  331. # See https://www.python.org/dev/peps/pep-3154/#framing to get
  332. # more information on the framer behavior.
  333. if self.proto >= 4:
  334. self.framer.commit_frame(force=True)
  335. # And then array bytes are written right after the wrapper.
  336. wrapper.write_array(obj, self)
  337. return
  338. return Pickler.save(self, obj)
  339. class NumpyUnpickler(Unpickler):
  340. """A subclass of the Unpickler to unpickle our numpy pickles.
  341. Attributes
  342. ----------
  343. mmap_mode: str
  344. The memorymap mode to use for reading numpy arrays.
  345. file_handle: file_like
  346. File object to unpickle from.
  347. ensure_native_byte_order: bool
  348. If True, coerce the array to use the native endianness of the
  349. host system.
  350. filename: str
  351. Name of the file to unpickle from. It should correspond to file_handle.
  352. This parameter is required when using mmap_mode.
  353. np: module
  354. Reference to numpy module if numpy is installed else None.
  355. """
  356. dispatch = Unpickler.dispatch.copy()
  357. def __init__(self, filename, file_handle, ensure_native_byte_order, mmap_mode=None):
  358. # The next line is for backward compatibility with pickle generated
  359. # with joblib versions less than 0.10.
  360. self._dirname = os.path.dirname(filename)
  361. self.mmap_mode = mmap_mode
  362. self.file_handle = file_handle
  363. # filename is required for numpy mmap mode.
  364. self.filename = filename
  365. self.compat_mode = False
  366. self.ensure_native_byte_order = ensure_native_byte_order
  367. Unpickler.__init__(self, self.file_handle)
  368. try:
  369. import numpy as np
  370. except ImportError:
  371. np = None
  372. self.np = np
  373. def load_build(self):
  374. """Called to set the state of a newly created object.
  375. We capture it to replace our place-holder objects, NDArrayWrapper or
  376. NumpyArrayWrapper, by the array we are interested in. We
  377. replace them directly in the stack of pickler.
  378. NDArrayWrapper is used for backward compatibility with joblib <= 0.9.
  379. """
  380. Unpickler.load_build(self)
  381. # For backward compatibility, we support NDArrayWrapper objects.
  382. if isinstance(self.stack[-1], (NDArrayWrapper, NumpyArrayWrapper)):
  383. if self.np is None:
  384. raise ImportError(
  385. "Trying to unpickle an ndarray, but numpy didn't import correctly"
  386. )
  387. array_wrapper = self.stack.pop()
  388. # If any NDArrayWrapper is found, we switch to compatibility mode,
  389. # this will be used to raise a DeprecationWarning to the user at
  390. # the end of the unpickling.
  391. if isinstance(array_wrapper, NDArrayWrapper):
  392. self.compat_mode = True
  393. _array_payload = array_wrapper.read(self)
  394. else:
  395. _array_payload = array_wrapper.read(self, self.ensure_native_byte_order)
  396. self.stack.append(_array_payload)
  397. # Be careful to register our new method.
  398. dispatch[pickle.BUILD[0]] = load_build
  399. ###############################################################################
  400. # Utility functions
  401. def dump(value, filename, compress=0, protocol=None):
  402. """Persist an arbitrary Python object into one file.
  403. Read more in the :ref:`User Guide <persistence>`.
  404. Parameters
  405. ----------
  406. value: any Python object
  407. The object to store to disk.
  408. filename: str, pathlib.Path, or file object.
  409. The file object or path of the file in which it is to be stored.
  410. The compression method corresponding to one of the supported filename
  411. extensions ('.z', '.gz', '.bz2', '.xz' or '.lzma') will be used
  412. automatically.
  413. compress: int from 0 to 9 or bool or 2-tuple, optional
  414. Optional compression level for the data. 0 or False is no compression.
  415. Higher value means more compression, but also slower read and
  416. write times. Using a value of 3 is often a good compromise.
  417. See the notes for more details.
  418. If compress is True, the compression level used is 3.
  419. If compress is a 2-tuple, the first element must correspond to a string
  420. between supported compressors (e.g 'zlib', 'gzip', 'bz2', 'lzma'
  421. 'xz'), the second element must be an integer from 0 to 9, corresponding
  422. to the compression level.
  423. protocol: int, optional
  424. Pickle protocol, see pickle.dump documentation for more details.
  425. Returns
  426. -------
  427. filenames: list of strings
  428. The list of file names in which the data is stored. If
  429. compress is false, each array is stored in a different file.
  430. See Also
  431. --------
  432. joblib.load : corresponding loader
  433. Notes
  434. -----
  435. Memmapping on load cannot be used for compressed files. Thus
  436. using compression can significantly slow down loading. In
  437. addition, compressed files take up extra memory during
  438. dump and load.
  439. """
  440. if Path is not None and isinstance(filename, Path):
  441. filename = str(filename)
  442. is_filename = isinstance(filename, str)
  443. is_fileobj = hasattr(filename, "write")
  444. compress_method = "zlib" # zlib is the default compression method.
  445. if compress is True:
  446. # By default, if compress is enabled, we want the default compress
  447. # level of the compressor.
  448. compress_level = None
  449. elif isinstance(compress, tuple):
  450. # a 2-tuple was set in compress
  451. if len(compress) != 2:
  452. raise ValueError(
  453. "Compress argument tuple should contain exactly 2 elements: "
  454. "(compress method, compress level), you passed {}".format(compress)
  455. )
  456. compress_method, compress_level = compress
  457. elif isinstance(compress, str):
  458. compress_method = compress
  459. compress_level = None # Use default compress level
  460. compress = (compress_method, compress_level)
  461. else:
  462. compress_level = compress
  463. if compress_method == "lz4" and lz4 is None:
  464. raise ValueError(LZ4_NOT_INSTALLED_ERROR)
  465. if (
  466. compress_level is not None
  467. and compress_level is not False
  468. and compress_level not in range(10)
  469. ):
  470. # Raising an error if a non valid compress level is given.
  471. raise ValueError(
  472. 'Non valid compress level given: "{}". Possible values are {}.'.format(
  473. compress_level, list(range(10))
  474. )
  475. )
  476. if compress_method not in _COMPRESSORS:
  477. # Raising an error if an unsupported compression method is given.
  478. raise ValueError(
  479. 'Non valid compression method given: "{}". Possible values are {}.'.format(
  480. compress_method, _COMPRESSORS
  481. )
  482. )
  483. if not is_filename and not is_fileobj:
  484. # People keep inverting arguments, and the resulting error is
  485. # incomprehensible
  486. raise ValueError(
  487. "Second argument should be a filename or a file-like object, "
  488. "%s (type %s) was given." % (filename, type(filename))
  489. )
  490. if is_filename and not isinstance(compress, tuple):
  491. # In case no explicit compression was requested using both compression
  492. # method and level in a tuple and the filename has an explicit
  493. # extension, we select the corresponding compressor.
  494. # unset the variable to be sure no compression level is set afterwards.
  495. compress_method = None
  496. for name, compressor in _COMPRESSORS.items():
  497. if filename.endswith(compressor.extension):
  498. compress_method = name
  499. if compress_method in _COMPRESSORS and compress_level == 0:
  500. # we choose the default compress_level in case it was not given
  501. # as an argument (using compress).
  502. compress_level = None
  503. if compress_level != 0:
  504. with _write_fileobject(
  505. filename, compress=(compress_method, compress_level)
  506. ) as f:
  507. NumpyPickler(f, protocol=protocol).dump(value)
  508. elif is_filename:
  509. with open(filename, "wb") as f:
  510. NumpyPickler(f, protocol=protocol).dump(value)
  511. else:
  512. NumpyPickler(filename, protocol=protocol).dump(value)
  513. # If the target container is a file object, nothing is returned.
  514. if is_fileobj:
  515. return
  516. # For compatibility, the list of created filenames (e.g with one element
  517. # after 0.10.0) is returned by default.
  518. return [filename]
  519. def _unpickle(fobj, ensure_native_byte_order, filename="", mmap_mode=None):
  520. """Internal unpickling function."""
  521. # We are careful to open the file handle early and keep it open to
  522. # avoid race-conditions on renames.
  523. # That said, if data is stored in companion files, which can be
  524. # the case with the old persistence format, moving the directory
  525. # will create a race when joblib tries to access the companion
  526. # files.
  527. unpickler = NumpyUnpickler(
  528. filename, fobj, ensure_native_byte_order, mmap_mode=mmap_mode
  529. )
  530. obj = None
  531. try:
  532. obj = unpickler.load()
  533. if unpickler.compat_mode:
  534. warnings.warn(
  535. "The file '%s' has been generated with a "
  536. "joblib version less than 0.10. "
  537. "Please regenerate this pickle file." % filename,
  538. DeprecationWarning,
  539. stacklevel=3,
  540. )
  541. except UnicodeDecodeError as exc:
  542. # More user-friendly error message
  543. new_exc = ValueError(
  544. "You may be trying to read with "
  545. "python 3 a joblib pickle generated with python 2. "
  546. "This feature is not supported by joblib."
  547. )
  548. new_exc.__cause__ = exc
  549. raise new_exc
  550. return obj
  551. def load_temporary_memmap(filename, mmap_mode, unlink_on_gc_collect):
  552. from ._memmapping_reducer import JOBLIB_MMAPS, add_maybe_unlink_finalizer
  553. with open(filename, "rb") as f:
  554. with _validate_fileobject_and_memmap(f, filename, mmap_mode) as (
  555. fobj,
  556. validated_mmap_mode,
  557. ):
  558. # Memmap are used for interprocess communication, which should
  559. # keep the objects untouched. We pass `ensure_native_byte_order=False`
  560. # to remain consistent with the loading behavior of non-memmaped arrays
  561. # in workers, where the byte order is preserved.
  562. # Note that we do not implement endianness change for memmaps, as this
  563. # would result in inconsistent behavior.
  564. obj = _unpickle(
  565. fobj,
  566. ensure_native_byte_order=False,
  567. filename=filename,
  568. mmap_mode=validated_mmap_mode,
  569. )
  570. JOBLIB_MMAPS.add(obj.filename)
  571. if unlink_on_gc_collect:
  572. add_maybe_unlink_finalizer(obj)
  573. return obj
  574. def load(filename, mmap_mode=None, ensure_native_byte_order="auto"):
  575. """Reconstruct a Python object from a file persisted with joblib.dump.
  576. Read more in the :ref:`User Guide <persistence>`.
  577. WARNING: joblib.load relies on the pickle module and can therefore
  578. execute arbitrary Python code. It should therefore never be used
  579. to load files from untrusted sources.
  580. Parameters
  581. ----------
  582. filename: str, pathlib.Path, or file object.
  583. The file object or path of the file from which to load the object
  584. mmap_mode: {None, 'r+', 'r', 'w+', 'c'}, optional
  585. If not None, the arrays are memory-mapped from the disk. This
  586. mode has no effect for compressed files. Note that in this
  587. case the reconstructed object might no longer match exactly
  588. the originally pickled object.
  589. ensure_native_byte_order: bool, or 'auto', default=='auto'
  590. If True, ensures that the byte order of the loaded arrays matches the
  591. native byte ordering (or _endianness_) of the host system. This is not
  592. compatible with memory-mapped arrays and using non-null `mmap_mode`
  593. parameter at the same time will raise an error. The default 'auto'
  594. parameter is equivalent to True if `mmap_mode` is None, else False.
  595. Returns
  596. -------
  597. result: any Python object
  598. The object stored in the file.
  599. See Also
  600. --------
  601. joblib.dump : function to save an object
  602. Notes
  603. -----
  604. This function can load numpy array files saved separately during the
  605. dump. If the mmap_mode argument is given, it is passed to np.load and
  606. arrays are loaded as memmaps. As a consequence, the reconstructed
  607. object might not match the original pickled object. Note that if the
  608. file was saved with compression, the arrays cannot be memmapped.
  609. """
  610. if ensure_native_byte_order == "auto":
  611. ensure_native_byte_order = mmap_mode is None
  612. if ensure_native_byte_order and mmap_mode is not None:
  613. raise ValueError(
  614. "Native byte ordering can only be enforced if 'mmap_mode' parameter "
  615. f"is set to None, but got 'mmap_mode={mmap_mode}' instead."
  616. )
  617. if Path is not None and isinstance(filename, Path):
  618. filename = str(filename)
  619. if hasattr(filename, "read"):
  620. fobj = filename
  621. filename = getattr(fobj, "name", "")
  622. with _validate_fileobject_and_memmap(fobj, filename, mmap_mode) as (fobj, _):
  623. obj = _unpickle(fobj, ensure_native_byte_order=ensure_native_byte_order)
  624. else:
  625. with open(filename, "rb") as f:
  626. with _validate_fileobject_and_memmap(f, filename, mmap_mode) as (
  627. fobj,
  628. validated_mmap_mode,
  629. ):
  630. if isinstance(fobj, str):
  631. # if the returned file object is a string, this means we
  632. # try to load a pickle file generated with an version of
  633. # Joblib so we load it with joblib compatibility function.
  634. return load_compatibility(fobj)
  635. # A memory-mapped array has to be mapped with the endianness
  636. # it has been written with. Other arrays are coerced to the
  637. # native endianness of the host system.
  638. obj = _unpickle(
  639. fobj,
  640. ensure_native_byte_order=ensure_native_byte_order,
  641. filename=filename,
  642. mmap_mode=validated_mmap_mode,
  643. )
  644. return obj