| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487 |
- """Pickler class to extend the standard pickle.Pickler functionality
- The main objective is to make it natural to perform distributed computing on
- clusters (such as PySpark, Dask, Ray...) with interactively defined code
- (functions, classes, ...) written in notebooks or console.
- In particular this pickler adds the following features:
- - serialize interactively-defined or locally-defined functions, classes,
- enums, typevars, lambdas and nested functions to compiled byte code;
- - deal with some other non-serializable objects in an ad-hoc manner where
- applicable.
- This pickler is therefore meant to be used for the communication between short
- lived Python processes running the same version of Python and libraries. In
- particular, it is not meant to be used for long term storage of Python objects.
- It does not include an unpickler, as standard Python unpickling suffices.
- This module was extracted from the `cloud` package, developed by `PiCloud, Inc.
- <https://web.archive.org/web/20140626004012/http://www.picloud.com/>`_.
- Copyright (c) 2012-now, CloudPickle developers and contributors.
- Copyright (c) 2012, Regents of the University of California.
- Copyright (c) 2009 `PiCloud, Inc. <https://web.archive.org/web/20140626004012/http://www.picloud.com/>`_.
- All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions
- are met:
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
- * Neither the name of the University of California, Berkeley nor the
- names of its contributors may be used to endorse or promote
- products derived from this software without specific prior written
- permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- """
- import _collections_abc
- from collections import ChainMap, OrderedDict
- import abc
- import builtins
- import copyreg
- import dataclasses
- import dis
- from enum import Enum
- import io
- import itertools
- import logging
- import opcode
- import pickle
- from pickle import _getattribute
- import platform
- import struct
- import sys
- import threading
- import types
- import typing
- import uuid
- import warnings
- import weakref
- # The following import is required to be imported in the cloudpickle
- # namespace to be able to load pickle files generated with older versions of
- # cloudpickle. See: tests/test_backward_compat.py
- from types import CellType # noqa: F401
- # cloudpickle is meant for inter process communication: we expect all
- # communicating processes to run the same Python version hence we favor
- # communication speed over compatibility:
- DEFAULT_PROTOCOL = pickle.HIGHEST_PROTOCOL
- # Names of modules whose resources should be treated as dynamic.
- _PICKLE_BY_VALUE_MODULES = set()
- # Track the provenance of reconstructed dynamic classes to make it possible to
- # reconstruct instances from the matching singleton class definition when
- # appropriate and preserve the usual "isinstance" semantics of Python objects.
- _DYNAMIC_CLASS_TRACKER_BY_CLASS = weakref.WeakKeyDictionary()
- _DYNAMIC_CLASS_TRACKER_BY_ID = weakref.WeakValueDictionary()
- _DYNAMIC_CLASS_TRACKER_LOCK = threading.Lock()
- PYPY = platform.python_implementation() == "PyPy"
- builtin_code_type = None
- if PYPY:
- # builtin-code objects only exist in pypy
- builtin_code_type = type(float.__new__.__code__)
- _extract_code_globals_cache = weakref.WeakKeyDictionary()
- def _get_or_create_tracker_id(class_def):
- with _DYNAMIC_CLASS_TRACKER_LOCK:
- class_tracker_id = _DYNAMIC_CLASS_TRACKER_BY_CLASS.get(class_def)
- if class_tracker_id is None:
- class_tracker_id = uuid.uuid4().hex
- _DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
- _DYNAMIC_CLASS_TRACKER_BY_ID[class_tracker_id] = class_def
- return class_tracker_id
- def _lookup_class_or_track(class_tracker_id, class_def):
- if class_tracker_id is not None:
- with _DYNAMIC_CLASS_TRACKER_LOCK:
- class_def = _DYNAMIC_CLASS_TRACKER_BY_ID.setdefault(
- class_tracker_id, class_def
- )
- _DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
- return class_def
- def register_pickle_by_value(module):
- """Register a module to make it functions and classes picklable by value.
- By default, functions and classes that are attributes of an importable
- module are to be pickled by reference, that is relying on re-importing
- the attribute from the module at load time.
- If `register_pickle_by_value(module)` is called, all its functions and
- classes are subsequently to be pickled by value, meaning that they can
- be loaded in Python processes where the module is not importable.
- This is especially useful when developing a module in a distributed
- execution environment: restarting the client Python process with the new
- source code is enough: there is no need to re-install the new version
- of the module on all the worker nodes nor to restart the workers.
- Note: this feature is considered experimental. See the cloudpickle
- README.md file for more details and limitations.
- """
- if not isinstance(module, types.ModuleType):
- raise ValueError(f"Input should be a module object, got {str(module)} instead")
- # In the future, cloudpickle may need a way to access any module registered
- # for pickling by value in order to introspect relative imports inside
- # functions pickled by value. (see
- # https://github.com/cloudpipe/cloudpickle/pull/417#issuecomment-873684633).
- # This access can be ensured by checking that module is present in
- # sys.modules at registering time and assuming that it will still be in
- # there when accessed during pickling. Another alternative would be to
- # store a weakref to the module. Even though cloudpickle does not implement
- # this introspection yet, in order to avoid a possible breaking change
- # later, we still enforce the presence of module inside sys.modules.
- if module.__name__ not in sys.modules:
- raise ValueError(
- f"{module} was not imported correctly, have you used an "
- "`import` statement to access it?"
- )
- _PICKLE_BY_VALUE_MODULES.add(module.__name__)
- def unregister_pickle_by_value(module):
- """Unregister that the input module should be pickled by value."""
- if not isinstance(module, types.ModuleType):
- raise ValueError(f"Input should be a module object, got {str(module)} instead")
- if module.__name__ not in _PICKLE_BY_VALUE_MODULES:
- raise ValueError(f"{module} is not registered for pickle by value")
- else:
- _PICKLE_BY_VALUE_MODULES.remove(module.__name__)
- def list_registry_pickle_by_value():
- return _PICKLE_BY_VALUE_MODULES.copy()
- def _is_registered_pickle_by_value(module):
- module_name = module.__name__
- if module_name in _PICKLE_BY_VALUE_MODULES:
- return True
- while True:
- parent_name = module_name.rsplit(".", 1)[0]
- if parent_name == module_name:
- break
- if parent_name in _PICKLE_BY_VALUE_MODULES:
- return True
- module_name = parent_name
- return False
- def _whichmodule(obj, name):
- """Find the module an object belongs to.
- This function differs from ``pickle.whichmodule`` in two ways:
- - it does not mangle the cases where obj's module is __main__ and obj was
- not found in any module.
- - Errors arising during module introspection are ignored, as those errors
- are considered unwanted side effects.
- """
- module_name = getattr(obj, "__module__", None)
- if module_name is not None:
- return module_name
- # Protect the iteration by using a copy of sys.modules against dynamic
- # modules that trigger imports of other modules upon calls to getattr or
- # other threads importing at the same time.
- for module_name, module in sys.modules.copy().items():
- # Some modules such as coverage can inject non-module objects inside
- # sys.modules
- if (
- module_name == "__main__"
- or module is None
- or not isinstance(module, types.ModuleType)
- ):
- continue
- try:
- if _getattribute(module, name)[0] is obj:
- return module_name
- except Exception:
- pass
- return None
- def _should_pickle_by_reference(obj, name=None):
- """Test whether an function or a class should be pickled by reference
- Pickling by reference means by that the object (typically a function or a
- class) is an attribute of a module that is assumed to be importable in the
- target Python environment. Loading will therefore rely on importing the
- module and then calling `getattr` on it to access the function or class.
- Pickling by reference is the only option to pickle functions and classes
- in the standard library. In cloudpickle the alternative option is to
- pickle by value (for instance for interactively or locally defined
- functions and classes or for attributes of modules that have been
- explicitly registered to be pickled by value.
- """
- if isinstance(obj, types.FunctionType) or issubclass(type(obj), type):
- module_and_name = _lookup_module_and_qualname(obj, name=name)
- if module_and_name is None:
- return False
- module, name = module_and_name
- return not _is_registered_pickle_by_value(module)
- elif isinstance(obj, types.ModuleType):
- # We assume that sys.modules is primarily used as a cache mechanism for
- # the Python import machinery. Checking if a module has been added in
- # is sys.modules therefore a cheap and simple heuristic to tell us
- # whether we can assume that a given module could be imported by name
- # in another Python process.
- if _is_registered_pickle_by_value(obj):
- return False
- return obj.__name__ in sys.modules
- else:
- raise TypeError(
- "cannot check importability of {} instances".format(type(obj).__name__)
- )
- def _lookup_module_and_qualname(obj, name=None):
- if name is None:
- name = getattr(obj, "__qualname__", None)
- if name is None: # pragma: no cover
- # This used to be needed for Python 2.7 support but is probably not
- # needed anymore. However we keep the __name__ introspection in case
- # users of cloudpickle rely on this old behavior for unknown reasons.
- name = getattr(obj, "__name__", None)
- module_name = _whichmodule(obj, name)
- if module_name is None:
- # In this case, obj.__module__ is None AND obj was not found in any
- # imported module. obj is thus treated as dynamic.
- return None
- if module_name == "__main__":
- return None
- # Note: if module_name is in sys.modules, the corresponding module is
- # assumed importable at unpickling time. See #357
- module = sys.modules.get(module_name, None)
- if module is None:
- # The main reason why obj's module would not be imported is that this
- # module has been dynamically created, using for example
- # types.ModuleType. The other possibility is that module was removed
- # from sys.modules after obj was created/imported. But this case is not
- # supported, as the standard pickle does not support it either.
- return None
- try:
- obj2, parent = _getattribute(module, name)
- except AttributeError:
- # obj was not found inside the module it points to
- return None
- if obj2 is not obj:
- return None
- return module, name
- def _extract_code_globals(co):
- """Find all globals names read or written to by codeblock co."""
- out_names = _extract_code_globals_cache.get(co)
- if out_names is None:
- # We use a dict with None values instead of a set to get a
- # deterministic order and avoid introducing non-deterministic pickle
- # bytes as a results.
- out_names = {name: None for name in _walk_global_ops(co)}
- # Declaring a function inside another one using the "def ..." syntax
- # generates a constant code object corresponding to the one of the
- # nested function's As the nested function may itself need global
- # variables, we need to introspect its code, extract its globals, (look
- # for code object in it's co_consts attribute..) and add the result to
- # code_globals
- if co.co_consts:
- for const in co.co_consts:
- if isinstance(const, types.CodeType):
- out_names.update(_extract_code_globals(const))
- _extract_code_globals_cache[co] = out_names
- return out_names
- def _find_imported_submodules(code, top_level_dependencies):
- """Find currently imported submodules used by a function.
- Submodules used by a function need to be detected and referenced for the
- function to work correctly at depickling time. Because submodules can be
- referenced as attribute of their parent package (``package.submodule``), we
- need a special introspection technique that does not rely on GLOBAL-related
- opcodes to find references of them in a code object.
- Example:
- ```
- import concurrent.futures
- import cloudpickle
- def func():
- x = concurrent.futures.ThreadPoolExecutor
- if __name__ == '__main__':
- cloudpickle.dumps(func)
- ```
- The globals extracted by cloudpickle in the function's state include the
- concurrent package, but not its submodule (here, concurrent.futures), which
- is the module used by func. Find_imported_submodules will detect the usage
- of concurrent.futures. Saving this module alongside with func will ensure
- that calling func once depickled does not fail due to concurrent.futures
- not being imported
- """
- subimports = []
- # check if any known dependency is an imported package
- for x in top_level_dependencies:
- if (
- isinstance(x, types.ModuleType)
- and hasattr(x, "__package__")
- and x.__package__
- ):
- # check if the package has any currently loaded sub-imports
- prefix = x.__name__ + "."
- # A concurrent thread could mutate sys.modules,
- # make sure we iterate over a copy to avoid exceptions
- for name in list(sys.modules):
- # Older versions of pytest will add a "None" module to
- # sys.modules.
- if name is not None and name.startswith(prefix):
- # check whether the function can address the sub-module
- tokens = set(name[len(prefix) :].split("."))
- if not tokens - set(code.co_names):
- subimports.append(sys.modules[name])
- return subimports
- # relevant opcodes
- STORE_GLOBAL = opcode.opmap["STORE_GLOBAL"]
- DELETE_GLOBAL = opcode.opmap["DELETE_GLOBAL"]
- LOAD_GLOBAL = opcode.opmap["LOAD_GLOBAL"]
- GLOBAL_OPS = (STORE_GLOBAL, DELETE_GLOBAL, LOAD_GLOBAL)
- HAVE_ARGUMENT = dis.HAVE_ARGUMENT
- EXTENDED_ARG = dis.EXTENDED_ARG
- _BUILTIN_TYPE_NAMES = {}
- for k, v in types.__dict__.items():
- if type(v) is type:
- _BUILTIN_TYPE_NAMES[v] = k
- def _builtin_type(name):
- if name == "ClassType": # pragma: no cover
- # Backward compat to load pickle files generated with cloudpickle
- # < 1.3 even if loading pickle files from older versions is not
- # officially supported.
- return type
- return getattr(types, name)
- def _walk_global_ops(code):
- """Yield referenced name for global-referencing instructions in code."""
- for instr in dis.get_instructions(code):
- op = instr.opcode
- if op in GLOBAL_OPS:
- yield instr.argval
- def _extract_class_dict(cls):
- """Retrieve a copy of the dict of a class without the inherited method."""
- clsdict = dict(cls.__dict__) # copy dict proxy to a dict
- if len(cls.__bases__) == 1:
- inherited_dict = cls.__bases__[0].__dict__
- else:
- inherited_dict = {}
- for base in reversed(cls.__bases__):
- inherited_dict.update(base.__dict__)
- to_remove = []
- for name, value in clsdict.items():
- try:
- base_value = inherited_dict[name]
- if value is base_value:
- to_remove.append(name)
- except KeyError:
- pass
- for name in to_remove:
- clsdict.pop(name)
- return clsdict
- def is_tornado_coroutine(func):
- """Return whether `func` is a Tornado coroutine function.
- Running coroutines are not supported.
- """
- warnings.warn(
- "is_tornado_coroutine is deprecated in cloudpickle 3.0 and will be "
- "removed in cloudpickle 4.0. Use tornado.gen.is_coroutine_function "
- "directly instead.",
- category=DeprecationWarning,
- )
- if "tornado.gen" not in sys.modules:
- return False
- gen = sys.modules["tornado.gen"]
- if not hasattr(gen, "is_coroutine_function"):
- # Tornado version is too old
- return False
- return gen.is_coroutine_function(func)
- def subimport(name):
- # We cannot do simply: `return __import__(name)`: Indeed, if ``name`` is
- # the name of a submodule, __import__ will return the top-level root module
- # of this submodule. For instance, __import__('os.path') returns the `os`
- # module.
- __import__(name)
- return sys.modules[name]
- def dynamic_subimport(name, vars):
- mod = types.ModuleType(name)
- mod.__dict__.update(vars)
- mod.__dict__["__builtins__"] = builtins.__dict__
- return mod
- def _get_cell_contents(cell):
- try:
- return cell.cell_contents
- except ValueError:
- # Handle empty cells explicitly with a sentinel value.
- return _empty_cell_value
- def instance(cls):
- """Create a new instance of a class.
- Parameters
- ----------
- cls : type
- The class to create an instance of.
- Returns
- -------
- instance : cls
- A new instance of ``cls``.
- """
- return cls()
- @instance
- class _empty_cell_value:
- """Sentinel for empty closures."""
- @classmethod
- def __reduce__(cls):
- return cls.__name__
- def _make_function(code, globals, name, argdefs, closure):
- # Setting __builtins__ in globals is needed for nogil CPython.
- globals["__builtins__"] = __builtins__
- return types.FunctionType(code, globals, name, argdefs, closure)
- def _make_empty_cell():
- if False:
- # trick the compiler into creating an empty cell in our lambda
- cell = None
- raise AssertionError("this route should not be executed")
- return (lambda: cell).__closure__[0]
- def _make_cell(value=_empty_cell_value):
- cell = _make_empty_cell()
- if value is not _empty_cell_value:
- cell.cell_contents = value
- return cell
- def _make_skeleton_class(
- type_constructor, name, bases, type_kwargs, class_tracker_id, extra
- ):
- """Build dynamic class with an empty __dict__ to be filled once memoized
- If class_tracker_id is not None, try to lookup an existing class definition
- matching that id. If none is found, track a newly reconstructed class
- definition under that id so that other instances stemming from the same
- class id will also reuse this class definition.
- The "extra" variable is meant to be a dict (or None) that can be used for
- forward compatibility shall the need arise.
- """
- skeleton_class = types.new_class(
- name, bases, {"metaclass": type_constructor}, lambda ns: ns.update(type_kwargs)
- )
- return _lookup_class_or_track(class_tracker_id, skeleton_class)
- def _make_skeleton_enum(
- bases, name, qualname, members, module, class_tracker_id, extra
- ):
- """Build dynamic enum with an empty __dict__ to be filled once memoized
- The creation of the enum class is inspired by the code of
- EnumMeta._create_.
- If class_tracker_id is not None, try to lookup an existing enum definition
- matching that id. If none is found, track a newly reconstructed enum
- definition under that id so that other instances stemming from the same
- class id will also reuse this enum definition.
- The "extra" variable is meant to be a dict (or None) that can be used for
- forward compatibility shall the need arise.
- """
- # enums always inherit from their base Enum class at the last position in
- # the list of base classes:
- enum_base = bases[-1]
- metacls = enum_base.__class__
- classdict = metacls.__prepare__(name, bases)
- for member_name, member_value in members.items():
- classdict[member_name] = member_value
- enum_class = metacls.__new__(metacls, name, bases, classdict)
- enum_class.__module__ = module
- enum_class.__qualname__ = qualname
- return _lookup_class_or_track(class_tracker_id, enum_class)
- def _make_typevar(name, bound, constraints, covariant, contravariant, class_tracker_id):
- tv = typing.TypeVar(
- name,
- *constraints,
- bound=bound,
- covariant=covariant,
- contravariant=contravariant,
- )
- return _lookup_class_or_track(class_tracker_id, tv)
- def _decompose_typevar(obj):
- return (
- obj.__name__,
- obj.__bound__,
- obj.__constraints__,
- obj.__covariant__,
- obj.__contravariant__,
- _get_or_create_tracker_id(obj),
- )
- def _typevar_reduce(obj):
- # TypeVar instances require the module information hence why we
- # are not using the _should_pickle_by_reference directly
- module_and_name = _lookup_module_and_qualname(obj, name=obj.__name__)
- if module_and_name is None:
- return (_make_typevar, _decompose_typevar(obj))
- elif _is_registered_pickle_by_value(module_and_name[0]):
- return (_make_typevar, _decompose_typevar(obj))
- return (getattr, module_and_name)
- def _get_bases(typ):
- if "__orig_bases__" in getattr(typ, "__dict__", {}):
- # For generic types (see PEP 560)
- # Note that simply checking `hasattr(typ, '__orig_bases__')` is not
- # correct. Subclasses of a fully-parameterized generic class does not
- # have `__orig_bases__` defined, but `hasattr(typ, '__orig_bases__')`
- # will return True because it's defined in the base class.
- bases_attr = "__orig_bases__"
- else:
- # For regular class objects
- bases_attr = "__bases__"
- return getattr(typ, bases_attr)
- def _make_dict_keys(obj, is_ordered=False):
- if is_ordered:
- return OrderedDict.fromkeys(obj).keys()
- else:
- return dict.fromkeys(obj).keys()
- def _make_dict_values(obj, is_ordered=False):
- if is_ordered:
- return OrderedDict((i, _) for i, _ in enumerate(obj)).values()
- else:
- return {i: _ for i, _ in enumerate(obj)}.values()
- def _make_dict_items(obj, is_ordered=False):
- if is_ordered:
- return OrderedDict(obj).items()
- else:
- return obj.items()
- # COLLECTION OF OBJECTS __getnewargs__-LIKE METHODS
- # -------------------------------------------------
- def _class_getnewargs(obj):
- type_kwargs = {}
- if "__module__" in obj.__dict__:
- type_kwargs["__module__"] = obj.__module__
- __dict__ = obj.__dict__.get("__dict__", None)
- if isinstance(__dict__, property):
- type_kwargs["__dict__"] = __dict__
- return (
- type(obj),
- obj.__name__,
- _get_bases(obj),
- type_kwargs,
- _get_or_create_tracker_id(obj),
- None,
- )
- def _enum_getnewargs(obj):
- members = {e.name: e.value for e in obj}
- return (
- obj.__bases__,
- obj.__name__,
- obj.__qualname__,
- members,
- obj.__module__,
- _get_or_create_tracker_id(obj),
- None,
- )
- # COLLECTION OF OBJECTS RECONSTRUCTORS
- # ------------------------------------
- def _file_reconstructor(retval):
- return retval
- # COLLECTION OF OBJECTS STATE GETTERS
- # -----------------------------------
- def _function_getstate(func):
- # - Put func's dynamic attributes (stored in func.__dict__) in state. These
- # attributes will be restored at unpickling time using
- # f.__dict__.update(state)
- # - Put func's members into slotstate. Such attributes will be restored at
- # unpickling time by iterating over slotstate and calling setattr(func,
- # slotname, slotvalue)
- slotstate = {
- "__name__": func.__name__,
- "__qualname__": func.__qualname__,
- "__annotations__": func.__annotations__,
- "__kwdefaults__": func.__kwdefaults__,
- "__defaults__": func.__defaults__,
- "__module__": func.__module__,
- "__doc__": func.__doc__,
- "__closure__": func.__closure__,
- }
- f_globals_ref = _extract_code_globals(func.__code__)
- f_globals = {k: func.__globals__[k] for k in f_globals_ref if k in func.__globals__}
- if func.__closure__ is not None:
- closure_values = list(map(_get_cell_contents, func.__closure__))
- else:
- closure_values = ()
- # Extract currently-imported submodules used by func. Storing these modules
- # in a smoke _cloudpickle_subimports attribute of the object's state will
- # trigger the side effect of importing these modules at unpickling time
- # (which is necessary for func to work correctly once depickled)
- slotstate["_cloudpickle_submodules"] = _find_imported_submodules(
- func.__code__, itertools.chain(f_globals.values(), closure_values)
- )
- slotstate["__globals__"] = f_globals
- state = func.__dict__
- return state, slotstate
- def _class_getstate(obj):
- clsdict = _extract_class_dict(obj)
- clsdict.pop("__weakref__", None)
- if issubclass(type(obj), abc.ABCMeta):
- # If obj is an instance of an ABCMeta subclass, don't pickle the
- # cache/negative caches populated during isinstance/issubclass
- # checks, but pickle the list of registered subclasses of obj.
- clsdict.pop("_abc_cache", None)
- clsdict.pop("_abc_negative_cache", None)
- clsdict.pop("_abc_negative_cache_version", None)
- registry = clsdict.pop("_abc_registry", None)
- if registry is None:
- # The abc caches and registered subclasses of a
- # class are bundled into the single _abc_impl attribute
- clsdict.pop("_abc_impl", None)
- (registry, _, _, _) = abc._get_dump(obj)
- clsdict["_abc_impl"] = [subclass_weakref() for subclass_weakref in registry]
- else:
- # In the above if clause, registry is a set of weakrefs -- in
- # this case, registry is a WeakSet
- clsdict["_abc_impl"] = [type_ for type_ in registry]
- if "__slots__" in clsdict:
- # pickle string length optimization: member descriptors of obj are
- # created automatically from obj's __slots__ attribute, no need to
- # save them in obj's state
- if isinstance(obj.__slots__, str):
- clsdict.pop(obj.__slots__)
- else:
- for k in obj.__slots__:
- clsdict.pop(k, None)
- clsdict.pop("__dict__", None) # unpicklable property object
- return (clsdict, {})
- def _enum_getstate(obj):
- clsdict, slotstate = _class_getstate(obj)
- members = {e.name: e.value for e in obj}
- # Cleanup the clsdict that will be passed to _make_skeleton_enum:
- # Those attributes are already handled by the metaclass.
- for attrname in [
- "_generate_next_value_",
- "_member_names_",
- "_member_map_",
- "_member_type_",
- "_value2member_map_",
- ]:
- clsdict.pop(attrname, None)
- for member in members:
- clsdict.pop(member)
- # Special handling of Enum subclasses
- return clsdict, slotstate
- # COLLECTIONS OF OBJECTS REDUCERS
- # -------------------------------
- # A reducer is a function taking a single argument (obj), and that returns a
- # tuple with all the necessary data to re-construct obj. Apart from a few
- # exceptions (list, dict, bytes, int, etc.), a reducer is necessary to
- # correctly pickle an object.
- # While many built-in objects (Exceptions objects, instances of the "object"
- # class, etc), are shipped with their own built-in reducer (invoked using
- # obj.__reduce__), some do not. The following methods were created to "fill
- # these holes".
- def _code_reduce(obj):
- """code object reducer."""
- # If you are not sure about the order of arguments, take a look at help
- # of the specific type from types, for example:
- # >>> from types import CodeType
- # >>> help(CodeType)
- if hasattr(obj, "co_exceptiontable"):
- # Python 3.11 and later: there are some new attributes
- # related to the enhanced exceptions.
- args = (
- obj.co_argcount,
- obj.co_posonlyargcount,
- obj.co_kwonlyargcount,
- obj.co_nlocals,
- obj.co_stacksize,
- obj.co_flags,
- obj.co_code,
- obj.co_consts,
- obj.co_names,
- obj.co_varnames,
- obj.co_filename,
- obj.co_name,
- obj.co_qualname,
- obj.co_firstlineno,
- obj.co_linetable,
- obj.co_exceptiontable,
- obj.co_freevars,
- obj.co_cellvars,
- )
- elif hasattr(obj, "co_linetable"):
- # Python 3.10 and later: obj.co_lnotab is deprecated and constructor
- # expects obj.co_linetable instead.
- args = (
- obj.co_argcount,
- obj.co_posonlyargcount,
- obj.co_kwonlyargcount,
- obj.co_nlocals,
- obj.co_stacksize,
- obj.co_flags,
- obj.co_code,
- obj.co_consts,
- obj.co_names,
- obj.co_varnames,
- obj.co_filename,
- obj.co_name,
- obj.co_firstlineno,
- obj.co_linetable,
- obj.co_freevars,
- obj.co_cellvars,
- )
- elif hasattr(obj, "co_nmeta"): # pragma: no cover
- # "nogil" Python: modified attributes from 3.9
- args = (
- obj.co_argcount,
- obj.co_posonlyargcount,
- obj.co_kwonlyargcount,
- obj.co_nlocals,
- obj.co_framesize,
- obj.co_ndefaultargs,
- obj.co_nmeta,
- obj.co_flags,
- obj.co_code,
- obj.co_consts,
- obj.co_varnames,
- obj.co_filename,
- obj.co_name,
- obj.co_firstlineno,
- obj.co_lnotab,
- obj.co_exc_handlers,
- obj.co_jump_table,
- obj.co_freevars,
- obj.co_cellvars,
- obj.co_free2reg,
- obj.co_cell2reg,
- )
- else:
- # Backward compat for 3.8 and 3.9
- args = (
- obj.co_argcount,
- obj.co_posonlyargcount,
- obj.co_kwonlyargcount,
- obj.co_nlocals,
- obj.co_stacksize,
- obj.co_flags,
- obj.co_code,
- obj.co_consts,
- obj.co_names,
- obj.co_varnames,
- obj.co_filename,
- obj.co_name,
- obj.co_firstlineno,
- obj.co_lnotab,
- obj.co_freevars,
- obj.co_cellvars,
- )
- return types.CodeType, args
- def _cell_reduce(obj):
- """Cell (containing values of a function's free variables) reducer."""
- try:
- obj.cell_contents
- except ValueError: # cell is empty
- return _make_empty_cell, ()
- else:
- return _make_cell, (obj.cell_contents,)
- def _classmethod_reduce(obj):
- orig_func = obj.__func__
- return type(obj), (orig_func,)
- def _file_reduce(obj):
- """Save a file."""
- import io
- if not hasattr(obj, "name") or not hasattr(obj, "mode"):
- raise pickle.PicklingError(
- "Cannot pickle files that do not map to an actual file"
- )
- if obj is sys.stdout:
- return getattr, (sys, "stdout")
- if obj is sys.stderr:
- return getattr, (sys, "stderr")
- if obj is sys.stdin:
- raise pickle.PicklingError("Cannot pickle standard input")
- if obj.closed:
- raise pickle.PicklingError("Cannot pickle closed files")
- if hasattr(obj, "isatty") and obj.isatty():
- raise pickle.PicklingError("Cannot pickle files that map to tty objects")
- if "r" not in obj.mode and "+" not in obj.mode:
- raise pickle.PicklingError(
- "Cannot pickle files that are not opened for reading: %s" % obj.mode
- )
- name = obj.name
- retval = io.StringIO()
- try:
- # Read the whole file
- curloc = obj.tell()
- obj.seek(0)
- contents = obj.read()
- obj.seek(curloc)
- except OSError as e:
- raise pickle.PicklingError(
- "Cannot pickle file %s as it cannot be read" % name
- ) from e
- retval.write(contents)
- retval.seek(curloc)
- retval.name = name
- return _file_reconstructor, (retval,)
- def _getset_descriptor_reduce(obj):
- return getattr, (obj.__objclass__, obj.__name__)
- def _mappingproxy_reduce(obj):
- return types.MappingProxyType, (dict(obj),)
- def _memoryview_reduce(obj):
- return bytes, (obj.tobytes(),)
- def _module_reduce(obj):
- if _should_pickle_by_reference(obj):
- return subimport, (obj.__name__,)
- else:
- # Some external libraries can populate the "__builtins__" entry of a
- # module's `__dict__` with unpicklable objects (see #316). For that
- # reason, we do not attempt to pickle the "__builtins__" entry, and
- # restore a default value for it at unpickling time.
- state = obj.__dict__.copy()
- state.pop("__builtins__", None)
- return dynamic_subimport, (obj.__name__, state)
- def _method_reduce(obj):
- return (types.MethodType, (obj.__func__, obj.__self__))
- def _logger_reduce(obj):
- return logging.getLogger, (obj.name,)
- def _root_logger_reduce(obj):
- return logging.getLogger, ()
- def _property_reduce(obj):
- return property, (obj.fget, obj.fset, obj.fdel, obj.__doc__)
- def _weakset_reduce(obj):
- return weakref.WeakSet, (list(obj),)
- def _dynamic_class_reduce(obj):
- """Save a class that can't be referenced as a module attribute.
- This method is used to serialize classes that are defined inside
- functions, or that otherwise can't be serialized as attribute lookups
- from importable modules.
- """
- if Enum is not None and issubclass(obj, Enum):
- return (
- _make_skeleton_enum,
- _enum_getnewargs(obj),
- _enum_getstate(obj),
- None,
- None,
- _class_setstate,
- )
- else:
- return (
- _make_skeleton_class,
- _class_getnewargs(obj),
- _class_getstate(obj),
- None,
- None,
- _class_setstate,
- )
- def _class_reduce(obj):
- """Select the reducer depending on the dynamic nature of the class obj."""
- if obj is type(None): # noqa
- return type, (None,)
- elif obj is type(Ellipsis):
- return type, (Ellipsis,)
- elif obj is type(NotImplemented):
- return type, (NotImplemented,)
- elif obj in _BUILTIN_TYPE_NAMES:
- return _builtin_type, (_BUILTIN_TYPE_NAMES[obj],)
- elif not _should_pickle_by_reference(obj):
- return _dynamic_class_reduce(obj)
- return NotImplemented
- def _dict_keys_reduce(obj):
- # Safer not to ship the full dict as sending the rest might
- # be unintended and could potentially cause leaking of
- # sensitive information
- return _make_dict_keys, (list(obj),)
- def _dict_values_reduce(obj):
- # Safer not to ship the full dict as sending the rest might
- # be unintended and could potentially cause leaking of
- # sensitive information
- return _make_dict_values, (list(obj),)
- def _dict_items_reduce(obj):
- return _make_dict_items, (dict(obj),)
- def _odict_keys_reduce(obj):
- # Safer not to ship the full dict as sending the rest might
- # be unintended and could potentially cause leaking of
- # sensitive information
- return _make_dict_keys, (list(obj), True)
- def _odict_values_reduce(obj):
- # Safer not to ship the full dict as sending the rest might
- # be unintended and could potentially cause leaking of
- # sensitive information
- return _make_dict_values, (list(obj), True)
- def _odict_items_reduce(obj):
- return _make_dict_items, (dict(obj), True)
- def _dataclass_field_base_reduce(obj):
- return _get_dataclass_field_type_sentinel, (obj.name,)
- # COLLECTIONS OF OBJECTS STATE SETTERS
- # ------------------------------------
- # state setters are called at unpickling time, once the object is created and
- # it has to be updated to how it was at unpickling time.
- def _function_setstate(obj, state):
- """Update the state of a dynamic function.
- As __closure__ and __globals__ are readonly attributes of a function, we
- cannot rely on the native setstate routine of pickle.load_build, that calls
- setattr on items of the slotstate. Instead, we have to modify them inplace.
- """
- state, slotstate = state
- obj.__dict__.update(state)
- obj_globals = slotstate.pop("__globals__")
- obj_closure = slotstate.pop("__closure__")
- # _cloudpickle_subimports is a set of submodules that must be loaded for
- # the pickled function to work correctly at unpickling time. Now that these
- # submodules are depickled (hence imported), they can be removed from the
- # object's state (the object state only served as a reference holder to
- # these submodules)
- slotstate.pop("_cloudpickle_submodules")
- obj.__globals__.update(obj_globals)
- obj.__globals__["__builtins__"] = __builtins__
- if obj_closure is not None:
- for i, cell in enumerate(obj_closure):
- try:
- value = cell.cell_contents
- except ValueError: # cell is empty
- continue
- obj.__closure__[i].cell_contents = value
- for k, v in slotstate.items():
- setattr(obj, k, v)
- def _class_setstate(obj, state):
- state, slotstate = state
- registry = None
- for attrname, attr in state.items():
- if attrname == "_abc_impl":
- registry = attr
- else:
- setattr(obj, attrname, attr)
- if registry is not None:
- for subclass in registry:
- obj.register(subclass)
- return obj
- # COLLECTION OF DATACLASS UTILITIES
- # ---------------------------------
- # There are some internal sentinel values whose identity must be preserved when
- # unpickling dataclass fields. Each sentinel value has a unique name that we can
- # use to retrieve its identity at unpickling time.
- _DATACLASSE_FIELD_TYPE_SENTINELS = {
- dataclasses._FIELD.name: dataclasses._FIELD,
- dataclasses._FIELD_CLASSVAR.name: dataclasses._FIELD_CLASSVAR,
- dataclasses._FIELD_INITVAR.name: dataclasses._FIELD_INITVAR,
- }
- def _get_dataclass_field_type_sentinel(name):
- return _DATACLASSE_FIELD_TYPE_SENTINELS[name]
- class Pickler(pickle.Pickler):
- # set of reducers defined and used by cloudpickle (private)
- _dispatch_table = {}
- _dispatch_table[classmethod] = _classmethod_reduce
- _dispatch_table[io.TextIOWrapper] = _file_reduce
- _dispatch_table[logging.Logger] = _logger_reduce
- _dispatch_table[logging.RootLogger] = _root_logger_reduce
- _dispatch_table[memoryview] = _memoryview_reduce
- _dispatch_table[property] = _property_reduce
- _dispatch_table[staticmethod] = _classmethod_reduce
- _dispatch_table[CellType] = _cell_reduce
- _dispatch_table[types.CodeType] = _code_reduce
- _dispatch_table[types.GetSetDescriptorType] = _getset_descriptor_reduce
- _dispatch_table[types.ModuleType] = _module_reduce
- _dispatch_table[types.MethodType] = _method_reduce
- _dispatch_table[types.MappingProxyType] = _mappingproxy_reduce
- _dispatch_table[weakref.WeakSet] = _weakset_reduce
- _dispatch_table[typing.TypeVar] = _typevar_reduce
- _dispatch_table[_collections_abc.dict_keys] = _dict_keys_reduce
- _dispatch_table[_collections_abc.dict_values] = _dict_values_reduce
- _dispatch_table[_collections_abc.dict_items] = _dict_items_reduce
- _dispatch_table[type(OrderedDict().keys())] = _odict_keys_reduce
- _dispatch_table[type(OrderedDict().values())] = _odict_values_reduce
- _dispatch_table[type(OrderedDict().items())] = _odict_items_reduce
- _dispatch_table[abc.abstractmethod] = _classmethod_reduce
- _dispatch_table[abc.abstractclassmethod] = _classmethod_reduce
- _dispatch_table[abc.abstractstaticmethod] = _classmethod_reduce
- _dispatch_table[abc.abstractproperty] = _property_reduce
- _dispatch_table[dataclasses._FIELD_BASE] = _dataclass_field_base_reduce
- dispatch_table = ChainMap(_dispatch_table, copyreg.dispatch_table)
- # function reducers are defined as instance methods of cloudpickle.Pickler
- # objects, as they rely on a cloudpickle.Pickler attribute (globals_ref)
- def _dynamic_function_reduce(self, func):
- """Reduce a function that is not pickleable via attribute lookup."""
- newargs = self._function_getnewargs(func)
- state = _function_getstate(func)
- return (_make_function, newargs, state, None, None, _function_setstate)
- def _function_reduce(self, obj):
- """Reducer for function objects.
- If obj is a top-level attribute of a file-backed module, this reducer
- returns NotImplemented, making the cloudpickle.Pickler fall back to
- traditional pickle.Pickler routines to save obj. Otherwise, it reduces
- obj using a custom cloudpickle reducer designed specifically to handle
- dynamic functions.
- """
- if _should_pickle_by_reference(obj):
- return NotImplemented
- else:
- return self._dynamic_function_reduce(obj)
- def _function_getnewargs(self, func):
- code = func.__code__
- # base_globals represents the future global namespace of func at
- # unpickling time. Looking it up and storing it in
- # cloudpickle.Pickler.globals_ref allow functions sharing the same
- # globals at pickling time to also share them once unpickled, at one
- # condition: since globals_ref is an attribute of a cloudpickle.Pickler
- # instance, and that a new cloudpickle.Pickler is created each time
- # cloudpickle.dump or cloudpickle.dumps is called, functions also need
- # to be saved within the same invocation of
- # cloudpickle.dump/cloudpickle.dumps (for example:
- # cloudpickle.dumps([f1, f2])). There is no such limitation when using
- # cloudpickle.Pickler.dump, as long as the multiple invocations are
- # bound to the same cloudpickle.Pickler instance.
- base_globals = self.globals_ref.setdefault(id(func.__globals__), {})
- if base_globals == {}:
- # Add module attributes used to resolve relative imports
- # instructions inside func.
- for k in ["__package__", "__name__", "__path__", "__file__"]:
- if k in func.__globals__:
- base_globals[k] = func.__globals__[k]
- # Do not bind the free variables before the function is created to
- # avoid infinite recursion.
- if func.__closure__ is None:
- closure = None
- else:
- closure = tuple(_make_empty_cell() for _ in range(len(code.co_freevars)))
- return code, base_globals, None, None, closure
- def dump(self, obj):
- try:
- return super().dump(obj)
- except RuntimeError as e:
- if len(e.args) > 0 and "recursion" in e.args[0]:
- msg = "Could not pickle object as excessively deep recursion required."
- raise pickle.PicklingError(msg) from e
- else:
- raise
- def __init__(self, file, protocol=None, buffer_callback=None):
- if protocol is None:
- protocol = DEFAULT_PROTOCOL
- super().__init__(file, protocol=protocol, buffer_callback=buffer_callback)
- # map functions __globals__ attribute ids, to ensure that functions
- # sharing the same global namespace at pickling time also share
- # their global namespace at unpickling time.
- self.globals_ref = {}
- self.proto = int(protocol)
- if not PYPY:
- # pickle.Pickler is the C implementation of the CPython pickler and
- # therefore we rely on reduce_override method to customize the pickler
- # behavior.
- # `cloudpickle.Pickler.dispatch` is only left for backward
- # compatibility - note that when using protocol 5,
- # `cloudpickle.Pickler.dispatch` is not an extension of
- # `pickle._Pickler.dispatch` dictionary, because `cloudpickle.Pickler`
- # subclasses the C-implemented `pickle.Pickler`, which does not expose
- # a `dispatch` attribute. Earlier versions of `cloudpickle.Pickler`
- # used `cloudpickle.Pickler.dispatch` as a class-level attribute
- # storing all reducers implemented by cloudpickle, but the attribute
- # name was not a great choice given because it would collide with a
- # similarly named attribute in the pure-Python `pickle._Pickler`
- # implementation in the standard library.
- dispatch = dispatch_table
- # Implementation of the reducer_override callback, in order to
- # efficiently serialize dynamic functions and classes by subclassing
- # the C-implemented `pickle.Pickler`.
- # TODO: decorrelate reducer_override (which is tied to CPython's
- # implementation - would it make sense to backport it to pypy? - and
- # pickle's protocol 5 which is implementation agnostic. Currently, the
- # availability of both notions coincide on CPython's pickle, but it may
- # not be the case anymore when pypy implements protocol 5.
- def reducer_override(self, obj):
- """Type-agnostic reducing callback for function and classes.
- For performance reasons, subclasses of the C `pickle.Pickler` class
- cannot register custom reducers for functions and classes in the
- dispatch_table attribute. Reducers for such types must instead
- implemented via the special `reducer_override` method.
- Note that this method will be called for any object except a few
- builtin-types (int, lists, dicts etc.), which differs from reducers
- in the Pickler's dispatch_table, each of them being invoked for
- objects of a specific type only.
- This property comes in handy for classes: although most classes are
- instances of the ``type`` metaclass, some of them can be instances
- of other custom metaclasses (such as enum.EnumMeta for example). In
- particular, the metaclass will likely not be known in advance, and
- thus cannot be special-cased using an entry in the dispatch_table.
- reducer_override, among other things, allows us to register a
- reducer that will be called for any class, independently of its
- type.
- Notes:
- * reducer_override has the priority over dispatch_table-registered
- reducers.
- * reducer_override can be used to fix other limitations of
- cloudpickle for other types that suffered from type-specific
- reducers, such as Exceptions. See
- https://github.com/cloudpipe/cloudpickle/issues/248
- """
- t = type(obj)
- try:
- is_anyclass = issubclass(t, type)
- except TypeError: # t is not a class (old Boost; see SF #502085)
- is_anyclass = False
- if is_anyclass:
- return _class_reduce(obj)
- elif isinstance(obj, types.FunctionType):
- return self._function_reduce(obj)
- else:
- # fallback to save_global, including the Pickler's
- # dispatch_table
- return NotImplemented
- else:
- # When reducer_override is not available, hack the pure-Python
- # Pickler's types.FunctionType and type savers. Note: the type saver
- # must override Pickler.save_global, because pickle.py contains a
- # hard-coded call to save_global when pickling meta-classes.
- dispatch = pickle.Pickler.dispatch.copy()
- def _save_reduce_pickle5(
- self,
- func,
- args,
- state=None,
- listitems=None,
- dictitems=None,
- state_setter=None,
- obj=None,
- ):
- save = self.save
- write = self.write
- self.save_reduce(
- func,
- args,
- state=None,
- listitems=listitems,
- dictitems=dictitems,
- obj=obj,
- )
- # backport of the Python 3.8 state_setter pickle operations
- save(state_setter)
- save(obj) # simple BINGET opcode as obj is already memoized.
- save(state)
- write(pickle.TUPLE2)
- # Trigger a state_setter(obj, state) function call.
- write(pickle.REDUCE)
- # The purpose of state_setter is to carry-out an
- # inplace modification of obj. We do not care about what the
- # method might return, so its output is eventually removed from
- # the stack.
- write(pickle.POP)
- def save_global(self, obj, name=None, pack=struct.pack):
- """Main dispatch method.
- The name of this method is somewhat misleading: all types get
- dispatched here.
- """
- if obj is type(None): # noqa
- return self.save_reduce(type, (None,), obj=obj)
- elif obj is type(Ellipsis):
- return self.save_reduce(type, (Ellipsis,), obj=obj)
- elif obj is type(NotImplemented):
- return self.save_reduce(type, (NotImplemented,), obj=obj)
- elif obj in _BUILTIN_TYPE_NAMES:
- return self.save_reduce(
- _builtin_type, (_BUILTIN_TYPE_NAMES[obj],), obj=obj
- )
- if name is not None:
- super().save_global(obj, name=name)
- elif not _should_pickle_by_reference(obj, name=name):
- self._save_reduce_pickle5(*_dynamic_class_reduce(obj), obj=obj)
- else:
- super().save_global(obj, name=name)
- dispatch[type] = save_global
- def save_function(self, obj, name=None):
- """Registered with the dispatch to handle all function types.
- Determines what kind of function obj is (e.g. lambda, defined at
- interactive prompt, etc) and handles the pickling appropriately.
- """
- if _should_pickle_by_reference(obj, name=name):
- return super().save_global(obj, name=name)
- elif PYPY and isinstance(obj.__code__, builtin_code_type):
- return self.save_pypy_builtin_func(obj)
- else:
- return self._save_reduce_pickle5(
- *self._dynamic_function_reduce(obj), obj=obj
- )
- def save_pypy_builtin_func(self, obj):
- """Save pypy equivalent of builtin functions.
- PyPy does not have the concept of builtin-functions. Instead,
- builtin-functions are simple function instances, but with a
- builtin-code attribute.
- Most of the time, builtin functions should be pickled by attribute.
- But PyPy has flaky support for __qualname__, so some builtin
- functions such as float.__new__ will be classified as dynamic. For
- this reason only, we created this special routine. Because
- builtin-functions are not expected to have closure or globals,
- there is no additional hack (compared the one already implemented
- in pickle) to protect ourselves from reference cycles. A simple
- (reconstructor, newargs, obj.__dict__) tuple is save_reduced. Note
- also that PyPy improved their support for __qualname__ in v3.6, so
- this routing should be removed when cloudpickle supports only PyPy
- 3.6 and later.
- """
- rv = (
- types.FunctionType,
- (obj.__code__, {}, obj.__name__, obj.__defaults__, obj.__closure__),
- obj.__dict__,
- )
- self.save_reduce(*rv, obj=obj)
- dispatch[types.FunctionType] = save_function
- # Shorthands similar to pickle.dump/pickle.dumps
- def dump(obj, file, protocol=None, buffer_callback=None):
- """Serialize obj as bytes streamed into file
- protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to
- pickle.HIGHEST_PROTOCOL. This setting favors maximum communication
- speed between processes running the same Python version.
- Set protocol=pickle.DEFAULT_PROTOCOL instead if you need to ensure
- compatibility with older versions of Python (although this is not always
- guaranteed to work because cloudpickle relies on some internal
- implementation details that can change from one Python version to the
- next).
- """
- Pickler(file, protocol=protocol, buffer_callback=buffer_callback).dump(obj)
- def dumps(obj, protocol=None, buffer_callback=None):
- """Serialize obj as a string of bytes allocated in memory
- protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to
- pickle.HIGHEST_PROTOCOL. This setting favors maximum communication
- speed between processes running the same Python version.
- Set protocol=pickle.DEFAULT_PROTOCOL instead if you need to ensure
- compatibility with older versions of Python (although this is not always
- guaranteed to work because cloudpickle relies on some internal
- implementation details that can change from one Python version to the
- next).
- """
- with io.BytesIO() as file:
- cp = Pickler(file, protocol=protocol, buffer_callback=buffer_callback)
- cp.dump(obj)
- return file.getvalue()
- # Include pickles unloading functions in this namespace for convenience.
- load, loads = pickle.load, pickle.loads
- # Backward compat alias.
- CloudPickler = Pickler
|