├── .codecov.yml ├── .coveragerc ├── .gitignore ├── .readthedocs.yml ├── .travis.yml ├── DEV_NOTES ├── LICENSE ├── MANIFEST.in ├── README.md ├── dill ├── __diff.py ├── __init__.py ├── _dill.py ├── _objects.py ├── _shims.py ├── detect.py ├── logger.py ├── objtypes.py ├── pointers.py ├── session.py ├── settings.py ├── source.py ├── temp.py └── tests │ ├── __init__.py │ ├── __main__.py │ ├── test_abc.py │ ├── test_check.py │ ├── test_classdef.py │ ├── test_dataclasses.py │ ├── test_detect.py │ ├── test_dictviews.py │ ├── test_diff.py │ ├── test_extendpickle.py │ ├── test_fglobals.py │ ├── test_file.py │ ├── test_functions.py │ ├── test_functors.py │ ├── test_logger.py │ ├── test_mixins.py │ ├── test_module.py │ ├── test_moduledict.py │ ├── test_nested.py │ ├── test_objects.py │ ├── test_properties.py │ ├── test_pycapsule.py │ ├── test_recursive.py │ ├── test_registered.py │ ├── test_restricted.py │ ├── test_selected.py │ ├── test_session.py │ ├── test_source.py │ ├── test_sources.py │ ├── test_temp.py │ ├── test_threads.py │ └── test_weakref.py ├── docs ├── Makefile ├── requirements.txt └── source │ ├── _static │ └── css │ │ └── custom.css │ ├── conf.py │ ├── dill.rst │ ├── index.rst │ ├── pathos.png │ └── scripts.rst ├── pyproject.toml ├── scripts ├── get_gprof ├── get_objgraph └── undill ├── setup.cfg ├── setup.py ├── tox.ini └── version.py /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | # Commits pushed to master should not make the overall 8 | # project coverage decrease by more than 1%: 9 | target: auto 10 | threshold: 1% 11 | patch: 12 | default: 13 | # Be tolerant on slight code coverage diff on PRs to limit 14 | # noisy red coverage status on github PRs. 15 | # Note The coverage stats are still uploaded 16 | # to codecov so that PR reviewers can see uncovered lines 17 | # in the github diff if they install the codecov browser 18 | # extension: 19 | # https://github.com/codecov/browser-extension 20 | target: auto 21 | threshold: 1% 22 | 23 | fixes: 24 | # reduces pip-installed path to git root and 25 | # remove dist-name from setup-installed path 26 | - "*/site-packages/::" 27 | - "*/site-packages/dill-*::" 28 | 29 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | # source = dill 3 | include = 4 | */dill/* 5 | omit = 6 | */tests/* 7 | */info.py 8 | branch = true 9 | # timid = true 10 | # parallel = true # and need to 'combine' data files 11 | # concurrency = multiprocessing # thread 12 | # data_file = $TRAVIS_BUILD_DIR/.coverage 13 | # debug = trace 14 | 15 | [paths] 16 | source = 17 | dill 18 | */site-packages/dill 19 | */site-packages/dill-*/dill 20 | 21 | [report] 22 | include = 23 | */dill/* 24 | exclude_lines = 25 | pragma: no cover 26 | raise NotImplementedError 27 | if __name__ == .__main__.: 28 | # show_missing = true 29 | ignore_errors = true 30 | # pragma: no branch 31 | # noqa 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox/ 2 | .cache/ 3 | *.egg-info/ 4 | *.pyc 5 | /docs/build 6 | /build 7 | /README 8 | /dill/__info__.py 9 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # readthedocs configuration file 2 | # see https://docs.readthedocs.io/en/stable/config-file/v2.html 3 | version: 2 4 | 5 | # configure 6 | sphinx: 7 | configuration: docs/source/conf.py 8 | 9 | # build 10 | build: 11 | os: ubuntu-22.04 12 | tools: 13 | python: "3.10" 14 | 15 | # install 16 | python: 17 | install: 18 | - method: pip 19 | path: . 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: jammy 2 | language: python 3 | 4 | matrix: 5 | include: 6 | - python: '3.9' 7 | dist: focal 8 | env: 9 | - COVERAGE="true" 10 | - NUMPY="true" 11 | 12 | - python: '3.10' 13 | env: 14 | 15 | - python: '3.11' 16 | env: 17 | 18 | - python: '3.12' 19 | env: 20 | 21 | - python: '3.13' 22 | env: 23 | 24 | - python: '3.14-dev' 25 | env: 26 | 27 | - python: 'pypy3.9-7.3.9' # at 7.3.16 28 | env: 29 | 30 | - python: 'pypy3.10-7.3.19' 31 | env: 32 | 33 | - python: 'pypy3.11-7.3.19' 34 | env: 35 | 36 | allow_failures: 37 | - python: '3.14-dev' # CI missing 38 | - python: 'pypy3.10-7.3.19' # CI missing 39 | - python: 'pypy3.11-7.3.19' # CI missing 40 | fast_finish: true 41 | 42 | cache: 43 | pip: true 44 | 45 | before_install: 46 | - set -e # fail on any error 47 | - if [[ $COVERAGE == "true" ]]; then pip install coverage; fi 48 | - if [[ $NUMPY == "true" ]]; then pip install numpy; fi 49 | 50 | install: 51 | - python -m pip install . 52 | 53 | script: 54 | - for test in dill/tests/__init__.py; do echo $test ; if [[ $COVERAGE == "true" ]]; then coverage run -a $test > /dev/null; else python $test > /dev/null; fi ; done 55 | - for test in dill/tests/test_*.py; do echo $test ; if [[ $COVERAGE == "true" ]]; then coverage run -a $test > /dev/null; else python $test > /dev/null; fi ; done 56 | 57 | after_success: 58 | - if [[ $COVERAGE == "true" ]]; then bash <(curl -s https://codecov.io/bash); else echo ''; fi 59 | - if [[ $COVERAGE == "true" ]]; then coverage report; fi 60 | -------------------------------------------------------------------------------- /DEV_NOTES: -------------------------------------------------------------------------------- 1 | create a weakref: 2 | 3 | >>> import dill 4 | >>> import weakref 5 | >>> class Object: 6 | ... pass 7 | ... 8 | >>> o = Object() 9 | >>> r = weakref.ref(o) #XXX: type: weakref.ReferenceType 10 | 11 | >>> r 12 | 13 | >>> o 14 | <__main__.Object instance at 0xb23080> 15 | >>> r() 16 | <__main__.Object instance at 0xb23080> 17 | >>> r.__call__() 18 | <__main__.Object instance at 0xb23080> 19 | >>> r.__hash__() 20 | 11677824 21 | >>> id(o) 22 | 11677824 23 | 24 | 25 | >>> o2 = Object() 26 | >>> r2 = weakref.ref(o2) 27 | >>> del o2 28 | 29 | >>> r2 30 | 31 | >>> r2() 32 | >>> r2.__call__() 33 | >>> r2.__hash__() 34 | Traceback (most recent call last): 35 | File "", line 1, in 36 | TypeError: weak object has gone away 37 | 38 | 39 | >>> o3 = Object() 40 | >>> r3 = weakref.proxy(o3) #XXX: type: weakref.ProxyType 41 | 42 | >>> r3 43 | 44 | >>> o3 45 | <__main__.Object instance at 0x8d530> 46 | >>> r3.__class__ 47 | 48 | >>> o3.__class__ 49 | 50 | 51 | >>> del o3 52 | >>> r3 53 | 54 | >>> r3.__class__ 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | ReferenceError: weakly-referenced object no longer exists 58 | 59 | 60 | >>> class Object2: 61 | ... def __call__(self): 62 | ... pass 63 | ... 64 | >>> oo = Object2() 65 | >>> oo 66 | <__main__.Object instance at 0x8ca30> 67 | >>> oo() 68 | >>> rr = weakref.proxy(oo) #XXX: type: weakref.CallableProxyType 69 | >>> rr 70 | 71 | >>> rr() 72 | >>> rr.__call__ 73 | > 74 | >>> rr.__call__() 75 | >>> 76 | 77 | 78 | 79 | ######################################## 80 | approach to pickling weakrefs: 81 | 82 | *) register the weakref types ? (see line ~228 of dill.py) 83 | 84 | *) use hash to create hard copy of ref object 85 | then upon unpickle, create new weakref 86 | 87 | *) propose that pickling a weakref will always provide a dead reference 88 | unless the reference object is pickled along with the weakref 89 | 90 | ######################################## 91 | pickling generators: 92 | 93 | *) need to avoid the "new" method for FrameTypes... 94 | don't see how to do that, without going into C to get the GID Thread. 95 | 96 | *) currently inspecting: Objects/object.c Objects/dictobject.c Objects/genobject.c Objects/frameobject.c Python/pystate.c Python/thread.c Python/pythonrun.c 97 | 98 | *) the package "generator_tools" may have the answer. 99 | 100 | ######################################## 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2016 California Institute of Technology. 2 | Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 3 | All rights reserved. 4 | 5 | This software is available subject to the conditions and terms laid 6 | out below. By downloading and using this software you are agreeing 7 | to the following conditions. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions 11 | are met: 12 | 13 | - Redistributions of source code must retain the above copyright 14 | notice, this list of conditions and the following disclaimer. 15 | 16 | - Redistributions in binary form must reproduce the above copyright 17 | notice, this list of conditions and the following disclaimer in the 18 | documentation and/or other materials provided with the distribution. 19 | 20 | - Neither the names of the copyright holders nor the names of any of 21 | the contributors may be used to endorse or promote products derived 22 | from this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 26 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 27 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 31 | OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 32 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 33 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 34 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README* 3 | include MANIFEST.in 4 | include pyproject.toml 5 | include tox.ini 6 | include version.py 7 | include scripts/* 8 | recursive-include docs * 9 | include .* 10 | prune .git 11 | prune .coverage 12 | prune .eggs 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dill 2 | ==== 3 | serialize all of Python 4 | 5 | About Dill 6 | ---------- 7 | ``dill`` extends Python's ``pickle`` module for serializing and de-serializing 8 | Python objects to the majority of the built-in Python types. Serialization 9 | is the process of converting an object to a byte stream, and the inverse 10 | of which is converting a byte stream back to a Python object hierarchy. 11 | 12 | ``dill`` provides the user the same interface as the ``pickle`` module, and 13 | also includes some additional features. In addition to pickling Python 14 | objects, ``dill`` provides the ability to save the state of an interpreter 15 | session in a single command. Hence, it would be feasible to save an 16 | interpreter session, close the interpreter, ship the pickled file to 17 | another computer, open a new interpreter, unpickle the session and 18 | thus continue from the 'saved' state of the original interpreter 19 | session. 20 | 21 | ``dill`` can be used to store Python objects to a file, but the primary 22 | usage is to send Python objects across the network as a byte stream. 23 | ``dill`` is quite flexible, and allows arbitrary user defined classes 24 | and functions to be serialized. Thus ``dill`` is not intended to be 25 | secure against erroneously or maliciously constructed data. It is 26 | left to the user to decide whether the data they unpickle is from 27 | a trustworthy source. 28 | 29 | ``dill`` is part of ``pathos``, a Python framework for heterogeneous computing. 30 | ``dill`` is in active development, so any user feedback, bug reports, comments, 31 | or suggestions are highly appreciated. A list of issues is located at 32 | https://github.com/uqfoundation/dill/issues, with a legacy list maintained at 33 | https://uqfoundation.github.io/project/pathos/query. 34 | 35 | 36 | Major Features 37 | -------------- 38 | ``dill`` can pickle the following standard types: 39 | 40 | * none, type, bool, int, float, complex, bytes, str, 41 | * tuple, list, dict, file, buffer, builtin, 42 | * Python classes, namedtuples, dataclasses, metaclasses, 43 | * instances of classes, 44 | * set, frozenset, array, functions, exceptions 45 | 46 | ``dill`` can also pickle more 'exotic' standard types: 47 | 48 | * functions with yields, nested functions, lambdas, 49 | * cell, method, unboundmethod, module, code, methodwrapper, 50 | * methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, 51 | * dictproxy, slice, notimplemented, ellipsis, quit 52 | 53 | ``dill`` cannot yet pickle these standard types: 54 | 55 | * frame, generator, traceback 56 | 57 | ``dill`` also provides the capability to: 58 | 59 | * save and load Python interpreter sessions 60 | * save and extract the source code from functions and classes 61 | * interactively diagnose pickling errors 62 | 63 | 64 | Current Release 65 | [![Downloads](https://static.pepy.tech/personalized-badge/dill?period=total&units=international_system&left_color=grey&right_color=blue&left_text=pypi%20downloads)](https://pepy.tech/project/dill) 66 | [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/dill?color=blue&label=conda%20downloads)](https://anaconda.org/conda-forge/dill) 67 | [![Stack Overflow](https://img.shields.io/badge/stackoverflow-get%20help-black.svg)](https://stackoverflow.com/questions/tagged/dill) 68 | --------------- 69 | The latest released version of ``dill`` is available from: 70 | https://pypi.org/project/dill 71 | 72 | ``dill`` is distributed under a 3-clause BSD license. 73 | 74 | 75 | Development Version 76 | [![Support](https://img.shields.io/badge/support-the%20UQ%20Foundation-purple.svg?style=flat&colorA=grey&colorB=purple)](http://www.uqfoundation.org/pages/donate.html) 77 | [![Documentation Status](https://readthedocs.org/projects/dill/badge/?version=latest)](https://dill.readthedocs.io/en/latest/?badge=latest) 78 | [![Build Status](https://app.travis-ci.com/uqfoundation/dill.svg?label=build&logo=travis&branch=master)](https://app.travis-ci.com/github/uqfoundation/dill) 79 | [![codecov](https://codecov.io/gh/uqfoundation/dill/branch/master/graph/badge.svg)](https://codecov.io/gh/uqfoundation/dill) 80 | ------------------- 81 | You can get the latest development version with all the shiny new features at: 82 | https://github.com/uqfoundation 83 | 84 | If you have a new contribution, please submit a pull request. 85 | 86 | 87 | Installation 88 | ------------ 89 | ``dill`` can be installed with ``pip``:: 90 | 91 | $ pip install dill 92 | 93 | To optionally include the ``objgraph`` diagnostic tool in the install:: 94 | 95 | $ pip install dill[graph] 96 | 97 | To optionally include the ``gprof2dot`` diagnostic tool in the install:: 98 | 99 | $ pip install dill[profile] 100 | 101 | For windows users, to optionally install session history tools:: 102 | 103 | $ pip install dill[readline] 104 | 105 | 106 | Requirements 107 | ------------ 108 | ``dill`` requires: 109 | 110 | * ``python`` (or ``pypy``), **>=3.9** 111 | * ``setuptools``, **>=42** 112 | 113 | Optional requirements: 114 | 115 | * ``objgraph``, **>=1.7.2** 116 | * ``gprof2dot``, **>=2022.7.29** 117 | * ``pyreadline``, **>=1.7.1** (on windows) 118 | 119 | 120 | Basic Usage 121 | ----------- 122 | ``dill`` is a drop-in replacement for ``pickle``. Existing code can be 123 | updated to allow complete pickling using:: 124 | 125 | >>> import dill as pickle 126 | 127 | or:: 128 | 129 | >>> from dill import dumps, loads 130 | 131 | ``dumps`` converts the object to a unique byte string, and ``loads`` performs 132 | the inverse operation:: 133 | 134 | >>> squared = lambda x: x**2 135 | >>> loads(dumps(squared))(3) 136 | 9 137 | 138 | There are a number of options to control serialization which are provided 139 | as keyword arguments to several ``dill`` functions: 140 | 141 | * with *protocol*, the pickle protocol level can be set. This uses the 142 | same value as the ``pickle`` module, *DEFAULT_PROTOCOL*. 143 | * with *byref=True*, ``dill`` to behave a lot more like pickle with 144 | certain objects (like modules) pickled by reference as opposed to 145 | attempting to pickle the object itself. 146 | * with *recurse=True*, objects referred to in the global dictionary are 147 | recursively traced and pickled, instead of the default behavior of 148 | attempting to store the entire global dictionary. 149 | * with *fmode*, the contents of the file can be pickled along with the file 150 | handle, which is useful if the object is being sent over the wire to a 151 | remote system which does not have the original file on disk. Options are 152 | *HANDLE_FMODE* for just the handle, *CONTENTS_FMODE* for the file content 153 | and *FILE_FMODE* for content and handle. 154 | * with *ignore=False*, objects reconstructed with types defined in the 155 | top-level script environment use the existing type in the environment 156 | rather than a possibly different reconstructed type. 157 | 158 | The default serialization can also be set globally in *dill.settings*. 159 | Thus, we can modify how ``dill`` handles references to the global dictionary 160 | locally or globally:: 161 | 162 | >>> import dill.settings 163 | >>> dumps(absolute) == dumps(absolute, recurse=True) 164 | False 165 | >>> dill.settings['recurse'] = True 166 | >>> dumps(absolute) == dumps(absolute, recurse=True) 167 | True 168 | 169 | ``dill`` also includes source code inspection, as an alternate to pickling:: 170 | 171 | >>> import dill.source 172 | >>> print(dill.source.getsource(squared)) 173 | squared = lambda x:x**2 174 | 175 | To aid in debugging pickling issues, use *dill.detect* which provides 176 | tools like pickle tracing:: 177 | 178 | >>> import dill.detect 179 | >>> with dill.detect.trace(): 180 | >>> dumps(squared) 181 | ┬ F1: at 0x7fe074f8c280> 182 | ├┬ F2: 183 | │└ # F2 [34 B] 184 | ├┬ Co: at 0x7fe07501eb30, file "", line 1> 185 | │├┬ F2: 186 | ││└ # F2 [19 B] 187 | │└ # Co [87 B] 188 | ├┬ D1: 189 | │└ # D1 [22 B] 190 | ├┬ D2: 191 | │└ # D2 [2 B] 192 | ├┬ D2: 193 | │├┬ D2: 194 | ││└ # D2 [2 B] 195 | │└ # D2 [23 B] 196 | └ # F1 [180 B] 197 | 198 | With trace, we see how ``dill`` stored the lambda (``F1``) by first storing 199 | ``_create_function``, the underlying code object (``Co``) and ``_create_code`` 200 | (which is used to handle code objects), then we handle the reference to 201 | the global dict (``D2``) plus other dictionaries (``D1`` and ``D2``) that 202 | save the lambda object's state. A ``#`` marks when the object is actually stored. 203 | 204 | 205 | More Information 206 | ---------------- 207 | Probably the best way to get started is to look at the documentation at 208 | http://dill.rtfd.io. Also see ``dill.tests`` for a set of scripts that 209 | demonstrate how ``dill`` can serialize different Python objects. You can 210 | run the test suite with ``python -m dill.tests``. The contents of any 211 | pickle file can be examined with ``undill``. As ``dill`` conforms to 212 | the ``pickle`` interface, the examples and documentation found at 213 | http://docs.python.org/library/pickle.html also apply to ``dill`` 214 | if one will ``import dill as pickle``. The source code is also generally 215 | well documented, so further questions may be resolved by inspecting the 216 | code itself. Please feel free to submit a ticket on github, or ask a 217 | question on stackoverflow (**@Mike McKerns**). 218 | If you would like to share how you use ``dill`` in your work, please send 219 | an email (to **mmckerns at uqfoundation dot org**). 220 | 221 | 222 | Citation 223 | -------- 224 | If you use ``dill`` to do research that leads to publication, we ask that you 225 | acknowledge use of ``dill`` by citing the following in your publication:: 226 | 227 | M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis, 228 | "Building a framework for predictive science", Proceedings of 229 | the 10th Python in Science Conference, 2011; 230 | http://arxiv.org/pdf/1202.1056 231 | 232 | Michael McKerns and Michael Aivazis, 233 | "pathos: a framework for heterogeneous computing", 2010- ; 234 | https://uqfoundation.github.io/project/pathos 235 | 236 | Please see https://uqfoundation.github.io/project/pathos or 237 | http://arxiv.org/pdf/1202.1056 for further information. 238 | 239 | -------------------------------------------------------------------------------- /dill/__diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | """ 10 | Module to show if an object has changed since it was memorised 11 | """ 12 | 13 | import builtins 14 | import os 15 | import sys 16 | import types 17 | try: 18 | import numpy.ma 19 | HAS_NUMPY = True 20 | except ImportError: 21 | HAS_NUMPY = False 22 | 23 | # pypy doesn't use reference counting 24 | getrefcount = getattr(sys, 'getrefcount', lambda x:0) 25 | 26 | # memo of objects indexed by id to a tuple (attributes, sequence items) 27 | # attributes is a dict indexed by attribute name to attribute id 28 | # sequence items is either a list of ids, of a dictionary of keys to ids 29 | memo = {} 30 | id_to_obj = {} 31 | # types that cannot have changing attributes 32 | builtins_types = set((str, list, dict, set, frozenset, int)) 33 | dont_memo = set(id(i) for i in (memo, sys.modules, sys.path_importer_cache, 34 | os.environ, id_to_obj)) 35 | 36 | 37 | def get_attrs(obj): 38 | """ 39 | Gets all the attributes of an object though its __dict__ or return None 40 | """ 41 | if type(obj) in builtins_types \ 42 | or type(obj) is type and obj in builtins_types: 43 | return 44 | return getattr(obj, '__dict__', None) 45 | 46 | 47 | def get_seq(obj, cache={str: False, frozenset: False, list: True, set: True, 48 | dict: True, tuple: True, type: False, 49 | types.ModuleType: False, types.FunctionType: False, 50 | types.BuiltinFunctionType: False}): 51 | """ 52 | Gets all the items in a sequence or return None 53 | """ 54 | try: 55 | o_type = obj.__class__ 56 | except AttributeError: 57 | o_type = type(obj) 58 | hsattr = hasattr 59 | if o_type in cache: 60 | if cache[o_type]: 61 | if hsattr(obj, "copy"): 62 | return obj.copy() 63 | return obj 64 | elif HAS_NUMPY and o_type in (numpy.ndarray, numpy.ma.core.MaskedConstant): 65 | if obj.shape and obj.size: 66 | return obj 67 | else: 68 | return [] 69 | elif hsattr(obj, "__contains__") and hsattr(obj, "__iter__") \ 70 | and hsattr(obj, "__len__") and hsattr(o_type, "__contains__") \ 71 | and hsattr(o_type, "__iter__") and hsattr(o_type, "__len__"): 72 | cache[o_type] = True 73 | if hsattr(obj, "copy"): 74 | return obj.copy() 75 | return obj 76 | else: 77 | cache[o_type] = False 78 | return None 79 | 80 | 81 | def memorise(obj, force=False): 82 | """ 83 | Adds an object to the memo, and recursively adds all the objects 84 | attributes, and if it is a container, its items. Use force=True to update 85 | an object already in the memo. Updating is not recursively done. 86 | """ 87 | obj_id = id(obj) 88 | if obj_id in memo and not force or obj_id in dont_memo: 89 | return 90 | id_ = id 91 | g = get_attrs(obj) 92 | if g is None: 93 | attrs_id = None 94 | else: 95 | attrs_id = dict((key,id_(value)) for key, value in g.items()) 96 | 97 | s = get_seq(obj) 98 | if s is None: 99 | seq_id = None 100 | elif hasattr(s, "items"): 101 | seq_id = dict((id_(key),id_(value)) for key, value in s.items()) 102 | elif not hasattr(s, "__len__"): #XXX: avoid TypeError from unexpected case 103 | seq_id = None 104 | else: 105 | seq_id = [id_(i) for i in s] 106 | 107 | memo[obj_id] = attrs_id, seq_id 108 | id_to_obj[obj_id] = obj 109 | mem = memorise 110 | if g is not None: 111 | [mem(value) for key, value in g.items()] 112 | 113 | if s is not None: 114 | if hasattr(s, "items"): 115 | [(mem(key), mem(item)) 116 | for key, item in s.items()] 117 | else: 118 | if hasattr(s, '__len__'): 119 | [mem(item) for item in s] 120 | else: mem(s) 121 | 122 | 123 | def release_gone(): 124 | itop, mp, src = id_to_obj.pop, memo.pop, getrefcount 125 | [(itop(id_), mp(id_)) for id_, obj in list(id_to_obj.items()) 126 | if src(obj) < 4] #XXX: correct for pypy? 127 | 128 | 129 | def whats_changed(obj, seen=None, simple=False, first=True): 130 | """ 131 | Check an object against the memo. Returns a list in the form 132 | (attribute changes, container changed). Attribute changes is a dict of 133 | attribute name to attribute value. container changed is a boolean. 134 | If simple is true, just returns a boolean. None for either item means 135 | that it has not been checked yet 136 | """ 137 | # Special cases 138 | if first: 139 | # ignore the _ variable, which only appears in interactive sessions 140 | if "_" in builtins.__dict__: 141 | del builtins._ 142 | if seen is None: 143 | seen = {} 144 | 145 | obj_id = id(obj) 146 | 147 | if obj_id in seen: 148 | if simple: 149 | return any(seen[obj_id]) 150 | return seen[obj_id] 151 | 152 | # Safety checks 153 | if obj_id in dont_memo: 154 | seen[obj_id] = [{}, False] 155 | if simple: 156 | return False 157 | return seen[obj_id] 158 | elif obj_id not in memo: 159 | if simple: 160 | return True 161 | else: 162 | raise RuntimeError("Object not memorised " + str(obj)) 163 | 164 | seen[obj_id] = ({}, False) 165 | 166 | chngd = whats_changed 167 | id_ = id 168 | 169 | # compare attributes 170 | attrs = get_attrs(obj) 171 | if attrs is None: 172 | changed = {} 173 | else: 174 | obj_attrs = memo[obj_id][0] 175 | obj_get = obj_attrs.get 176 | changed = dict((key,None) for key in obj_attrs if key not in attrs) 177 | for key, o in attrs.items(): 178 | if id_(o) != obj_get(key, None) or chngd(o, seen, True, False): 179 | changed[key] = o 180 | 181 | # compare sequence 182 | items = get_seq(obj) 183 | seq_diff = False 184 | if (items is not None) and (hasattr(items, '__len__')): 185 | obj_seq = memo[obj_id][1] 186 | if (len(items) != len(obj_seq)): 187 | seq_diff = True 188 | elif hasattr(obj, "items"): # dict type obj 189 | obj_get = obj_seq.get 190 | for key, item in items.items(): 191 | if id_(item) != obj_get(id_(key)) \ 192 | or chngd(key, seen, True, False) \ 193 | or chngd(item, seen, True, False): 194 | seq_diff = True 195 | break 196 | else: 197 | for i, j in zip(items, obj_seq): # list type obj 198 | if id_(i) != j or chngd(i, seen, True, False): 199 | seq_diff = True 200 | break 201 | seen[obj_id] = changed, seq_diff 202 | if simple: 203 | return changed or seq_diff 204 | return changed, seq_diff 205 | 206 | 207 | def has_changed(*args, **kwds): 208 | kwds['simple'] = True # ignore simple if passed in 209 | return whats_changed(*args, **kwds) 210 | 211 | __import__ = __import__ 212 | 213 | 214 | def _imp(*args, **kwds): 215 | """ 216 | Replaces the default __import__, to allow a module to be memorised 217 | before the user can change it 218 | """ 219 | before = set(sys.modules.keys()) 220 | mod = __import__(*args, **kwds) 221 | after = set(sys.modules.keys()).difference(before) 222 | for m in after: 223 | memorise(sys.modules[m]) 224 | return mod 225 | 226 | builtins.__import__ = _imp 227 | if hasattr(builtins, "_"): 228 | del builtins._ 229 | 230 | # memorise all already imported modules. This implies that this must be 231 | # imported first for any changes to be recorded 232 | for mod in list(sys.modules.values()): 233 | memorise(mod) 234 | release_gone() 235 | -------------------------------------------------------------------------------- /dill/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | # author, version, license, and long description 10 | try: # the package is installed 11 | from .__info__ import __version__, __author__, __doc__, __license__ 12 | except: # pragma: no cover 13 | import os 14 | import sys 15 | parent = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) 16 | sys.path.append(parent) 17 | # get distribution meta info 18 | from version import (__version__, __author__, 19 | get_license_text, get_readme_as_rst) 20 | __license__ = get_license_text(os.path.join(parent, 'LICENSE')) 21 | __license__ = "\n%s" % __license__ 22 | __doc__ = get_readme_as_rst(os.path.join(parent, 'README.md')) 23 | del os, sys, parent, get_license_text, get_readme_as_rst 24 | 25 | 26 | from ._dill import ( 27 | dump, dumps, load, loads, copy, 28 | Pickler, Unpickler, register, pickle, pickles, check, 29 | DEFAULT_PROTOCOL, HIGHEST_PROTOCOL, HANDLE_FMODE, CONTENTS_FMODE, FILE_FMODE, 30 | PickleError, PickleWarning, PicklingError, PicklingWarning, UnpicklingError, 31 | UnpicklingWarning, 32 | ) 33 | from .session import ( 34 | dump_module, load_module, load_module_asdict, 35 | dump_session, load_session # backward compatibility 36 | ) 37 | from . import detect, logger, session, source, temp 38 | 39 | # get global settings 40 | from .settings import settings 41 | 42 | # make sure "trace" is turned off 43 | logger.trace(False) 44 | 45 | objects = {} 46 | # local import of dill._objects 47 | #from . import _objects 48 | #objects.update(_objects.succeeds) 49 | #del _objects 50 | 51 | # local import of dill.objtypes 52 | from . import objtypes as types 53 | 54 | def load_types(pickleable=True, unpickleable=True): 55 | """load pickleable and/or unpickleable types to ``dill.types`` 56 | 57 | ``dill.types`` is meant to mimic the ``types`` module, providing a 58 | registry of object types. By default, the module is empty (for import 59 | speed purposes). Use the ``load_types`` function to load selected object 60 | types to the ``dill.types`` module. 61 | 62 | Args: 63 | pickleable (bool, default=True): if True, load pickleable types. 64 | unpickleable (bool, default=True): if True, load unpickleable types. 65 | 66 | Returns: 67 | None 68 | """ 69 | from importlib import reload 70 | # local import of dill.objects 71 | from . import _objects 72 | if pickleable: 73 | objects.update(_objects.succeeds) 74 | else: 75 | [objects.pop(obj,None) for obj in _objects.succeeds] 76 | if unpickleable: 77 | objects.update(_objects.failures) 78 | else: 79 | [objects.pop(obj,None) for obj in _objects.failures] 80 | objects.update(_objects.registered) 81 | del _objects 82 | # reset contents of types to 'empty' 83 | [types.__dict__.pop(obj) for obj in list(types.__dict__.keys()) \ 84 | if obj.find('Type') != -1] 85 | # add corresponding types from objects to types 86 | reload(types) 87 | 88 | def extend(use_dill=True): 89 | '''add (or remove) dill types to/from the pickle registry 90 | 91 | by default, ``dill`` populates its types to ``pickle.Pickler.dispatch``. 92 | Thus, all ``dill`` types are available upon calling ``'import pickle'``. 93 | To drop all ``dill`` types from the ``pickle`` dispatch, *use_dill=False*. 94 | 95 | Args: 96 | use_dill (bool, default=True): if True, extend the dispatch table. 97 | 98 | Returns: 99 | None 100 | ''' 101 | from ._dill import _revert_extension, _extend 102 | if use_dill: _extend() 103 | else: _revert_extension() 104 | return 105 | 106 | extend() 107 | 108 | 109 | def license(): 110 | """print license""" 111 | print (__license__) 112 | return 113 | 114 | def citation(): 115 | """print citation""" 116 | print (__doc__[-491:-118]) 117 | return 118 | 119 | # end of file 120 | -------------------------------------------------------------------------------- /dill/_shims.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Author: Anirudh Vegesana (avegesan@cs.stanford.edu) 5 | # Copyright (c) 2021-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | Provides shims for compatibility between versions of dill and Python. 10 | 11 | Compatibility shims should be provided in this file. Here are two simple example 12 | use cases. 13 | 14 | Deprecation of constructor function: 15 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 16 | Assume that we were transitioning _import_module in _dill.py to 17 | the builtin function importlib.import_module when present. 18 | 19 | @move_to(_dill) 20 | def _import_module(import_name): 21 | ... # code already in _dill.py 22 | 23 | _import_module = Getattr(importlib, 'import_module', Getattr(_dill, '_import_module', None)) 24 | 25 | The code will attempt to find import_module in the importlib module. If not 26 | present, it will use the _import_module function in _dill. 27 | 28 | Emulate new Python behavior in older Python versions: 29 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 30 | CellType.cell_contents behaves differently in Python 3.6 and 3.7. It is 31 | read-only in Python 3.6 and writable and deletable in 3.7. 32 | 33 | if _dill.OLD37 and _dill.HAS_CTYPES and ...: 34 | @move_to(_dill) 35 | def _setattr(object, name, value): 36 | if type(object) is _dill.CellType and name == 'cell_contents': 37 | _PyCell_Set.argtypes = (ctypes.py_object, ctypes.py_object) 38 | _PyCell_Set(object, value) 39 | else: 40 | setattr(object, name, value) 41 | ... # more cases below 42 | 43 | _setattr = Getattr(_dill, '_setattr', setattr) 44 | 45 | _dill._setattr will be used when present to emulate Python 3.7 functionality in 46 | older versions of Python while defaulting to the standard setattr in 3.7+. 47 | 48 | See this PR for the discussion that lead to this system: 49 | https://github.com/uqfoundation/dill/pull/443 50 | """ 51 | 52 | import inspect 53 | import sys 54 | 55 | _dill = sys.modules['dill._dill'] 56 | 57 | 58 | class Reduce(object): 59 | """ 60 | Reduce objects are wrappers used for compatibility enforcement during 61 | unpickle-time. They should only be used in calls to pickler.save and 62 | other Reduce objects. They are only evaluated within unpickler.load. 63 | 64 | Pickling a Reduce object makes the two implementations equivalent: 65 | 66 | pickler.save(Reduce(*reduction)) 67 | 68 | pickler.save_reduce(*reduction, obj=reduction) 69 | """ 70 | __slots__ = ['reduction'] 71 | def __new__(cls, *reduction, **kwargs): 72 | """ 73 | Args: 74 | *reduction: a tuple that matches the format given here: 75 | https://docs.python.org/3/library/pickle.html#object.__reduce__ 76 | is_callable: a bool to indicate that the object created by 77 | unpickling `reduction` is callable. If true, the current Reduce 78 | is allowed to be used as the function in further save_reduce calls 79 | or Reduce objects. 80 | """ 81 | is_callable = kwargs.get('is_callable', False) # Pleases Py2. Can be removed later 82 | if is_callable: 83 | self = object.__new__(_CallableReduce) 84 | else: 85 | self = object.__new__(Reduce) 86 | self.reduction = reduction 87 | return self 88 | def __repr__(self): 89 | return 'Reduce%s' % (self.reduction,) 90 | def __copy__(self): 91 | return self # pragma: no cover 92 | def __deepcopy__(self, memo): 93 | return self # pragma: no cover 94 | def __reduce__(self): 95 | return self.reduction 96 | def __reduce_ex__(self, protocol): 97 | return self.__reduce__() 98 | 99 | class _CallableReduce(Reduce): 100 | # A version of Reduce for functions. Used to trick pickler.save_reduce into 101 | # thinking that Reduce objects of functions are themselves meaningful functions. 102 | def __call__(self, *args, **kwargs): 103 | reduction = self.__reduce__() 104 | func = reduction[0] 105 | f_args = reduction[1] 106 | obj = func(*f_args) 107 | return obj(*args, **kwargs) 108 | 109 | __NO_DEFAULT = _dill.Sentinel('Getattr.NO_DEFAULT') 110 | 111 | def Getattr(object, name, default=__NO_DEFAULT): 112 | """ 113 | A Reduce object that represents the getattr operation. When unpickled, the 114 | Getattr will access an attribute 'name' of 'object' and return the value 115 | stored there. If the attribute doesn't exist, the default value will be 116 | returned if present. 117 | 118 | The following statements are equivalent: 119 | 120 | Getattr(collections, 'OrderedDict') 121 | Getattr(collections, 'spam', None) 122 | Getattr(*args) 123 | 124 | Reduce(getattr, (collections, 'OrderedDict')) 125 | Reduce(getattr, (collections, 'spam', None)) 126 | Reduce(getattr, args) 127 | 128 | During unpickling, the first two will result in collections.OrderedDict and 129 | None respectively because the first attribute exists and the second one does 130 | not, forcing it to use the default value given in the third argument. 131 | """ 132 | 133 | if default is Getattr.NO_DEFAULT: 134 | reduction = (getattr, (object, name)) 135 | else: 136 | reduction = (getattr, (object, name, default)) 137 | 138 | return Reduce(*reduction, is_callable=callable(default)) 139 | 140 | Getattr.NO_DEFAULT = __NO_DEFAULT 141 | del __NO_DEFAULT 142 | 143 | def move_to(module, name=None): 144 | def decorator(func): 145 | if name is None: 146 | fname = func.__name__ 147 | else: 148 | fname = name 149 | module.__dict__[fname] = func 150 | func.__module__ = module.__name__ 151 | return func 152 | return decorator 153 | 154 | def register_shim(name, default): 155 | """ 156 | A easier to understand and more compact way of "softly" defining a function. 157 | These two pieces of code are equivalent: 158 | 159 | if _dill.OLD3X: 160 | def _create_class(): 161 | ... 162 | _create_class = register_shim('_create_class', types.new_class) 163 | 164 | if _dill.OLD3X: 165 | @move_to(_dill) 166 | def _create_class(): 167 | ... 168 | _create_class = Getattr(_dill, '_create_class', types.new_class) 169 | 170 | Intuitively, it creates a function or object in the versions of dill/python 171 | that require special reimplementations, and use a core library or default 172 | implementation if that function or object does not exist. 173 | """ 174 | func = globals().get(name) 175 | if func is not None: 176 | _dill.__dict__[name] = func 177 | func.__module__ = _dill.__name__ 178 | 179 | if default is Getattr.NO_DEFAULT: 180 | reduction = (getattr, (_dill, name)) 181 | else: 182 | reduction = (getattr, (_dill, name, default)) 183 | 184 | return Reduce(*reduction, is_callable=callable(default)) 185 | 186 | ###################### 187 | ## Compatibility Shims are defined below 188 | ###################### 189 | 190 | _CELL_EMPTY = register_shim('_CELL_EMPTY', None) 191 | 192 | _setattr = register_shim('_setattr', setattr) 193 | _delattr = register_shim('_delattr', delattr) 194 | -------------------------------------------------------------------------------- /dill/detect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | Methods for detecting objects leading to pickling failures. 10 | """ 11 | 12 | import dis 13 | from inspect import ismethod, isfunction, istraceback, isframe, iscode 14 | 15 | from .pointers import parent, reference, at, parents, children 16 | from .logger import trace 17 | 18 | __all__ = ['baditems','badobjects','badtypes','code','errors','freevars', 19 | 'getmodule','globalvars','nestedcode','nestedglobals','outermost', 20 | 'referredglobals','referrednested','trace','varnames'] 21 | 22 | def getmodule(object, _filename=None, force=False): 23 | """get the module of the object""" 24 | from inspect import getmodule as getmod 25 | module = getmod(object, _filename) 26 | if module or not force: return module 27 | import builtins 28 | from .source import getname 29 | name = getname(object, force=True) 30 | return builtins if name in vars(builtins).keys() else None 31 | 32 | def outermost(func): # is analogous to getsource(func,enclosing=True) 33 | """get outermost enclosing object (i.e. the outer function in a closure) 34 | 35 | NOTE: this is the object-equivalent of getsource(func, enclosing=True) 36 | """ 37 | if ismethod(func): 38 | _globals = func.__func__.__globals__ or {} 39 | elif isfunction(func): 40 | _globals = func.__globals__ or {} 41 | else: 42 | return #XXX: or raise? no matches 43 | _globals = _globals.items() 44 | # get the enclosing source 45 | from .source import getsourcelines 46 | try: lines,lnum = getsourcelines(func, enclosing=True) 47 | except Exception: #TypeError, IOError 48 | lines,lnum = [],None 49 | code = ''.join(lines) 50 | # get all possible names,objects that are named in the enclosing source 51 | _locals = ((name,obj) for (name,obj) in _globals if name in code) 52 | # now only save the objects that generate the enclosing block 53 | for name,obj in _locals: #XXX: don't really need 'name' 54 | try: 55 | if getsourcelines(obj) == (lines,lnum): return obj 56 | except Exception: #TypeError, IOError 57 | pass 58 | return #XXX: or raise? no matches 59 | 60 | def nestedcode(func, recurse=True): #XXX: or return dict of {co_name: co} ? 61 | """get the code objects for any nested functions (e.g. in a closure)""" 62 | func = code(func) 63 | if not iscode(func): return [] #XXX: or raise? no matches 64 | nested = set() 65 | for co in func.co_consts: 66 | if co is None: continue 67 | co = code(co) 68 | if co: 69 | nested.add(co) 70 | if recurse: nested |= set(nestedcode(co, recurse=True)) 71 | return list(nested) 72 | 73 | def code(func): 74 | """get the code object for the given function or method 75 | 76 | NOTE: use dill.source.getsource(CODEOBJ) to get the source code 77 | """ 78 | if ismethod(func): func = func.__func__ 79 | if isfunction(func): func = func.__code__ 80 | if istraceback(func): func = func.tb_frame 81 | if isframe(func): func = func.f_code 82 | if iscode(func): return func 83 | return 84 | 85 | #XXX: ugly: parse dis.dis for name after " len(referrednested(func)), try calling func(). 91 | If possible, python builds code objects, but delays building functions 92 | until func() is called. 93 | """ 94 | import gc 95 | funcs = set() 96 | # get the code objects, and try to track down by referrence 97 | for co in nestedcode(func, recurse): 98 | # look for function objects that refer to the code object 99 | for obj in gc.get_referrers(co): 100 | # get methods 101 | _ = getattr(obj, '__func__', None) # ismethod 102 | if getattr(_, '__code__', None) is co: funcs.add(obj) 103 | # get functions 104 | elif getattr(obj, '__code__', None) is co: funcs.add(obj) 105 | # get frame objects 106 | elif getattr(obj, 'f_code', None) is co: funcs.add(obj) 107 | # get code objects 108 | elif hasattr(obj, 'co_code') and obj is co: funcs.add(obj) 109 | # frameobjs => func.__code__.co_varnames not in func.__code__.co_cellvars 110 | # funcobjs => func.__code__.co_cellvars not in func.__code__.co_varnames 111 | # frameobjs are not found, however funcobjs are... 112 | # (see: test_mixins.quad ... and test_mixins.wtf) 113 | # after execution, code objects get compiled, and then may be found by gc 114 | return list(funcs) 115 | 116 | 117 | def freevars(func): 118 | """get objects defined in enclosing code that are referred to by func 119 | 120 | returns a dict of {name:object}""" 121 | if ismethod(func): func = func.__func__ 122 | if isfunction(func): 123 | closures = func.__closure__ or () 124 | func = func.__code__.co_freevars # get freevars 125 | else: 126 | return {} 127 | 128 | def get_cell_contents(): 129 | for name, c in zip(func, closures): 130 | try: 131 | cell_contents = c.cell_contents 132 | except ValueError: # cell is empty 133 | continue 134 | yield name, c.cell_contents 135 | 136 | return dict(get_cell_contents()) 137 | 138 | # thanks to Davies Liu for recursion of globals 139 | def nestedglobals(func, recurse=True): 140 | """get the names of any globals found within func""" 141 | func = code(func) 142 | if func is None: return list() 143 | import sys 144 | from .temp import capture 145 | CAN_NULL = sys.hexversion >= 0x30b00a7 # NULL may be prepended >= 3.11a7 146 | names = set() 147 | with capture('stdout') as out: 148 | try: 149 | dis.dis(func) #XXX: dis.dis(None) disassembles last traceback 150 | except IndexError: 151 | pass #FIXME: HACK for IS_PYPY (3.11) 152 | for line in out.getvalue().splitlines(): 153 | if '_GLOBAL' in line: 154 | name = line.split('(')[-1].split(')')[0] 155 | if CAN_NULL: 156 | names.add(name.replace('NULL + ', '').replace(' + NULL', '')) 157 | else: 158 | names.add(name) 159 | for co in getattr(func, 'co_consts', tuple()): 160 | if co and recurse and iscode(co): 161 | names.update(nestedglobals(co, recurse=True)) 162 | return list(names) 163 | 164 | def referredglobals(func, recurse=True, builtin=False): 165 | """get the names of objects in the global scope referred to by func""" 166 | return globalvars(func, recurse, builtin).keys() 167 | 168 | def globalvars(func, recurse=True, builtin=False): 169 | """get objects defined in global scope that are referred to by func 170 | 171 | return a dict of {name:object}""" 172 | if ismethod(func): func = func.__func__ 173 | if isfunction(func): 174 | globs = vars(getmodule(sum)).copy() if builtin else {} 175 | # get references from within closure 176 | orig_func, func = func, set() 177 | for obj in orig_func.__closure__ or {}: 178 | try: 179 | cell_contents = obj.cell_contents 180 | except ValueError: # cell is empty 181 | pass 182 | else: 183 | _vars = globalvars(cell_contents, recurse, builtin) or {} 184 | func.update(_vars) #XXX: (above) be wary of infinte recursion? 185 | globs.update(_vars) 186 | # get globals 187 | globs.update(orig_func.__globals__ or {}) 188 | # get names of references 189 | if not recurse: 190 | func.update(orig_func.__code__.co_names) 191 | else: 192 | func.update(nestedglobals(orig_func.__code__)) 193 | # find globals for all entries of func 194 | for key in func.copy(): #XXX: unnecessary...? 195 | nested_func = globs.get(key) 196 | if nested_func is orig_func: 197 | #func.remove(key) if key in func else None 198 | continue #XXX: globalvars(func, False)? 199 | func.update(globalvars(nested_func, True, builtin)) 200 | elif iscode(func): 201 | globs = vars(getmodule(sum)).copy() if builtin else {} 202 | #globs.update(globals()) 203 | if not recurse: 204 | func = func.co_names # get names 205 | else: 206 | orig_func = func.co_name # to stop infinite recursion 207 | func = set(nestedglobals(func)) 208 | # find globals for all entries of func 209 | for key in func.copy(): #XXX: unnecessary...? 210 | if key is orig_func: 211 | #func.remove(key) if key in func else None 212 | continue #XXX: globalvars(func, False)? 213 | nested_func = globs.get(key) 214 | func.update(globalvars(nested_func, True, builtin)) 215 | else: 216 | return {} 217 | #NOTE: if name not in __globals__, then we skip it... 218 | return dict((name,globs[name]) for name in func if name in globs) 219 | 220 | 221 | def varnames(func): 222 | """get names of variables defined by func 223 | 224 | returns a tuple (local vars, local vars referrenced by nested functions)""" 225 | func = code(func) 226 | if not iscode(func): 227 | return () #XXX: better ((),())? or None? 228 | return func.co_varnames, func.co_cellvars 229 | 230 | 231 | def baditems(obj, exact=False, safe=False): #XXX: obj=globals() ? 232 | """get items in object that fail to pickle""" 233 | if not hasattr(obj,'__iter__'): # is not iterable 234 | return [j for j in (badobjects(obj,0,exact,safe),) if j is not None] 235 | obj = obj.values() if getattr(obj,'values',None) else obj 236 | _obj = [] # can't use a set, as items may be unhashable 237 | [_obj.append(badobjects(i,0,exact,safe)) for i in obj if i not in _obj] 238 | return [j for j in _obj if j is not None] 239 | 240 | 241 | def badobjects(obj, depth=0, exact=False, safe=False): 242 | """get objects that fail to pickle""" 243 | from dill import pickles 244 | if not depth: 245 | if pickles(obj,exact,safe): return None 246 | return obj 247 | return dict(((attr, badobjects(getattr(obj,attr),depth-1,exact,safe)) \ 248 | for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe))) 249 | 250 | def badtypes(obj, depth=0, exact=False, safe=False): 251 | """get types for objects that fail to pickle""" 252 | from dill import pickles 253 | if not depth: 254 | if pickles(obj,exact,safe): return None 255 | return type(obj) 256 | return dict(((attr, badtypes(getattr(obj,attr),depth-1,exact,safe)) \ 257 | for attr in dir(obj) if not pickles(getattr(obj,attr),exact,safe))) 258 | 259 | def errors(obj, depth=0, exact=False, safe=False): 260 | """get errors for objects that fail to pickle""" 261 | from dill import pickles, copy 262 | if not depth: 263 | try: 264 | pik = copy(obj) 265 | if exact: 266 | assert pik == obj, \ 267 | "Unpickling produces %s instead of %s" % (pik,obj) 268 | assert type(pik) == type(obj), \ 269 | "Unpickling produces %s instead of %s" % (type(pik),type(obj)) 270 | return None 271 | except Exception: 272 | import sys 273 | return sys.exc_info()[1] 274 | _dict = {} 275 | for attr in dir(obj): 276 | try: 277 | _attr = getattr(obj,attr) 278 | except Exception: 279 | import sys 280 | _dict[attr] = sys.exc_info()[1] 281 | continue 282 | if not pickles(_attr,exact,safe): 283 | _dict[attr] = errors(_attr,depth-1,exact,safe) 284 | return _dict 285 | 286 | 287 | # EOF 288 | -------------------------------------------------------------------------------- /dill/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Author: Leonardo Gama (@leogama) 5 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | Logging utilities for dill. 10 | 11 | The 'logger' object is dill's top-level logger. 12 | 13 | The 'adapter' object wraps the logger and implements a 'trace()' method that 14 | generates a detailed tree-style trace for the pickling call at log level INFO. 15 | 16 | The 'trace()' function sets and resets dill's logger log level, enabling and 17 | disabling the pickling trace. 18 | 19 | The trace shows a tree structure depicting the depth of each object serialized 20 | *with dill save functions*, but not the ones that use save functions from 21 | 'pickle._Pickler.dispatch'. If the information is available, it also displays 22 | the size in bytes that the object contributed to the pickle stream (including 23 | its child objects). Sample trace output: 24 | 25 | >>> import dill, dill.tests 26 | >>> dill.detect.trace(True) 27 | >>> dill.dump_session(main=dill.tests) 28 | ┬ M1: 29 | ├┬ F2: 30 | │└ # F2 [32 B] 31 | ├┬ D2: 32 | │├┬ T4: 33 | ││└ # T4 [35 B] 34 | │├┬ D2: 35 | ││├┬ T4: 36 | │││└ # T4 [50 B] 37 | ││├┬ D2: 38 | │││└ # D2 [84 B] 39 | ││└ # D2 [413 B] 40 | │└ # D2 [763 B] 41 | └ # M1 [813 B] 42 | """ 43 | 44 | __all__ = ['adapter', 'logger', 'trace'] 45 | 46 | import codecs 47 | import contextlib 48 | import locale 49 | import logging 50 | import math 51 | import os 52 | from functools import partial 53 | from typing import TextIO, Union 54 | 55 | import dill 56 | 57 | # Tree drawing characters: Unicode to ASCII map. 58 | ASCII_MAP = str.maketrans({"│": "|", "├": "|", "┬": "+", "└": "`"}) 59 | 60 | ## Notes about the design choices ## 61 | 62 | # Here is some domumentation of the Standard Library's logging internals that 63 | # can't be found completely in the official documentation. dill's logger is 64 | # obtained by calling logging.getLogger('dill') and therefore is an instance of 65 | # logging.getLoggerClass() at the call time. As this is controlled by the user, 66 | # in order to add some functionality to it it's necessary to use a LoggerAdapter 67 | # to wrap it, overriding some of the adapter's methods and creating new ones. 68 | # 69 | # Basic calling sequence 70 | # ====================== 71 | # 72 | # Python's logging functionality can be conceptually divided into five steps: 73 | # 0. Check logging level -> abort if call level is greater than logger level 74 | # 1. Gather information -> construct a LogRecord from passed arguments and context 75 | # 2. Filter (optional) -> discard message if the record matches a filter 76 | # 3. Format -> format message with args, then format output string with message plus record 77 | # 4. Handle -> write the formatted string to output as defined in the handler 78 | # 79 | # dill.logging.logger.log -> # or logger.info, etc. 80 | # Logger.log -> \ 81 | # Logger._log -> }- accept 'extra' parameter for custom record entries 82 | # Logger.makeRecord -> / 83 | # LogRecord.__init__ 84 | # Logger.handle -> 85 | # Logger.callHandlers -> 86 | # Handler.handle -> 87 | # Filterer.filter -> 88 | # Filter.filter 89 | # StreamHandler.emit -> 90 | # Handler.format -> 91 | # Formatter.format -> 92 | # LogRecord.getMessage # does: record.message = msg % args 93 | # Formatter.formatMessage -> 94 | # PercentStyle.format # does: self._fmt % vars(record) 95 | # 96 | # NOTE: All methods from the second line on are from logging.__init__.py 97 | 98 | class TraceAdapter(logging.LoggerAdapter): 99 | """ 100 | Tracks object tree depth and calculates pickled object size. 101 | 102 | A single instance of this wraps the module's logger, as the logging API 103 | doesn't allow setting it directly with a custom Logger subclass. The added 104 | 'trace()' method receives a pickle instance as the first argument and 105 | creates extra values to be added in the LogRecord from it, then calls 106 | 'info()'. 107 | 108 | Usage of logger with 'trace()' method: 109 | 110 | >>> from dill.logger import adapter as logger #NOTE: not dill.logger.logger 111 | >>> ... 112 | >>> def save_atype(pickler, obj): 113 | >>> logger.trace(pickler, "Message with %s and %r etc. placeholders", 'text', obj) 114 | >>> ... 115 | """ 116 | def __init__(self, logger): 117 | self.logger = logger 118 | def addHandler(self, handler): 119 | formatter = TraceFormatter("%(prefix)s%(message)s%(suffix)s", handler=handler) 120 | handler.setFormatter(formatter) 121 | self.logger.addHandler(handler) 122 | def removeHandler(self, handler): 123 | self.logger.removeHandler(handler) 124 | def process(self, msg, kwargs): 125 | # A no-op override, as we don't have self.extra. 126 | return msg, kwargs 127 | def trace_setup(self, pickler): 128 | # Called by Pickler.dump(). 129 | if not dill._dill.is_dill(pickler, child=False): 130 | return 131 | if self.isEnabledFor(logging.INFO): 132 | pickler._trace_depth = 1 133 | pickler._size_stack = [] 134 | else: 135 | pickler._trace_depth = None 136 | def trace(self, pickler, msg, *args, **kwargs): 137 | if not hasattr(pickler, '_trace_depth'): 138 | logger.info(msg, *args, **kwargs) 139 | return 140 | if pickler._trace_depth is None: 141 | return 142 | extra = kwargs.get('extra', {}) 143 | pushed_obj = msg.startswith('#') 144 | size = None 145 | try: 146 | # Streams are not required to be tellable. 147 | size = pickler._file.tell() 148 | frame = pickler.framer.current_frame 149 | try: 150 | size += frame.tell() 151 | except AttributeError: 152 | # PyPy may use a BytesBuilder as frame 153 | size += len(frame) 154 | except (AttributeError, TypeError): 155 | pass 156 | if size is not None: 157 | if not pushed_obj: 158 | pickler._size_stack.append(size) 159 | else: 160 | size -= pickler._size_stack.pop() 161 | extra['size'] = size 162 | if pushed_obj: 163 | pickler._trace_depth -= 1 164 | extra['depth'] = pickler._trace_depth 165 | kwargs['extra'] = extra 166 | self.info(msg, *args, **kwargs) 167 | if not pushed_obj: 168 | pickler._trace_depth += 1 169 | 170 | class TraceFormatter(logging.Formatter): 171 | """ 172 | Generates message prefix and suffix from record. 173 | 174 | This Formatter adds prefix and suffix strings to the log message in trace 175 | mode (an also provides empty string defaults for normal logs). 176 | """ 177 | def __init__(self, *args, handler=None, **kwargs): 178 | super().__init__(*args, **kwargs) 179 | try: 180 | encoding = handler.stream.encoding 181 | if encoding is None: 182 | raise AttributeError 183 | except AttributeError: 184 | encoding = locale.getpreferredencoding() 185 | try: 186 | encoding = codecs.lookup(encoding).name 187 | except LookupError: 188 | self.is_utf8 = False 189 | else: 190 | self.is_utf8 = (encoding == codecs.lookup('utf-8').name) 191 | def format(self, record): 192 | fields = {'prefix': "", 'suffix': ""} 193 | if getattr(record, 'depth', 0) > 0: 194 | if record.msg.startswith("#"): 195 | prefix = (record.depth - 1)*"│" + "└" 196 | elif record.depth == 1: 197 | prefix = "┬" 198 | else: 199 | prefix = (record.depth - 2)*"│" + "├┬" 200 | if not self.is_utf8: 201 | prefix = prefix.translate(ASCII_MAP) + "-" 202 | fields['prefix'] = prefix + " " 203 | if hasattr(record, 'size') and record.size is not None and record.size >= 1: 204 | # Show object size in human-readable form. 205 | power = int(math.log(record.size, 2)) // 10 206 | size = record.size >> power*10 207 | fields['suffix'] = " [%d %sB]" % (size, "KMGTP"[power] + "i" if power else "") 208 | vars(record).update(fields) 209 | return super().format(record) 210 | 211 | logger = logging.getLogger('dill') 212 | logger.propagate = False 213 | adapter = TraceAdapter(logger) 214 | stderr_handler = logging._StderrHandler() 215 | adapter.addHandler(stderr_handler) 216 | 217 | def trace(arg: Union[bool, TextIO, str, os.PathLike] = None, *, mode: str = 'a') -> None: 218 | """print a trace through the stack when pickling; useful for debugging 219 | 220 | With a single boolean argument, enable or disable the tracing. 221 | 222 | Example usage: 223 | 224 | >>> import dill 225 | >>> dill.detect.trace(True) 226 | >>> dill.dump_session() 227 | 228 | Alternatively, ``trace()`` can be used as a context manager. With no 229 | arguments, it just takes care of restoring the tracing state on exit. 230 | Either a file handle, or a file name and (optionally) a file mode may be 231 | specitfied to redirect the tracing output in the ``with`` block context. A 232 | log function is yielded by the manager so the user can write extra 233 | information to the file. 234 | 235 | Example usage: 236 | 237 | >>> from dill import detect 238 | >>> D = {'a': 42, 'b': {'x': None}} 239 | >>> with detect.trace(): 240 | >>> dumps(D) 241 | ┬ D2: 242 | ├┬ D2: 243 | │└ # D2 [8 B] 244 | └ # D2 [22 B] 245 | >>> squared = lambda x: x**2 246 | >>> with detect.trace('output.txt', mode='w') as log: 247 | >>> log("> D = %r", D) 248 | >>> dumps(D) 249 | >>> log("> squared = %r", squared) 250 | >>> dumps(squared) 251 | 252 | Arguments: 253 | arg: a boolean value, or an optional file-like or path-like object for the context manager 254 | mode: mode string for ``open()`` if a file name is passed as the first argument 255 | """ 256 | if repr(arg) not in ('False', 'True'): 257 | return TraceManager(file=arg, mode=mode) 258 | logger.setLevel(logging.INFO if arg else logging.WARNING) 259 | 260 | class TraceManager(contextlib.AbstractContextManager): 261 | """context manager version of trace(); can redirect the trace to a file""" 262 | def __init__(self, file, mode): 263 | self.file = file 264 | self.mode = mode 265 | self.redirect = file is not None 266 | self.file_is_stream = hasattr(file, 'write') 267 | def __enter__(self): 268 | if self.redirect: 269 | stderr_handler.flush() 270 | if self.file_is_stream: 271 | self.handler = logging.StreamHandler(self.file) 272 | else: 273 | self.handler = logging.FileHandler(self.file, self.mode) 274 | adapter.removeHandler(stderr_handler) 275 | adapter.addHandler(self.handler) 276 | self.old_level = adapter.getEffectiveLevel() 277 | adapter.setLevel(logging.INFO) 278 | return adapter.info 279 | def __exit__(self, *exc_info): 280 | adapter.setLevel(self.old_level) 281 | if self.redirect: 282 | adapter.removeHandler(self.handler) 283 | adapter.addHandler(stderr_handler) 284 | if not self.file_is_stream: 285 | self.handler.close() 286 | -------------------------------------------------------------------------------- /dill/objtypes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | all Python Standard Library object types (currently: CH 1-15 @ 2.7) 10 | and some other common object types (i.e. numpy.ndarray) 11 | 12 | to load more objects and types, use dill.load_types() 13 | """ 14 | 15 | # non-local import of dill.objects 16 | from dill import objects 17 | for _type in objects.keys(): 18 | exec("%s = type(objects['%s'])" % (_type,_type)) 19 | 20 | del objects 21 | try: 22 | del _type 23 | except NameError: 24 | pass 25 | -------------------------------------------------------------------------------- /dill/pointers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | __all__ = ['parent', 'reference', 'at', 'parents', 'children'] 10 | 11 | import gc 12 | import sys 13 | 14 | from ._dill import _proxy_helper as reference 15 | from ._dill import _locate_object as at 16 | 17 | def parent(obj, objtype, ignore=()): 18 | """ 19 | >>> listiter = iter([4,5,6,7]) 20 | >>> obj = parent(listiter, list) 21 | >>> obj == [4,5,6,7] # actually 'is', but don't have handle any longer 22 | True 23 | 24 | NOTE: objtype can be a single type (e.g. int or list) or a tuple of types. 25 | 26 | WARNING: if obj is a sequence (e.g. list), may produce unexpected results. 27 | Parent finds *one* parent (e.g. the last member of the sequence). 28 | """ 29 | depth = 1 #XXX: always looking for the parent (only, right?) 30 | chain = parents(obj, objtype, depth, ignore) 31 | parent = chain.pop() 32 | if parent is obj: 33 | return None 34 | return parent 35 | 36 | 37 | def parents(obj, objtype, depth=1, ignore=()): #XXX: objtype=object ? 38 | """Find the chain of referents for obj. Chain will end with obj. 39 | 40 | objtype: an object type or tuple of types to search for 41 | depth: search depth (e.g. depth=2 is 'grandparents') 42 | ignore: an object or tuple of objects to ignore in the search 43 | """ 44 | edge_func = gc.get_referents # looking for refs, not back_refs 45 | predicate = lambda x: isinstance(x, objtype) # looking for parent type 46 | #if objtype is None: predicate = lambda x: True #XXX: in obj.mro() ? 47 | ignore = (ignore,) if not hasattr(ignore, '__len__') else ignore 48 | ignore = (id(obj) for obj in ignore) 49 | chain = find_chain(obj, predicate, edge_func, depth)[::-1] 50 | #XXX: should pop off obj... ? 51 | return chain 52 | 53 | 54 | def children(obj, objtype, depth=1, ignore=()): #XXX: objtype=object ? 55 | """Find the chain of referrers for obj. Chain will start with obj. 56 | 57 | objtype: an object type or tuple of types to search for 58 | depth: search depth (e.g. depth=2 is 'grandchildren') 59 | ignore: an object or tuple of objects to ignore in the search 60 | 61 | NOTE: a common thing to ignore is all globals, 'ignore=(globals(),)' 62 | 63 | NOTE: repeated calls may yield different results, as python stores 64 | the last value in the special variable '_'; thus, it is often good 65 | to execute something to replace '_' (e.g. >>> 1+1). 66 | """ 67 | edge_func = gc.get_referrers # looking for back_refs, not refs 68 | predicate = lambda x: isinstance(x, objtype) # looking for child type 69 | #if objtype is None: predicate = lambda x: True #XXX: in obj.mro() ? 70 | ignore = (ignore,) if not hasattr(ignore, '__len__') else ignore 71 | ignore = (id(obj) for obj in ignore) 72 | chain = find_chain(obj, predicate, edge_func, depth, ignore) 73 | #XXX: should pop off obj... ? 74 | return chain 75 | 76 | 77 | # more generic helper function (cut-n-paste from objgraph) 78 | # Source at http://mg.pov.lt/objgraph/ 79 | # Copyright (c) 2008-2010 Marius Gedminas 80 | # Copyright (c) 2010 Stefano Rivera 81 | # Released under the MIT licence (see objgraph/objgrah.py) 82 | 83 | def find_chain(obj, predicate, edge_func, max_depth=20, extra_ignore=()): 84 | queue = [obj] 85 | depth = {id(obj): 0} 86 | parent = {id(obj): None} 87 | ignore = set(extra_ignore) 88 | ignore.add(id(extra_ignore)) 89 | ignore.add(id(queue)) 90 | ignore.add(id(depth)) 91 | ignore.add(id(parent)) 92 | ignore.add(id(ignore)) 93 | ignore.add(id(sys._getframe())) # this function 94 | ignore.add(id(sys._getframe(1))) # find_chain/find_backref_chain, likely 95 | gc.collect() 96 | while queue: 97 | target = queue.pop(0) 98 | if predicate(target): 99 | chain = [target] 100 | while parent[id(target)] is not None: 101 | target = parent[id(target)] 102 | chain.append(target) 103 | return chain 104 | tdepth = depth[id(target)] 105 | if tdepth < max_depth: 106 | referrers = edge_func(target) 107 | ignore.add(id(referrers)) 108 | for source in referrers: 109 | if id(source) in ignore: 110 | continue 111 | if id(source) not in depth: 112 | depth[id(source)] = tdepth + 1 113 | parent[id(source)] = target 114 | queue.append(source) 115 | return [obj] # not found 116 | 117 | 118 | # backward compatibility 119 | refobject = at 120 | 121 | 122 | # EOF 123 | -------------------------------------------------------------------------------- /dill/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | global settings for Pickler 10 | """ 11 | 12 | from pickle import DEFAULT_PROTOCOL 13 | 14 | settings = { 15 | #'main' : None, 16 | 'protocol' : DEFAULT_PROTOCOL, 17 | 'byref' : False, 18 | #'strictio' : False, 19 | 'fmode' : 0, #HANDLE_FMODE 20 | 'recurse' : False, 21 | 'ignore' : False, 22 | } 23 | 24 | del DEFAULT_PROTOCOL 25 | 26 | -------------------------------------------------------------------------------- /dill/temp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | Methods for serialized objects (or source code) stored in temporary files 10 | and file-like objects. 11 | """ 12 | #XXX: better instead to have functions write to any given file-like object ? 13 | #XXX: currently, all file-like objects are created by the function... 14 | 15 | __all__ = ['dump_source', 'dump', 'dumpIO_source', 'dumpIO',\ 16 | 'load_source', 'load', 'loadIO_source', 'loadIO',\ 17 | 'capture'] 18 | 19 | import contextlib 20 | 21 | 22 | @contextlib.contextmanager 23 | def capture(stream='stdout'): 24 | """builds a context that temporarily replaces the given stream name 25 | 26 | >>> with capture('stdout') as out: 27 | ... print ("foo!") 28 | ... 29 | >>> print (out.getvalue()) 30 | foo! 31 | 32 | """ 33 | import sys 34 | from io import StringIO 35 | orig = getattr(sys, stream) 36 | setattr(sys, stream, StringIO()) 37 | try: 38 | yield getattr(sys, stream) 39 | finally: 40 | setattr(sys, stream, orig) 41 | 42 | 43 | def b(x): # deal with b'foo' versus 'foo' 44 | import codecs 45 | return codecs.latin_1_encode(x)[0] 46 | 47 | def load_source(file, **kwds): 48 | """load an object that was stored with dill.temp.dump_source 49 | 50 | file: filehandle 51 | alias: string name of stored object 52 | mode: mode to open the file, one of: {'r', 'rb'} 53 | 54 | >>> f = lambda x: x**2 55 | >>> pyfile = dill.temp.dump_source(f, alias='_f') 56 | >>> _f = dill.temp.load_source(pyfile) 57 | >>> _f(4) 58 | 16 59 | """ 60 | alias = kwds.pop('alias', None) 61 | mode = kwds.pop('mode', 'r') 62 | fname = getattr(file, 'name', file) # fname=file.name or fname=file (if str) 63 | source = open(fname, mode=mode, **kwds).read() 64 | if not alias: 65 | tag = source.strip().splitlines()[-1].split() 66 | if tag[0] != '#NAME:': 67 | stub = source.splitlines()[0] 68 | raise IOError("unknown name for code: %s" % stub) 69 | alias = tag[-1] 70 | local = {} 71 | exec(source, local) 72 | _ = eval("%s" % alias, local) 73 | return _ 74 | 75 | def dump_source(object, **kwds): 76 | """write object source to a NamedTemporaryFile (instead of dill.dump) 77 | Loads with "import" or "dill.temp.load_source". Returns the filehandle. 78 | 79 | >>> f = lambda x: x**2 80 | >>> pyfile = dill.temp.dump_source(f, alias='_f') 81 | >>> _f = dill.temp.load_source(pyfile) 82 | >>> _f(4) 83 | 16 84 | 85 | >>> f = lambda x: x**2 86 | >>> pyfile = dill.temp.dump_source(f, dir='.') 87 | >>> modulename = os.path.basename(pyfile.name).split('.py')[0] 88 | >>> exec('from %s import f as _f' % modulename) 89 | >>> _f(4) 90 | 16 91 | 92 | Optional kwds: 93 | If 'alias' is specified, the object will be renamed to the given string. 94 | 95 | If 'prefix' is specified, the file name will begin with that prefix, 96 | otherwise a default prefix is used. 97 | 98 | If 'dir' is specified, the file will be created in that directory, 99 | otherwise a default directory is used. 100 | 101 | If 'text' is specified and true, the file is opened in text 102 | mode. Else (the default) the file is opened in binary mode. On 103 | some operating systems, this makes no difference. 104 | 105 | NOTE: Keep the return value for as long as you want your file to exist ! 106 | """ #XXX: write a "load_source"? 107 | from .source import importable, getname 108 | import tempfile 109 | kwds.setdefault('delete', True) 110 | kwds.pop('suffix', '') # this is *always* '.py' 111 | alias = kwds.pop('alias', '') #XXX: include an alias so a name is known 112 | name = str(alias) or getname(object) 113 | name = "\n#NAME: %s\n" % name 114 | #XXX: assumes kwds['dir'] is writable and on $PYTHONPATH 115 | file = tempfile.NamedTemporaryFile(suffix='.py', **kwds) 116 | file.write(b(''.join([importable(object, alias=alias),name]))) 117 | file.flush() 118 | return file 119 | 120 | def load(file, **kwds): 121 | """load an object that was stored with dill.temp.dump 122 | 123 | file: filehandle 124 | mode: mode to open the file, one of: {'r', 'rb'} 125 | 126 | >>> dumpfile = dill.temp.dump([1, 2, 3, 4, 5]) 127 | >>> dill.temp.load(dumpfile) 128 | [1, 2, 3, 4, 5] 129 | """ 130 | import dill as pickle 131 | mode = kwds.pop('mode', 'rb') 132 | name = getattr(file, 'name', file) # name=file.name or name=file (if str) 133 | return pickle.load(open(name, mode=mode, **kwds)) 134 | 135 | def dump(object, **kwds): 136 | """dill.dump of object to a NamedTemporaryFile. 137 | Loads with "dill.temp.load". Returns the filehandle. 138 | 139 | >>> dumpfile = dill.temp.dump([1, 2, 3, 4, 5]) 140 | >>> dill.temp.load(dumpfile) 141 | [1, 2, 3, 4, 5] 142 | 143 | Optional kwds: 144 | If 'suffix' is specified, the file name will end with that suffix, 145 | otherwise there will be no suffix. 146 | 147 | If 'prefix' is specified, the file name will begin with that prefix, 148 | otherwise a default prefix is used. 149 | 150 | If 'dir' is specified, the file will be created in that directory, 151 | otherwise a default directory is used. 152 | 153 | If 'text' is specified and true, the file is opened in text 154 | mode. Else (the default) the file is opened in binary mode. On 155 | some operating systems, this makes no difference. 156 | 157 | NOTE: Keep the return value for as long as you want your file to exist ! 158 | """ 159 | import dill as pickle 160 | import tempfile 161 | kwds.setdefault('delete', True) 162 | file = tempfile.NamedTemporaryFile(**kwds) 163 | pickle.dump(object, file) 164 | file.flush() 165 | return file 166 | 167 | def loadIO(buffer, **kwds): 168 | """load an object that was stored with dill.temp.dumpIO 169 | 170 | buffer: buffer object 171 | 172 | >>> dumpfile = dill.temp.dumpIO([1, 2, 3, 4, 5]) 173 | >>> dill.temp.loadIO(dumpfile) 174 | [1, 2, 3, 4, 5] 175 | """ 176 | import dill as pickle 177 | from io import BytesIO as StringIO 178 | value = getattr(buffer, 'getvalue', buffer) # value or buffer.getvalue 179 | if value != buffer: value = value() # buffer.getvalue() 180 | return pickle.load(StringIO(value)) 181 | 182 | def dumpIO(object, **kwds): 183 | """dill.dump of object to a buffer. 184 | Loads with "dill.temp.loadIO". Returns the buffer object. 185 | 186 | >>> dumpfile = dill.temp.dumpIO([1, 2, 3, 4, 5]) 187 | >>> dill.temp.loadIO(dumpfile) 188 | [1, 2, 3, 4, 5] 189 | """ 190 | import dill as pickle 191 | from io import BytesIO as StringIO 192 | file = StringIO() 193 | pickle.dump(object, file) 194 | file.flush() 195 | return file 196 | 197 | def loadIO_source(buffer, **kwds): 198 | """load an object that was stored with dill.temp.dumpIO_source 199 | 200 | buffer: buffer object 201 | alias: string name of stored object 202 | 203 | >>> f = lambda x:x**2 204 | >>> pyfile = dill.temp.dumpIO_source(f, alias='_f') 205 | >>> _f = dill.temp.loadIO_source(pyfile) 206 | >>> _f(4) 207 | 16 208 | """ 209 | alias = kwds.pop('alias', None) 210 | source = getattr(buffer, 'getvalue', buffer) # source or buffer.getvalue 211 | if source != buffer: source = source() # buffer.getvalue() 212 | source = source.decode() # buffer to string 213 | if not alias: 214 | tag = source.strip().splitlines()[-1].split() 215 | if tag[0] != '#NAME:': 216 | stub = source.splitlines()[0] 217 | raise IOError("unknown name for code: %s" % stub) 218 | alias = tag[-1] 219 | local = {} 220 | exec(source, local) 221 | _ = eval("%s" % alias, local) 222 | return _ 223 | 224 | def dumpIO_source(object, **kwds): 225 | """write object source to a buffer (instead of dill.dump) 226 | Loads by with dill.temp.loadIO_source. Returns the buffer object. 227 | 228 | >>> f = lambda x:x**2 229 | >>> pyfile = dill.temp.dumpIO_source(f, alias='_f') 230 | >>> _f = dill.temp.loadIO_source(pyfile) 231 | >>> _f(4) 232 | 16 233 | 234 | Optional kwds: 235 | If 'alias' is specified, the object will be renamed to the given string. 236 | """ 237 | from .source import importable, getname 238 | from io import BytesIO as StringIO 239 | alias = kwds.pop('alias', '') #XXX: include an alias so a name is known 240 | name = str(alias) or getname(object) 241 | name = "\n#NAME: %s\n" % name 242 | #XXX: assumes kwds['dir'] is writable and on $PYTHONPATH 243 | file = StringIO() 244 | file.write(b(''.join([importable(object, alias=alias),name]))) 245 | file.flush() 246 | return file 247 | 248 | 249 | del contextlib 250 | 251 | 252 | # EOF 253 | -------------------------------------------------------------------------------- /dill/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2018-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | """ 8 | to run this test suite, first build and install `dill`. 9 | 10 | $ python -m pip install ../.. 11 | 12 | 13 | then run the tests with: 14 | 15 | $ python -m dill.tests 16 | 17 | 18 | or, if `nose` is installed: 19 | 20 | $ nosetests 21 | 22 | """ 23 | -------------------------------------------------------------------------------- /dill/tests/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2018-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import glob 9 | import os 10 | import sys 11 | import subprocess as sp 12 | python = sys.executable 13 | try: 14 | import pox 15 | python = pox.which_python(version=True) or python 16 | except ImportError: 17 | pass 18 | shell = sys.platform[:3] == 'win' 19 | 20 | suite = os.path.dirname(__file__) or os.path.curdir 21 | tests = glob.glob(suite + os.path.sep + 'test_*.py') 22 | 23 | 24 | if __name__ == '__main__': 25 | 26 | failed = 0 27 | for test in tests: 28 | p = sp.Popen([python, test], shell=shell).wait() 29 | if p: 30 | print('F', end='', flush=True) 31 | failed = 1 32 | else: 33 | print('.', end='', flush=True) 34 | print('') 35 | exit(failed) 36 | -------------------------------------------------------------------------------- /dill/tests/test_abc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2023-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | """ 8 | test dill's ability to pickle abstract base class objects 9 | """ 10 | import dill 11 | import abc 12 | from abc import ABC 13 | import warnings 14 | 15 | from types import FunctionType 16 | 17 | dill.settings['recurse'] = True 18 | 19 | class OneTwoThree(ABC): 20 | @abc.abstractmethod 21 | def foo(self): 22 | """A method""" 23 | pass 24 | 25 | @property 26 | @abc.abstractmethod 27 | def bar(self): 28 | """Property getter""" 29 | pass 30 | 31 | @bar.setter 32 | @abc.abstractmethod 33 | def bar(self, value): 34 | """Property setter""" 35 | pass 36 | 37 | @classmethod 38 | @abc.abstractmethod 39 | def cfoo(cls): 40 | """Class method""" 41 | pass 42 | 43 | @staticmethod 44 | @abc.abstractmethod 45 | def sfoo(): 46 | """Static method""" 47 | pass 48 | 49 | class EasyAsAbc(OneTwoThree): 50 | def __init__(self): 51 | self._bar = None 52 | 53 | def foo(self): 54 | return "Instance Method FOO" 55 | 56 | @property 57 | def bar(self): 58 | return self._bar 59 | 60 | @bar.setter 61 | def bar(self, value): 62 | self._bar = value 63 | 64 | @classmethod 65 | def cfoo(cls): 66 | return "Class Method CFOO" 67 | 68 | @staticmethod 69 | def sfoo(): 70 | return "Static Method SFOO" 71 | 72 | def test_abc_non_local(): 73 | assert dill.copy(OneTwoThree) is not OneTwoThree 74 | assert dill.copy(EasyAsAbc) is not EasyAsAbc 75 | 76 | with warnings.catch_warnings(): 77 | warnings.simplefilter("ignore", dill.PicklingWarning) 78 | assert dill.copy(OneTwoThree, byref=True) is OneTwoThree 79 | assert dill.copy(EasyAsAbc, byref=True) is EasyAsAbc 80 | 81 | instance = EasyAsAbc() 82 | # Set a property that StockPickle can't preserve 83 | instance.bar = lambda x: x**2 84 | depickled = dill.copy(instance) 85 | assert type(depickled) is type(instance) #NOTE: issue #612, test_abc_local 86 | #NOTE: dill.copy of local (or non-local) classes should (not) be the same? 87 | assert type(depickled.bar) is FunctionType 88 | assert depickled.bar(3) == 9 89 | assert depickled.sfoo() == "Static Method SFOO" 90 | assert depickled.cfoo() == "Class Method CFOO" 91 | assert depickled.foo() == "Instance Method FOO" 92 | 93 | def test_abc_local(): 94 | """ 95 | Test using locally scoped ABC class 96 | """ 97 | class LocalABC(ABC): 98 | @abc.abstractmethod 99 | def foo(self): 100 | pass 101 | 102 | def baz(self): 103 | return repr(self) 104 | 105 | labc = dill.copy(LocalABC) 106 | assert labc is not LocalABC 107 | assert type(labc) is type(LocalABC) 108 | #NOTE: dill.copy of local (or non-local) classes should (not) be the same? 109 | # 110 | # .LocalABC'> 111 | 112 | class Real(labc): 113 | def foo(self): 114 | return "True!" 115 | 116 | def baz(self): 117 | return "My " + super(Real, self).baz() 118 | 119 | real = Real() 120 | assert real.foo() == "True!" 121 | 122 | try: 123 | labc() 124 | except TypeError as e: 125 | # Expected error 126 | pass 127 | else: 128 | print('Failed to raise type error') 129 | assert False 130 | 131 | labc2, pik = dill.copy((labc, Real())) 132 | assert 'Real' == type(pik).__name__ 133 | assert '.Real' in type(pik).__qualname__ 134 | assert type(pik) is not Real 135 | assert labc2 is not LocalABC 136 | assert labc2 is not labc 137 | assert isinstance(pik, labc2) 138 | assert not isinstance(pik, labc) 139 | assert not isinstance(pik, LocalABC) 140 | assert pik.baz() == "My " + repr(pik) 141 | 142 | def test_meta_local_no_cache(): 143 | """ 144 | Test calling metaclass and cache registration 145 | """ 146 | LocalMetaABC = abc.ABCMeta('LocalMetaABC', (), {}) 147 | 148 | class ClassyClass: 149 | pass 150 | 151 | class KlassyClass: 152 | pass 153 | 154 | LocalMetaABC.register(ClassyClass) 155 | 156 | assert not issubclass(KlassyClass, LocalMetaABC) 157 | assert issubclass(ClassyClass, LocalMetaABC) 158 | 159 | res = dill.dumps((LocalMetaABC, ClassyClass, KlassyClass)) 160 | 161 | lmabc, cc, kc = dill.loads(res) 162 | assert type(lmabc) == type(LocalMetaABC) 163 | assert not issubclass(kc, lmabc) 164 | assert issubclass(cc, lmabc) 165 | 166 | if __name__ == '__main__': 167 | test_abc_non_local() 168 | test_abc_local() 169 | test_meta_local_no_cache() 170 | -------------------------------------------------------------------------------- /dill/tests/test_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | from dill import check 10 | import sys 11 | 12 | from dill.temp import capture 13 | 14 | 15 | #FIXME: this doesn't catch output... it's from the internal call 16 | def raise_check(func, **kwds): 17 | try: 18 | with capture('stdout') as out: 19 | check(func, **kwds) 20 | except Exception: 21 | e = sys.exc_info()[1] 22 | raise AssertionError(str(e)) 23 | else: 24 | assert 'Traceback' not in out.getvalue() 25 | finally: 26 | out.close() 27 | 28 | 29 | f = lambda x:x**2 30 | 31 | 32 | def test_simple(verbose=None): 33 | raise_check(f, verbose=verbose) 34 | 35 | 36 | def test_recurse(verbose=None): 37 | raise_check(f, recurse=True, verbose=verbose) 38 | 39 | 40 | def test_byref(verbose=None): 41 | raise_check(f, byref=True, verbose=verbose) 42 | 43 | 44 | def test_protocol(verbose=None): 45 | raise_check(f, protocol=True, verbose=verbose) 46 | 47 | 48 | def test_python(verbose=None): 49 | raise_check(f, python=None, verbose=verbose) 50 | 51 | 52 | #TODO: test incompatible versions 53 | #TODO: test dump failure 54 | #TODO: test load failure 55 | 56 | 57 | if __name__ == '__main__': 58 | test_simple() 59 | test_recurse() 60 | test_byref() 61 | test_protocol() 62 | test_python() 63 | -------------------------------------------------------------------------------- /dill/tests/test_classdef.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | from enum import EnumMeta 11 | import sys 12 | dill.settings['recurse'] = True 13 | 14 | # test classdefs 15 | class _class: 16 | def _method(self): 17 | pass 18 | def ok(self): 19 | return True 20 | 21 | class _class2: 22 | def __call__(self): 23 | pass 24 | def ok(self): 25 | return True 26 | 27 | class _newclass(object): 28 | def _method(self): 29 | pass 30 | def ok(self): 31 | return True 32 | 33 | class _newclass2(object): 34 | def __call__(self): 35 | pass 36 | def ok(self): 37 | return True 38 | 39 | class _meta(type): 40 | pass 41 | 42 | def __call__(self): 43 | pass 44 | def ok(self): 45 | return True 46 | 47 | _mclass = _meta("_mclass", (object,), {"__call__": __call__, "ok": ok}) 48 | 49 | del __call__ 50 | del ok 51 | 52 | o = _class() 53 | oc = _class2() 54 | n = _newclass() 55 | nc = _newclass2() 56 | m = _mclass() 57 | 58 | if sys.hexversion < 0x03090000: 59 | import typing 60 | class customIntList(typing.List[int]): 61 | pass 62 | else: 63 | class customIntList(list[int]): 64 | pass 65 | 66 | # test pickles for class instances 67 | def test_class_instances(): 68 | assert dill.pickles(o) 69 | assert dill.pickles(oc) 70 | assert dill.pickles(n) 71 | assert dill.pickles(nc) 72 | assert dill.pickles(m) 73 | 74 | def test_class_objects(): 75 | clslist = [_class,_class2,_newclass,_newclass2,_mclass] 76 | objlist = [o,oc,n,nc,m] 77 | _clslist = [dill.dumps(obj) for obj in clslist] 78 | _objlist = [dill.dumps(obj) for obj in objlist] 79 | 80 | for obj in clslist: 81 | globals().pop(obj.__name__) 82 | del clslist 83 | for obj in ['o','oc','n','nc']: 84 | globals().pop(obj) 85 | del objlist 86 | del obj 87 | 88 | for obj,cls in zip(_objlist,_clslist): 89 | _cls = dill.loads(cls) 90 | _obj = dill.loads(obj) 91 | assert _obj.ok() 92 | assert _cls.ok(_cls()) 93 | if _cls.__name__ == "_mclass": 94 | assert type(_cls).__name__ == "_meta" 95 | 96 | # test NoneType 97 | def test_specialtypes(): 98 | assert dill.pickles(type(None)) 99 | assert dill.pickles(type(NotImplemented)) 100 | assert dill.pickles(type(Ellipsis)) 101 | assert dill.pickles(type(EnumMeta)) 102 | 103 | from collections import namedtuple 104 | Z = namedtuple("Z", ['a','b']) 105 | Zi = Z(0,1) 106 | X = namedtuple("Y", ['a','b']) 107 | X.__name__ = "X" 108 | X.__qualname__ = "X" #XXX: name must 'match' or fails to pickle 109 | Xi = X(0,1) 110 | Bad = namedtuple("FakeName", ['a','b']) 111 | Badi = Bad(0,1) 112 | Defaults = namedtuple('Defaults', ['x', 'y'], defaults=[1]) 113 | Defaultsi = Defaults(2) 114 | 115 | # test namedtuple 116 | def test_namedtuple(): 117 | assert Z is dill.loads(dill.dumps(Z)) 118 | assert Zi == dill.loads(dill.dumps(Zi)) 119 | assert X is dill.loads(dill.dumps(X)) 120 | assert Xi == dill.loads(dill.dumps(Xi)) 121 | assert Defaults is dill.loads(dill.dumps(Defaults)) 122 | assert Defaultsi == dill.loads(dill.dumps(Defaultsi)) 123 | assert Bad is not dill.loads(dill.dumps(Bad)) 124 | assert Bad._fields == dill.loads(dill.dumps(Bad))._fields 125 | assert tuple(Badi) == tuple(dill.loads(dill.dumps(Badi))) 126 | 127 | class A: 128 | class B(namedtuple("C", ["one", "two"])): 129 | '''docstring''' 130 | B.__module__ = 'testing' 131 | 132 | a = A() 133 | assert dill.copy(a) 134 | 135 | assert dill.copy(A.B).__name__ == 'B' 136 | assert dill.copy(A.B).__qualname__.endswith('..A.B') 137 | assert dill.copy(A.B).__doc__ == 'docstring' 138 | assert dill.copy(A.B).__module__ == 'testing' 139 | 140 | from typing import NamedTuple 141 | 142 | def A(): 143 | class B(NamedTuple): 144 | x: int 145 | return B 146 | 147 | assert type(dill.copy(A()(8))).__qualname__ == type(A()(8)).__qualname__ 148 | 149 | def test_dtype(): 150 | try: 151 | import numpy as np 152 | 153 | dti = np.dtype('int') 154 | assert np.dtype == dill.copy(np.dtype) 155 | assert dti == dill.copy(dti) 156 | except ImportError: pass 157 | 158 | 159 | def test_array_nested(): 160 | try: 161 | import numpy as np 162 | 163 | x = np.array([1]) 164 | y = (x,) 165 | assert y == dill.copy(y) 166 | 167 | except ImportError: pass 168 | 169 | 170 | def test_array_subclass(): 171 | try: 172 | import numpy as np 173 | 174 | class TestArray(np.ndarray): 175 | def __new__(cls, input_array, color): 176 | obj = np.asarray(input_array).view(cls) 177 | obj.color = color 178 | return obj 179 | def __array_finalize__(self, obj): 180 | if obj is None: 181 | return 182 | if isinstance(obj, type(self)): 183 | self.color = obj.color 184 | def __getnewargs__(self): 185 | return np.asarray(self), self.color 186 | 187 | a1 = TestArray(np.zeros(100), color='green') 188 | if not dill._dill.IS_PYPY: 189 | assert dill.pickles(a1) 190 | assert a1.__dict__ == dill.copy(a1).__dict__ 191 | 192 | a2 = a1[0:9] 193 | if not dill._dill.IS_PYPY: 194 | assert dill.pickles(a2) 195 | assert a2.__dict__ == dill.copy(a2).__dict__ 196 | 197 | class TestArray2(np.ndarray): 198 | color = 'blue' 199 | 200 | a3 = TestArray2([1,2,3,4,5]) 201 | a3.color = 'green' 202 | if not dill._dill.IS_PYPY: 203 | assert dill.pickles(a3) 204 | assert a3.__dict__ == dill.copy(a3).__dict__ 205 | 206 | except ImportError: pass 207 | 208 | 209 | def test_method_decorator(): 210 | class A(object): 211 | @classmethod 212 | def test(cls): 213 | pass 214 | 215 | a = A() 216 | 217 | res = dill.dumps(a) 218 | new_obj = dill.loads(res) 219 | new_obj.__class__.test() 220 | 221 | # test slots 222 | class Y(object): 223 | __slots__ = ('y', '__weakref__') 224 | def __init__(self, y): 225 | self.y = y 226 | 227 | value = 123 228 | y = Y(value) 229 | 230 | class Y2(object): 231 | __slots__ = 'y' 232 | def __init__(self, y): 233 | self.y = y 234 | 235 | def test_slots(): 236 | assert dill.pickles(Y) 237 | assert dill.pickles(y) 238 | assert dill.pickles(Y.y) 239 | assert dill.copy(y).y == value 240 | assert dill.copy(Y2(value)).y == value 241 | 242 | def test_origbases(): 243 | assert dill.copy(customIntList).__orig_bases__ == customIntList.__orig_bases__ 244 | 245 | def test_attr(): 246 | import attr 247 | @attr.s 248 | class A: 249 | a = attr.ib() 250 | 251 | v = A(1) 252 | assert dill.copy(v) == v 253 | 254 | def test_metaclass(): 255 | class metaclass_with_new(type): 256 | def __new__(mcls, name, bases, ns, **kwds): 257 | cls = super().__new__(mcls, name, bases, ns, **kwds) 258 | assert mcls is not None 259 | assert cls.method(mcls) 260 | return cls 261 | def method(cls, mcls): 262 | return isinstance(cls, mcls) 263 | 264 | l = locals() 265 | exec("""class subclass_with_new(metaclass=metaclass_with_new): 266 | def __new__(cls): 267 | self = super().__new__(cls) 268 | return self""", None, l) 269 | subclass_with_new = l['subclass_with_new'] 270 | 271 | assert dill.copy(subclass_with_new()) 272 | 273 | def test_enummeta(): 274 | from http import HTTPStatus 275 | import enum 276 | assert dill.copy(HTTPStatus.OK) is HTTPStatus.OK 277 | assert dill.copy(enum.EnumMeta) is enum.EnumMeta 278 | 279 | def test_inherit(): #NOTE: see issue #612 280 | class Foo: 281 | w = 0 282 | x = 1 283 | y = 1.1 284 | a = () 285 | b = (1,) 286 | n = None 287 | 288 | class Bar(Foo): 289 | w = 2 290 | x = 1 291 | y = 1.1 292 | z = 0.2 293 | a = () 294 | b = (1,) 295 | c = (2,) 296 | n = None 297 | 298 | Baz = dill.copy(Bar) 299 | 300 | import platform 301 | is_pypy = platform.python_implementation() == 'PyPy' 302 | assert Bar.__dict__ == Baz.__dict__ 303 | # ints 304 | assert 'w' in Bar.__dict__ and 'w' in Baz.__dict__ 305 | assert Bar.__dict__['w'] is Baz.__dict__['w'] 306 | assert 'x' in Bar.__dict__ and 'x' in Baz.__dict__ 307 | assert Bar.__dict__['x'] is Baz.__dict__['x'] 308 | # floats 309 | assert 'y' in Bar.__dict__ and 'y' in Baz.__dict__ 310 | same = Bar.__dict__['y'] is Baz.__dict__['y'] 311 | assert same if is_pypy else not same 312 | assert 'z' in Bar.__dict__ and 'z' in Baz.__dict__ 313 | same = Bar.__dict__['z'] is Baz.__dict__['z'] 314 | assert same if is_pypy else not same 315 | # tuples 316 | assert 'a' in Bar.__dict__ and 'a' in Baz.__dict__ 317 | assert Bar.__dict__['a'] is Baz.__dict__['a'] 318 | assert 'b' in Bar.__dict__ and 'b' in Baz.__dict__ 319 | assert Bar.__dict__['b'] is not Baz.__dict__['b'] 320 | assert 'c' in Bar.__dict__ and 'c' in Baz.__dict__ 321 | assert Bar.__dict__['c'] is not Baz.__dict__['c'] 322 | # None 323 | assert 'n' in Bar.__dict__ and 'n' in Baz.__dict__ 324 | assert Bar.__dict__['n'] is Baz.__dict__['n'] 325 | 326 | 327 | if __name__ == '__main__': 328 | test_class_instances() 329 | test_class_objects() 330 | test_specialtypes() 331 | test_namedtuple() 332 | test_dtype() 333 | test_array_nested() 334 | test_array_subclass() 335 | test_method_decorator() 336 | test_slots() 337 | test_origbases() 338 | test_metaclass() 339 | test_enummeta() 340 | test_inherit() 341 | -------------------------------------------------------------------------------- /dill/tests/test_dataclasses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Author: Anirudh Vegesana (avegesan@cs.stanford.edu) 5 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | test pickling a dataclass 10 | """ 11 | 12 | import dill 13 | import dataclasses 14 | 15 | def test_dataclasses(): 16 | # Issue #500 17 | @dataclasses.dataclass 18 | class A: 19 | x: int 20 | y: str 21 | 22 | @dataclasses.dataclass 23 | class B: 24 | a: A 25 | 26 | a = A(1, "test") 27 | before = B(a) 28 | save = dill.dumps(before) 29 | after = dill.loads(save) 30 | assert before != after # classes don't match 31 | assert before == B(A(**dataclasses.asdict(after.a))) 32 | assert dataclasses.asdict(before) == dataclasses.asdict(after) 33 | 34 | if __name__ == '__main__': 35 | test_dataclasses() 36 | -------------------------------------------------------------------------------- /dill/tests/test_detect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | from dill.detect import baditems, badobjects, badtypes, errors, parent, at, globalvars 10 | from dill import settings 11 | from dill._dill import IS_PYPY 12 | from pickle import PicklingError 13 | 14 | import inspect 15 | import sys 16 | import os 17 | 18 | def test_bad_things(): 19 | f = inspect.currentframe() 20 | assert baditems(f) == [f] 21 | #assert baditems(globals()) == [f] #XXX 22 | assert badobjects(f) is f 23 | assert badtypes(f) == type(f) 24 | assert type(errors(f)) is TypeError 25 | d = badtypes(f, 1) 26 | assert isinstance(d, dict) 27 | assert list(badobjects(f, 1).keys()) == list(d.keys()) 28 | assert list(errors(f, 1).keys()) == list(d.keys()) 29 | s = set([(err.__class__.__name__,err.args[0]) for err in list(errors(f, 1).values())]) 30 | a = dict(s) 31 | if not os.environ.get('COVERAGE'): #XXX: travis-ci 32 | proxy = 0 if type(f.f_locals) is dict else 1 33 | assert len(s) == len(a) + proxy # TypeError (and possibly PicklingError) 34 | n = 2 35 | assert len(a) is n if 'PicklingError' in a.keys() else n-1 36 | 37 | def test_parent(): 38 | x = [4,5,6,7] 39 | listiter = iter(x) 40 | obj = parent(listiter, list) 41 | assert obj is x 42 | 43 | if IS_PYPY: assert parent(obj, int) is None 44 | else: assert parent(obj, int) is x[-1] # python oddly? finds last int 45 | assert at(id(at)) is at 46 | 47 | a, b, c = 1, 2, 3 48 | 49 | def squared(x): 50 | return a+x**2 51 | 52 | def foo(x): 53 | def bar(y): 54 | return squared(x)+y 55 | return bar 56 | 57 | class _class: 58 | def _method(self): 59 | pass 60 | def ok(self): 61 | return True 62 | 63 | def test_globals(): 64 | def f(): 65 | a 66 | def g(): 67 | b 68 | def h(): 69 | c 70 | assert globalvars(f) == dict(a=1, b=2, c=3) 71 | 72 | res = globalvars(foo, recurse=True) 73 | assert set(res) == set(['squared', 'a']) 74 | res = globalvars(foo, recurse=False) 75 | assert res == {} 76 | zap = foo(2) 77 | res = globalvars(zap, recurse=True) 78 | assert set(res) == set(['squared', 'a']) 79 | res = globalvars(zap, recurse=False) 80 | assert set(res) == set(['squared']) 81 | del zap 82 | res = globalvars(squared) 83 | assert set(res) == set(['a']) 84 | # FIXME: should find referenced __builtins__ 85 | #res = globalvars(_class, recurse=True) 86 | #assert set(res) == set(['True']) 87 | #res = globalvars(_class, recurse=False) 88 | #assert res == {} 89 | #res = globalvars(_class.ok, recurse=True) 90 | #assert set(res) == set(['True']) 91 | #res = globalvars(_class.ok, recurse=False) 92 | #assert set(res) == set(['True']) 93 | 94 | 95 | #98 dill ignores __getstate__ in interactive lambdas 96 | bar = [0] 97 | 98 | class Foo(object): 99 | def __init__(self): 100 | pass 101 | def __getstate__(self): 102 | bar[0] = bar[0]+1 103 | return {} 104 | def __setstate__(self, data): 105 | pass 106 | 107 | f = Foo() 108 | 109 | def test_getstate(): 110 | from dill import dumps, loads 111 | dumps(f) 112 | b = bar[0] 113 | dumps(lambda: f, recurse=False) # doesn't call __getstate__ 114 | assert bar[0] == b 115 | dumps(lambda: f, recurse=True) # calls __getstate__ 116 | assert bar[0] == b + 1 117 | 118 | #97 serialize lambdas in test files 119 | def test_deleted(): 120 | global sin 121 | from dill import dumps, loads 122 | from math import sin, pi 123 | 124 | def sinc(x): 125 | return sin(x)/x 126 | 127 | settings['recurse'] = True 128 | _sinc = dumps(sinc) 129 | sin = globals().pop('sin') 130 | sin = 1 131 | del sin 132 | sinc_ = loads(_sinc) # no NameError... pickling preserves 'sin' 133 | res = sinc_(1) 134 | from math import sin 135 | assert sinc(1) == res 136 | 137 | 138 | def test_lambdify(): 139 | try: 140 | from sympy import symbols, lambdify 141 | except ImportError: 142 | return 143 | settings['recurse'] = True 144 | x = symbols("x") 145 | y = x**2 146 | f = lambdify([x], y) 147 | z = min 148 | d = globals() 149 | globalvars(f, recurse=True, builtin=True) 150 | assert z is min 151 | assert d is globals() 152 | 153 | 154 | if __name__ == '__main__': 155 | test_bad_things() 156 | test_parent() 157 | test_globals() 158 | test_getstate() 159 | test_deleted() 160 | test_lambdify() 161 | -------------------------------------------------------------------------------- /dill/tests/test_dictviews.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Author: Anirudh Vegesana (avegesan@cs.stanford.edu) 5 | # Copyright (c) 2021-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | from dill._dill import OLD310, MAPPING_PROXY_TRICK, DictProxyType 11 | 12 | def test_dictproxy(): 13 | assert dill.copy(DictProxyType({'a': 2})) 14 | 15 | def test_dictviews(): 16 | x = {'a': 1} 17 | assert dill.copy(x.keys()) 18 | assert dill.copy(x.values()) 19 | assert dill.copy(x.items()) 20 | 21 | def test_dictproxy_trick(): 22 | if not OLD310 and MAPPING_PROXY_TRICK: 23 | x = {'a': 1} 24 | all_views = (x.values(), x.items(), x.keys(), x) 25 | seperate_views = dill.copy(all_views) 26 | new_x = seperate_views[-1] 27 | new_x['b'] = 2 28 | new_x['c'] = 1 29 | assert len(new_x) == 3 and len(x) == 1 30 | assert len(seperate_views[0]) == 3 and len(all_views[0]) == 1 31 | assert len(seperate_views[1]) == 3 and len(all_views[1]) == 1 32 | assert len(seperate_views[2]) == 3 and len(all_views[2]) == 1 33 | assert dict(all_views[1]) == x 34 | assert dict(seperate_views[1]) == new_x 35 | 36 | if __name__ == '__main__': 37 | test_dictproxy() 38 | test_dictviews() 39 | test_dictproxy_trick() 40 | -------------------------------------------------------------------------------- /dill/tests/test_diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | from dill import __diff as diff 10 | 11 | import sys 12 | IS_PYPY = not hasattr(sys, 'getrefcount') 13 | 14 | class A: 15 | pass 16 | 17 | def test_diff(): 18 | a = A() 19 | b = A() 20 | c = A() 21 | a.a = b 22 | b.a = c 23 | diff.memorise(a) 24 | assert not diff.has_changed(a) 25 | c.a = 1 26 | assert diff.has_changed(a) 27 | diff.memorise(c, force=True) 28 | assert not diff.has_changed(a) 29 | c.a = 2 30 | assert diff.has_changed(a) 31 | changed = diff.whats_changed(a) 32 | assert list(changed[0].keys()) == ["a"] 33 | assert not changed[1] 34 | 35 | a2 = [] 36 | b2 = [a2] 37 | c2 = [b2] 38 | diff.memorise(c2) 39 | assert not diff.has_changed(c2) 40 | a2.append(1) 41 | assert diff.has_changed(c2) 42 | changed = diff.whats_changed(c2) 43 | assert changed[0] == {} 44 | assert changed[1] 45 | 46 | a3 = {} 47 | b3 = {1: a3} 48 | c3 = {1: b3} 49 | diff.memorise(c3) 50 | assert not diff.has_changed(c3) 51 | a3[1] = 1 52 | assert diff.has_changed(c3) 53 | changed = diff.whats_changed(c3) 54 | assert changed[0] == {} 55 | assert changed[1] 56 | 57 | if not IS_PYPY: 58 | import abc 59 | # make sure the "_abc_invaldation_counter" doesn't make test fail 60 | diff.memorise(abc.ABCMeta, force=True) 61 | assert not diff.has_changed(abc) 62 | abc.ABCMeta.zzz = 1 63 | assert diff.has_changed(abc) 64 | changed = diff.whats_changed(abc) 65 | assert list(changed[0].keys()) == ["ABCMeta"] 66 | assert not changed[1] 67 | 68 | ''' 69 | import Queue 70 | diff.memorise(Queue, force=True) 71 | assert not diff.has_changed(Queue) 72 | Queue.Queue.zzz = 1 73 | assert diff.has_changed(Queue) 74 | changed = diff.whats_changed(Queue) 75 | assert list(changed[0].keys()) == ["Queue"] 76 | assert not changed[1] 77 | 78 | import math 79 | diff.memorise(math, force=True) 80 | assert not diff.has_changed(math) 81 | math.zzz = 1 82 | assert diff.has_changed(math) 83 | changed = diff.whats_changed(math) 84 | assert list(changed[0].keys()) == ["zzz"] 85 | assert not changed[1] 86 | ''' 87 | 88 | a = A() 89 | b = A() 90 | c = A() 91 | a.a = b 92 | b.a = c 93 | diff.memorise(a) 94 | assert not diff.has_changed(a) 95 | c.a = 1 96 | assert diff.has_changed(a) 97 | diff.memorise(c, force=True) 98 | assert not diff.has_changed(a) 99 | del c.a 100 | assert diff.has_changed(a) 101 | changed = diff.whats_changed(a) 102 | assert list(changed[0].keys()) == ["a"] 103 | assert not changed[1] 104 | 105 | 106 | if __name__ == '__main__': 107 | test_diff() 108 | -------------------------------------------------------------------------------- /dill/tests/test_extendpickle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill as pickle 10 | from io import BytesIO as StringIO 11 | 12 | 13 | def my_fn(x): 14 | return x * 17 15 | 16 | 17 | def test_extend(): 18 | obj = lambda : my_fn(34) 19 | assert obj() == 578 20 | 21 | obj_io = StringIO() 22 | pickler = pickle.Pickler(obj_io) 23 | pickler.dump(obj) 24 | 25 | obj_str = obj_io.getvalue() 26 | 27 | obj2_io = StringIO(obj_str) 28 | unpickler = pickle.Unpickler(obj2_io) 29 | obj2 = unpickler.load() 30 | 31 | assert obj2() == 578 32 | 33 | 34 | def test_isdill(): 35 | obj_io = StringIO() 36 | pickler = pickle.Pickler(obj_io) 37 | assert pickle._dill.is_dill(pickler) is True 38 | 39 | pickler = pickle._dill.StockPickler(obj_io) 40 | assert pickle._dill.is_dill(pickler) is False 41 | 42 | try: 43 | import multiprocess as mp 44 | pickler = mp.reduction.ForkingPickler(obj_io) 45 | assert pickle._dill.is_dill(pickler, child=True) is True 46 | assert pickle._dill.is_dill(pickler, child=False) is False 47 | except Exception: 48 | pass 49 | 50 | 51 | if __name__ == '__main__': 52 | test_extend() 53 | test_isdill() 54 | -------------------------------------------------------------------------------- /dill/tests/test_fglobals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2021-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import dill 9 | dill.settings['recurse'] = True 10 | 11 | def get_fun_with_strftime(): 12 | def fun_with_strftime(): 13 | import datetime 14 | return datetime.datetime.strptime("04-01-1943", "%d-%m-%Y").strftime( 15 | "%Y-%m-%d %H:%M:%S" 16 | ) 17 | return fun_with_strftime 18 | 19 | 20 | def get_fun_with_strftime2(): 21 | import datetime 22 | return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') 23 | 24 | 25 | def test_doc_dill_issue_219(): 26 | back_fn = dill.loads(dill.dumps(get_fun_with_strftime())) 27 | assert back_fn() == "1943-01-04 00:00:00" 28 | dupl = dill.loads(dill.dumps(get_fun_with_strftime2)) 29 | assert dupl() == get_fun_with_strftime2() 30 | 31 | 32 | def get_fun_with_internal_import(): 33 | def fun_with_import(): 34 | import re 35 | return re.compile("$") 36 | return fun_with_import 37 | 38 | 39 | def test_method_with_internal_import_should_work(): 40 | import re 41 | back_fn = dill.loads(dill.dumps(get_fun_with_internal_import())) 42 | import inspect 43 | if hasattr(inspect, 'getclosurevars'): 44 | vars = inspect.getclosurevars(back_fn) 45 | assert vars.globals == {} 46 | assert vars.nonlocals == {} 47 | assert back_fn() == re.compile("$") 48 | assert "__builtins__" in back_fn.__globals__ 49 | 50 | 51 | if __name__ == "__main__": 52 | import sys 53 | if (sys.version_info[:3] != (3,10,0) or sys.version_info[3] != 'alpha'): 54 | test_doc_dill_issue_219() 55 | test_method_with_internal_import_should_work() 56 | -------------------------------------------------------------------------------- /dill/tests/test_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2019-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import functools 9 | import dill 10 | import sys 11 | dill.settings['recurse'] = True 12 | 13 | 14 | def function_a(a): 15 | return a 16 | 17 | 18 | def function_b(b, b1): 19 | return b + b1 20 | 21 | 22 | def function_c(c, c1=1): 23 | return c + c1 24 | 25 | 26 | def function_d(d, d1, d2=1): 27 | """doc string""" 28 | return d + d1 + d2 29 | 30 | function_d.__module__ = 'a module' 31 | 32 | 33 | exec(''' 34 | def function_e(e, *e1, e2=1, e3=2): 35 | return e + sum(e1) + e2 + e3''') 36 | 37 | globalvar = 0 38 | 39 | @functools.lru_cache(None) 40 | def function_with_cache(x): 41 | global globalvar 42 | globalvar += x 43 | return globalvar 44 | 45 | 46 | def function_with_unassigned_variable(): 47 | if False: 48 | value = None 49 | return (lambda: value) 50 | 51 | 52 | def test_issue_510(): 53 | # A very bizzare use of functions and methods that pickle doesn't get 54 | # correctly for odd reasons. 55 | class Foo: 56 | def __init__(self): 57 | def f2(self): 58 | return self 59 | self.f2 = f2.__get__(self) 60 | 61 | import dill, pickletools 62 | f = Foo() 63 | f1 = dill.copy(f) 64 | assert f1.f2() is f1 65 | 66 | 67 | def test_functions(): 68 | dumped_func_a = dill.dumps(function_a) 69 | assert dill.loads(dumped_func_a)(0) == 0 70 | 71 | dumped_func_b = dill.dumps(function_b) 72 | assert dill.loads(dumped_func_b)(1,2) == 3 73 | 74 | dumped_func_c = dill.dumps(function_c) 75 | assert dill.loads(dumped_func_c)(1) == 2 76 | assert dill.loads(dumped_func_c)(1, 2) == 3 77 | 78 | dumped_func_d = dill.dumps(function_d) 79 | assert dill.loads(dumped_func_d).__doc__ == function_d.__doc__ 80 | assert dill.loads(dumped_func_d).__module__ == function_d.__module__ 81 | assert dill.loads(dumped_func_d)(1, 2) == 4 82 | assert dill.loads(dumped_func_d)(1, 2, 3) == 6 83 | assert dill.loads(dumped_func_d)(1, 2, d2=3) == 6 84 | 85 | function_with_cache(1) 86 | globalvar = 0 87 | dumped_func_cache = dill.dumps(function_with_cache) 88 | assert function_with_cache(2) == 3 89 | assert function_with_cache(1) == 1 90 | assert function_with_cache(3) == 6 91 | assert function_with_cache(2) == 3 92 | 93 | empty_cell = function_with_unassigned_variable() 94 | cell_copy = dill.loads(dill.dumps(empty_cell)) 95 | assert 'empty' in str(cell_copy.__closure__[0]) 96 | try: 97 | cell_copy() 98 | except Exception: 99 | # this is good 100 | pass 101 | else: 102 | raise AssertionError('cell_copy() did not read an empty cell') 103 | 104 | exec(''' 105 | dumped_func_e = dill.dumps(function_e) 106 | assert dill.loads(dumped_func_e)(1, 2) == 6 107 | assert dill.loads(dumped_func_e)(1, 2, 3) == 9 108 | assert dill.loads(dumped_func_e)(1, 2, e2=3) == 8 109 | assert dill.loads(dumped_func_e)(1, 2, e2=3, e3=4) == 10 110 | assert dill.loads(dumped_func_e)(1, 2, 3, e2=4) == 12 111 | assert dill.loads(dumped_func_e)(1, 2, 3, e2=4, e3=5) == 15''') 112 | 113 | def test_code_object(): 114 | import warnings 115 | from dill._dill import ALL_CODE_PARAMS, CODE_PARAMS, CODE_VERSION, _create_code 116 | code = function_c.__code__ 117 | warnings.filterwarnings('ignore', category=DeprecationWarning) # issue 597 118 | LNOTAB = getattr(code, 'co_lnotab', b'') 119 | if warnings.filters: del warnings.filters[0] 120 | fields = {f: getattr(code, 'co_'+f) for f in CODE_PARAMS} 121 | fields.setdefault('posonlyargcount', 0) # python >= 3.8 122 | fields.setdefault('lnotab', LNOTAB) # python <= 3.9 123 | fields.setdefault('linetable', b'') # python >= 3.10 124 | fields.setdefault('qualname', fields['name']) # python >= 3.11 125 | fields.setdefault('exceptiontable', b'') # python >= 3.11 126 | fields.setdefault('endlinetable', None) # python == 3.11a 127 | fields.setdefault('columntable', None) # python == 3.11a 128 | 129 | for version, _, params in ALL_CODE_PARAMS: 130 | args = tuple(fields[p] for p in params.split()) 131 | try: 132 | _create_code(*args) 133 | if version >= (3,10): 134 | _create_code(fields['lnotab'], *args) 135 | except Exception as error: 136 | raise Exception("failed to construct code object with format version {}".format(version)) from error 137 | 138 | if __name__ == '__main__': 139 | test_functions() 140 | test_issue_510() 141 | test_code_object() 142 | -------------------------------------------------------------------------------- /dill/tests/test_functors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import functools 10 | import dill 11 | dill.settings['recurse'] = True 12 | 13 | 14 | def f(a, b, c): # without keywords 15 | pass 16 | 17 | 18 | def g(a, b, c=2): # with keywords 19 | pass 20 | 21 | 22 | def h(a=1, b=2, c=3): # without args 23 | pass 24 | 25 | 26 | def test_functools(): 27 | fp = functools.partial(f, 1, 2) 28 | gp = functools.partial(g, 1, c=2) 29 | hp = functools.partial(h, 1, c=2) 30 | bp = functools.partial(int, base=2) 31 | 32 | assert dill.pickles(fp, safe=True) 33 | assert dill.pickles(gp, safe=True) 34 | assert dill.pickles(hp, safe=True) 35 | assert dill.pickles(bp, safe=True) 36 | 37 | 38 | if __name__ == '__main__': 39 | test_functools() 40 | -------------------------------------------------------------------------------- /dill/tests/test_logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Author: Leonardo Gama (@leogama) 4 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import logging 9 | import re 10 | import tempfile 11 | 12 | import dill 13 | from dill import detect 14 | from dill.logger import stderr_handler, adapter as logger 15 | 16 | try: 17 | from StringIO import StringIO 18 | except ImportError: 19 | from io import StringIO 20 | 21 | test_obj = {'a': (1, 2), 'b': object(), 'f': lambda x: x**2, 'big': list(range(10))} 22 | 23 | def test_logging(should_trace): 24 | buffer = StringIO() 25 | handler = logging.StreamHandler(buffer) 26 | logger.addHandler(handler) 27 | try: 28 | dill.dumps(test_obj) 29 | if should_trace: 30 | regex = re.compile(r'(\S*┬ \w.*[^)]' # begin pickling object 31 | r'|│*└ # \w.* \[\d+ (\wi)?B])' # object written (with size) 32 | ) 33 | for line in buffer.getvalue().splitlines(): 34 | assert regex.fullmatch(line) 35 | return buffer.getvalue() 36 | else: 37 | assert buffer.getvalue() == "" 38 | finally: 39 | logger.removeHandler(handler) 40 | buffer.close() 41 | 42 | def test_trace_to_file(stream_trace): 43 | file = tempfile.NamedTemporaryFile(mode='r') 44 | with detect.trace(file.name, mode='w'): 45 | dill.dumps(test_obj) 46 | file_trace = file.read() 47 | file.close() 48 | # Apparently, objects can change location in memory... 49 | reghex = re.compile(r'0x[0-9A-Za-z]+') 50 | file_trace, stream_trace = reghex.sub('0x', file_trace), reghex.sub('0x', stream_trace) 51 | # PyPy prints dictionary contents with repr(dict)... 52 | regdict = re.compile(r'(dict\.__repr__ of ).*') 53 | file_trace, stream_trace = regdict.sub(r'\1{}>', file_trace), regdict.sub(r'\1{}>', stream_trace) 54 | assert file_trace == stream_trace 55 | 56 | if __name__ == '__main__': 57 | logger.removeHandler(stderr_handler) 58 | test_logging(should_trace=False) 59 | detect.trace(True) 60 | test_logging(should_trace=True) 61 | detect.trace(False) 62 | test_logging(should_trace=False) 63 | 64 | loglevel = logging.ERROR 65 | logger.setLevel(loglevel) 66 | with detect.trace(): 67 | stream_trace = test_logging(should_trace=True) 68 | test_logging(should_trace=False) 69 | assert logger.getEffectiveLevel() == loglevel 70 | test_trace_to_file(stream_trace) 71 | -------------------------------------------------------------------------------- /dill/tests/test_mixins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | dill.settings['recurse'] = True 11 | 12 | 13 | def wtf(x,y,z): 14 | def zzz(): 15 | return x 16 | def yyy(): 17 | return y 18 | def xxx(): 19 | return z 20 | return zzz,yyy 21 | 22 | 23 | def quad(a=1, b=1, c=0): 24 | inverted = [False] 25 | def invert(): 26 | inverted[0] = not inverted[0] 27 | def dec(f): 28 | def func(*args, **kwds): 29 | x = f(*args, **kwds) 30 | if inverted[0]: x = -x 31 | return a*x**2 + b*x + c 32 | func.__wrapped__ = f 33 | func.invert = invert 34 | func.inverted = inverted 35 | return func 36 | return dec 37 | 38 | 39 | @quad(a=0,b=2) 40 | def double_add(*args): 41 | return sum(args) 42 | 43 | 44 | fx = sum([1,2,3]) 45 | 46 | 47 | ### to make it interesting... 48 | def quad_factory(a=1,b=1,c=0): 49 | def dec(f): 50 | def func(*args,**kwds): 51 | fx = f(*args,**kwds) 52 | return a*fx**2 + b*fx + c 53 | return func 54 | return dec 55 | 56 | 57 | @quad_factory(a=0,b=4,c=0) 58 | def quadish(x): 59 | return x+1 60 | 61 | 62 | quadratic = quad_factory() 63 | 64 | 65 | def doubler(f): 66 | def inner(*args, **kwds): 67 | fx = f(*args, **kwds) 68 | return 2*fx 69 | return inner 70 | 71 | 72 | @doubler 73 | def quadruple(x): 74 | return 2*x 75 | 76 | 77 | def test_mixins(): 78 | # test mixins 79 | assert double_add(1,2,3) == 2*fx 80 | double_add.invert() 81 | assert double_add(1,2,3) == -2*fx 82 | 83 | _d = dill.copy(double_add) 84 | assert _d(1,2,3) == -2*fx 85 | #_d.invert() #FIXME: fails seemingly randomly 86 | #assert _d(1,2,3) == 2*fx 87 | 88 | assert _d.__wrapped__(1,2,3) == fx 89 | 90 | # XXX: issue or feature? in python3.4, inverted is linked through copy 91 | if not double_add.inverted[0]: 92 | double_add.invert() 93 | 94 | # test some stuff from source and pointers 95 | ds = dill.source 96 | dd = dill.detect 97 | assert ds.getsource(dd.freevars(quadish)['f']) == '@quad_factory(a=0,b=4,c=0)\ndef quadish(x):\n return x+1\n' 98 | assert ds.getsource(dd.freevars(quadruple)['f']) == '@doubler\ndef quadruple(x):\n return 2*x\n' 99 | assert ds.importable(quadish, source=False) == 'from %s import quadish\n' % __name__ 100 | assert ds.importable(quadruple, source=False) == 'from %s import quadruple\n' % __name__ 101 | assert ds.importable(quadratic, source=False) == 'from %s import quadratic\n' % __name__ 102 | assert ds.importable(double_add, source=False) == 'from %s import double_add\n' % __name__ 103 | assert ds.importable(quadruple, source=True) == 'def doubler(f):\n def inner(*args, **kwds):\n fx = f(*args, **kwds)\n return 2*fx\n return inner\n\n@doubler\ndef quadruple(x):\n return 2*x\n' 104 | #***** #FIXME: this needs work 105 | result = ds.importable(quadish, source=True) 106 | a,b,c,_,result = result.split('\n',4) 107 | assert result == 'def quad_factory(a=1,b=1,c=0):\n def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n return dec\n\n@quad_factory(a=0,b=4,c=0)\ndef quadish(x):\n return x+1\n' 108 | assert set([a,b,c]) == set(['a = 0', 'c = 0', 'b = 4']) 109 | result = ds.importable(quadratic, source=True) 110 | a,b,c,result = result.split('\n',3) 111 | assert result == '\ndef dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' 112 | assert set([a,b,c]) == set(['a = 1', 'c = 0', 'b = 1']) 113 | result = ds.importable(double_add, source=True) 114 | a,b,c,d,_,result = result.split('\n',5) 115 | assert result == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n\n@quad(a=0,b=2)\ndef double_add(*args):\n return sum(args)\n' 116 | assert set([a,b,c,d]) == set(['a = 0', 'c = 0', 'b = 2', 'inverted = [True]']) 117 | #***** 118 | 119 | 120 | if __name__ == '__main__': 121 | test_mixins() 122 | -------------------------------------------------------------------------------- /dill/tests/test_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import sys 10 | import dill 11 | import test_mixins as module 12 | from importlib import reload 13 | dill.settings['recurse'] = True 14 | 15 | cached = (module.__cached__ if hasattr(module, "__cached__") 16 | else module.__file__.split(".", 1)[0] + ".pyc") 17 | 18 | module.a = 1234 19 | 20 | pik_mod = dill.dumps(module) 21 | 22 | module.a = 0 23 | 24 | # remove module 25 | del sys.modules[module.__name__] 26 | del module 27 | 28 | module = dill.loads(pik_mod) 29 | def test_attributes(): 30 | #assert hasattr(module, "a") and module.a == 1234 #FIXME: -m dill.tests 31 | assert module.double_add(1, 2, 3) == 2 * module.fx 32 | 33 | # Restart, and test use_diff 34 | 35 | reload(module) 36 | 37 | try: 38 | dill.use_diff() 39 | 40 | module.a = 1234 41 | 42 | pik_mod = dill.dumps(module) 43 | 44 | module.a = 0 45 | 46 | # remove module 47 | del sys.modules[module.__name__] 48 | del module 49 | 50 | module = dill.loads(pik_mod) 51 | def test_diff_attributes(): 52 | assert hasattr(module, "a") and module.a == 1234 53 | assert module.double_add(1, 2, 3) == 2 * module.fx 54 | 55 | except AttributeError: 56 | def test_diff_attributes(): 57 | pass 58 | 59 | # clean up 60 | import os 61 | if os.path.exists(cached): 62 | os.remove(cached) 63 | pycache = os.path.join(os.path.dirname(module.__file__), "__pycache__") 64 | if os.path.exists(pycache) and not os.listdir(pycache): 65 | os.removedirs(pycache) 66 | 67 | 68 | # test when module is None 69 | import math 70 | 71 | def get_lambda(str, **kwarg): 72 | return eval(str, kwarg, None) 73 | 74 | obj = get_lambda('lambda x: math.exp(x)', math=math) 75 | 76 | def test_module_is_none(): 77 | assert obj.__module__ is None 78 | assert dill.copy(obj)(3) == obj(3) 79 | 80 | 81 | if __name__ == '__main__': 82 | test_attributes() 83 | test_diff_attributes() 84 | test_module_is_none() 85 | -------------------------------------------------------------------------------- /dill/tests/test_moduledict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | dill.settings['recurse'] = True 11 | 12 | def f(func): 13 | def w(*args): 14 | return f(*args) 15 | return w 16 | 17 | @f 18 | def f2(): pass 19 | 20 | # check when __main__ and on import 21 | def test_decorated(): 22 | assert dill.pickles(f2) 23 | 24 | 25 | import doctest 26 | import logging 27 | logging.basicConfig(level=logging.DEBUG) 28 | 29 | class SomeUnreferencedUnpicklableClass(object): 30 | def __reduce__(self): 31 | raise Exception 32 | 33 | unpicklable = SomeUnreferencedUnpicklableClass() 34 | 35 | # This works fine outside of Doctest: 36 | def test_normal(): 37 | serialized = dill.dumps(lambda x: x) 38 | 39 | # should not try to pickle unpicklable object in __globals__ 40 | def tests(): 41 | """ 42 | >>> serialized = dill.dumps(lambda x: x) 43 | """ 44 | return 45 | 46 | #print("\n\nRunning Doctest:") 47 | def test_doctest(): 48 | doctest.testmod() 49 | 50 | 51 | if __name__ == '__main__': 52 | test_decorated() 53 | test_normal() 54 | test_doctest() 55 | -------------------------------------------------------------------------------- /dill/tests/test_nested.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | test dill's ability to handle nested functions 10 | """ 11 | 12 | import os 13 | import math 14 | 15 | import dill as pickle 16 | pickle.settings['recurse'] = True 17 | 18 | 19 | # the nested function: pickle should fail here, but dill is ok. 20 | def adder(augend): 21 | zero = [0] 22 | 23 | def inner(addend): 24 | return addend + augend + zero[0] 25 | return inner 26 | 27 | 28 | # rewrite the nested function using a class: standard pickle should work here. 29 | class cadder(object): 30 | def __init__(self, augend): 31 | self.augend = augend 32 | self.zero = [0] 33 | 34 | def __call__(self, addend): 35 | return addend + self.augend + self.zero[0] 36 | 37 | 38 | # rewrite again, but as an old-style class 39 | class c2adder: 40 | def __init__(self, augend): 41 | self.augend = augend 42 | self.zero = [0] 43 | 44 | def __call__(self, addend): 45 | return addend + self.augend + self.zero[0] 46 | 47 | 48 | # some basic class stuff 49 | class basic(object): 50 | pass 51 | 52 | 53 | class basic2: 54 | pass 55 | 56 | 57 | x = 5 58 | y = 1 59 | 60 | 61 | def test_basic(): 62 | a = [0, 1, 2] 63 | pa = pickle.dumps(a) 64 | pmath = pickle.dumps(math) #XXX: FAILS in pickle 65 | pmap = pickle.dumps(map) 66 | # ... 67 | la = pickle.loads(pa) 68 | lmath = pickle.loads(pmath) 69 | lmap = pickle.loads(pmap) 70 | assert list(map(math.sin, a)) == list(lmap(lmath.sin, la)) 71 | 72 | 73 | def test_basic_class(): 74 | pbasic2 = pickle.dumps(basic2) 75 | _pbasic2 = pickle.loads(pbasic2)() 76 | pbasic = pickle.dumps(basic) 77 | _pbasic = pickle.loads(pbasic)() 78 | 79 | 80 | def test_c2adder(): 81 | pc2adder = pickle.dumps(c2adder) 82 | pc2add5 = pickle.loads(pc2adder)(x) 83 | assert pc2add5(y) == x+y 84 | 85 | 86 | def test_pickled_cadder(): 87 | pcadder = pickle.dumps(cadder) 88 | pcadd5 = pickle.loads(pcadder)(x) 89 | assert pcadd5(y) == x+y 90 | 91 | 92 | def test_raw_adder_and_inner(): 93 | add5 = adder(x) 94 | assert add5(y) == x+y 95 | 96 | 97 | def test_pickled_adder(): 98 | padder = pickle.dumps(adder) 99 | padd5 = pickle.loads(padder)(x) 100 | assert padd5(y) == x+y 101 | 102 | 103 | def test_pickled_inner(): 104 | add5 = adder(x) 105 | pinner = pickle.dumps(add5) #XXX: FAILS in pickle 106 | p5add = pickle.loads(pinner) 107 | assert p5add(y) == x+y 108 | 109 | 110 | def test_moduledict_where_not_main(): 111 | try: 112 | from . import test_moduledict 113 | except ImportError: 114 | import test_moduledict 115 | name = 'test_moduledict.py' 116 | if os.path.exists(name) and os.path.exists(name+'c'): 117 | os.remove(name+'c') 118 | 119 | if os.path.exists(name) and hasattr(test_moduledict, "__cached__") \ 120 | and os.path.exists(test_moduledict.__cached__): 121 | os.remove(getattr(test_moduledict, "__cached__")) 122 | 123 | if os.path.exists("__pycache__") and not os.listdir("__pycache__"): 124 | os.removedirs("__pycache__") 125 | 126 | 127 | if __name__ == '__main__': 128 | test_basic() 129 | test_basic_class() 130 | test_c2adder() 131 | test_pickled_cadder() 132 | test_raw_adder_and_inner() 133 | test_pickled_adder() 134 | test_pickled_inner() 135 | test_moduledict_where_not_main() 136 | -------------------------------------------------------------------------------- /dill/tests/test_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | demonstrate dill's ability to pickle different python types 10 | test pickling of all Python Standard Library objects (currently: CH 1-14 @ 2.7) 11 | """ 12 | 13 | import dill as pickle 14 | pickle.settings['recurse'] = True 15 | #pickle.detect.trace(True) 16 | #import pickle 17 | 18 | # get all objects for testing 19 | from dill import load_types, objects, extend 20 | load_types(pickleable=True,unpickleable=False) 21 | 22 | # uncomment the next two lines to test cloudpickle 23 | #extend(False) 24 | #import cloudpickle as pickle 25 | 26 | # helper objects 27 | class _class: 28 | def _method(self): 29 | pass 30 | 31 | # objects that *fail* if imported 32 | special = {} 33 | special['LambdaType'] = _lambda = lambda x: lambda y: x 34 | special['MethodType'] = _method = _class()._method 35 | special['UnboundMethodType'] = _class._method 36 | objects.update(special) 37 | 38 | def pickles(name, exact=False, verbose=True): 39 | """quick check if object pickles with dill""" 40 | obj = objects[name] 41 | try: 42 | pik = pickle.loads(pickle.dumps(obj)) 43 | if exact: 44 | try: 45 | assert pik == obj 46 | except AssertionError: 47 | assert type(obj) == type(pik) 48 | if verbose: print ("weak: %s %s" % (name, type(obj))) 49 | else: 50 | assert type(obj) == type(pik) 51 | except Exception: 52 | if verbose: print ("fails: %s %s" % (name, type(obj))) 53 | 54 | 55 | def test_objects(verbose=True): 56 | for member in objects.keys(): 57 | #pickles(member, exact=True, verbose=verbose) 58 | pickles(member, exact=False, verbose=verbose) 59 | 60 | if __name__ == '__main__': 61 | import warnings 62 | warnings.simplefilter('ignore') 63 | test_objects(verbose=False) 64 | -------------------------------------------------------------------------------- /dill/tests/test_properties.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import sys 10 | 11 | import dill 12 | dill.settings['recurse'] = True 13 | 14 | 15 | class Foo(object): 16 | def __init__(self): 17 | self._data = 1 18 | 19 | def _get_data(self): 20 | return self._data 21 | 22 | def _set_data(self, x): 23 | self._data = x 24 | 25 | data = property(_get_data, _set_data) 26 | 27 | 28 | def test_data_not_none(): 29 | FooS = dill.copy(Foo) 30 | assert FooS.data.fget is not None 31 | assert FooS.data.fset is not None 32 | assert FooS.data.fdel is None 33 | 34 | 35 | def test_data_unchanged(): 36 | FooS = dill.copy(Foo) 37 | try: 38 | res = FooS().data 39 | except Exception: 40 | e = sys.exc_info()[1] 41 | raise AssertionError(str(e)) 42 | else: 43 | assert res == 1 44 | 45 | 46 | def test_data_changed(): 47 | FooS = dill.copy(Foo) 48 | try: 49 | f = FooS() 50 | f.data = 1024 51 | res = f.data 52 | except Exception: 53 | e = sys.exc_info()[1] 54 | raise AssertionError(str(e)) 55 | else: 56 | assert res == 1024 57 | 58 | 59 | if __name__ == '__main__': 60 | test_data_not_none() 61 | test_data_unchanged() 62 | test_data_changed() 63 | -------------------------------------------------------------------------------- /dill/tests/test_pycapsule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Author: Anirudh Vegesana (avegesan@cs.stanford.edu) 5 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | test pickling a PyCapsule object 10 | """ 11 | 12 | import dill 13 | import warnings 14 | 15 | test_pycapsule = None 16 | 17 | if dill._dill._testcapsule is not None: 18 | import ctypes 19 | def test_pycapsule(): 20 | name = ctypes.create_string_buffer(b'dill._testcapsule') 21 | capsule = dill._dill._PyCapsule_New( 22 | ctypes.cast(dill._dill._PyCapsule_New, ctypes.c_void_p), 23 | name, 24 | None 25 | ) 26 | with warnings.catch_warnings(): 27 | warnings.simplefilter("ignore") 28 | dill.copy(capsule) 29 | dill._testcapsule = capsule 30 | with warnings.catch_warnings(): 31 | warnings.simplefilter("ignore") 32 | dill.copy(capsule) 33 | dill._testcapsule = None 34 | try: 35 | with warnings.catch_warnings(): 36 | warnings.simplefilter("ignore", dill.PicklingWarning) 37 | dill.copy(capsule) 38 | except dill.UnpicklingError: 39 | pass 40 | else: 41 | raise AssertionError("Expected a different error") 42 | 43 | if __name__ == '__main__': 44 | if test_pycapsule is not None: 45 | test_pycapsule() 46 | -------------------------------------------------------------------------------- /dill/tests/test_recursive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2019-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import dill 9 | from functools import partial 10 | import warnings 11 | 12 | 13 | def copy(obj, byref=False, recurse=False): 14 | if byref: 15 | try: 16 | return dill.copy(obj, byref=byref, recurse=recurse) 17 | except Exception: 18 | pass 19 | else: 20 | raise AssertionError('Copy of %s with byref=True should have given a warning!' % (obj,)) 21 | 22 | warnings.simplefilter('ignore') 23 | val = dill.copy(obj, byref=byref, recurse=recurse) 24 | warnings.simplefilter('error') 25 | return val 26 | else: 27 | return dill.copy(obj, byref=byref, recurse=recurse) 28 | 29 | 30 | class obj1(object): 31 | def __init__(self): 32 | super(obj1, self).__init__() 33 | 34 | class obj2(object): 35 | def __init__(self): 36 | super(obj2, self).__init__() 37 | 38 | class obj3(object): 39 | super_ = super 40 | def __init__(self): 41 | obj3.super_(obj3, self).__init__() 42 | 43 | 44 | def test_super(): 45 | assert copy(obj1(), byref=True) 46 | assert copy(obj1(), byref=True, recurse=True) 47 | assert copy(obj1(), recurse=True) 48 | assert copy(obj1()) 49 | 50 | assert copy(obj2(), byref=True) 51 | assert copy(obj2(), byref=True, recurse=True) 52 | assert copy(obj2(), recurse=True) 53 | assert copy(obj2()) 54 | 55 | assert copy(obj3(), byref=True) 56 | assert copy(obj3(), byref=True, recurse=True) 57 | assert copy(obj3(), recurse=True) 58 | assert copy(obj3()) 59 | 60 | 61 | def get_trigger(model): 62 | pass 63 | 64 | class Machine(object): 65 | def __init__(self): 66 | self.child = Model() 67 | self.trigger = partial(get_trigger, self) 68 | self.child.trigger = partial(get_trigger, self.child) 69 | 70 | class Model(object): 71 | pass 72 | 73 | 74 | 75 | def test_partial(): 76 | assert copy(Machine(), byref=True) 77 | assert copy(Machine(), byref=True, recurse=True) 78 | assert copy(Machine(), recurse=True) 79 | assert copy(Machine()) 80 | 81 | 82 | class Machine2(object): 83 | def __init__(self): 84 | self.go = partial(self.member, self) 85 | def member(self, model): 86 | pass 87 | 88 | 89 | class SubMachine(Machine2): 90 | def __init__(self): 91 | super(SubMachine, self).__init__() 92 | 93 | 94 | def test_partials(): 95 | assert copy(SubMachine(), byref=True) 96 | assert copy(SubMachine(), byref=True, recurse=True) 97 | assert copy(SubMachine(), recurse=True) 98 | assert copy(SubMachine()) 99 | 100 | 101 | class obj4(object): 102 | def __init__(self): 103 | super(obj4, self).__init__() 104 | a = self 105 | class obj5(object): 106 | def __init__(self): 107 | super(obj5, self).__init__() 108 | self.a = a 109 | self.b = obj5() 110 | 111 | 112 | def test_circular_reference(): 113 | assert copy(obj4()) 114 | obj4_copy = dill.loads(dill.dumps(obj4())) 115 | assert type(obj4_copy) is type(obj4_copy).__init__.__closure__[0].cell_contents 116 | assert type(obj4_copy.b) is type(obj4_copy.b).__init__.__closure__[0].cell_contents 117 | 118 | 119 | def f(): 120 | def g(): 121 | return g 122 | return g 123 | 124 | 125 | def test_function_cells(): 126 | assert copy(f()) 127 | 128 | 129 | def fib(n): 130 | assert n >= 0 131 | if n <= 1: 132 | return n 133 | else: 134 | return fib(n-1) + fib(n-2) 135 | 136 | 137 | def test_recursive_function(): 138 | global fib 139 | fib2 = copy(fib, recurse=True) 140 | fib3 = copy(fib) 141 | fib4 = fib 142 | del fib 143 | assert fib2(5) == 5 144 | for _fib in (fib3, fib4): 145 | try: 146 | _fib(5) 147 | except Exception: 148 | # This is expected to fail because fib no longer exists 149 | pass 150 | else: 151 | raise AssertionError("Function fib shouldn't have been found") 152 | fib = fib4 153 | 154 | 155 | def collection_function_recursion(): 156 | d = {} 157 | def g(): 158 | return d 159 | d['g'] = g 160 | return g 161 | 162 | 163 | def test_collection_function_recursion(): 164 | g = copy(collection_function_recursion()) 165 | assert g()['g'] is g 166 | 167 | 168 | if __name__ == '__main__': 169 | with warnings.catch_warnings(): 170 | warnings.simplefilter('error') 171 | test_super() 172 | test_partial() 173 | test_partials() 174 | test_circular_reference() 175 | test_function_cells() 176 | test_recursive_function() 177 | test_collection_function_recursion() 178 | -------------------------------------------------------------------------------- /dill/tests/test_registered.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | """ 8 | test pickling registered objects 9 | """ 10 | 11 | import dill 12 | from dill._objects import failures, registered, succeeds 13 | import warnings 14 | warnings.filterwarnings('ignore') 15 | 16 | def check(d, ok=True): 17 | res = [] 18 | for k,v in d.items(): 19 | try: 20 | z = dill.copy(v) 21 | if ok: res.append(k) 22 | except: 23 | if not ok: res.append(k) 24 | return res 25 | 26 | fails = check(failures) 27 | try: 28 | assert not bool(fails) 29 | except AssertionError as e: 30 | print("FAILS: %s" % fails) 31 | raise e from None 32 | 33 | register = check(registered, ok=False) 34 | try: 35 | assert not bool(register) 36 | except AssertionError as e: 37 | print("REGISTER: %s" % register) 38 | raise e from None 39 | 40 | success = check(succeeds, ok=False) 41 | try: 42 | assert not bool(success) 43 | except AssertionError as e: 44 | print("SUCCESS: %s" % success) 45 | raise e from None 46 | 47 | import builtins 48 | import types 49 | q = dill._dill._reverse_typemap 50 | p = {k:v for k,v in q.items() if k not in vars(builtins) and k not in vars(types)} 51 | 52 | diff = set(p.keys()).difference(registered.keys()) 53 | try: 54 | assert not bool(diff) 55 | except AssertionError as e: 56 | print("DIFF: %s" % diff) 57 | raise e from None 58 | 59 | miss = set(registered.keys()).difference(p.keys()) 60 | try: 61 | assert not bool(miss) 62 | except AssertionError as e: 63 | print("MISS: %s" % miss) 64 | raise e from None 65 | -------------------------------------------------------------------------------- /dill/tests/test_restricted.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Kirill Makhonin (@kirillmakhonin) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | 11 | class RestrictedType: 12 | def __bool__(*args, **kwargs): 13 | raise Exception('Restricted function') 14 | 15 | __eq__ = __lt__ = __le__ = __ne__ = __gt__ = __ge__ = __hash__ = __bool__ 16 | 17 | glob_obj = RestrictedType() 18 | 19 | def restricted_func(): 20 | a = glob_obj 21 | 22 | def test_function_with_restricted_object(): 23 | deserialized = dill.loads(dill.dumps(restricted_func, recurse=True)) 24 | 25 | 26 | if __name__ == '__main__': 27 | test_function_with_restricted_object() 28 | -------------------------------------------------------------------------------- /dill/tests/test_selected.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | testing some selected object types 10 | """ 11 | 12 | import dill 13 | dill.settings['recurse'] = True 14 | 15 | verbose = False 16 | 17 | def test_dict_contents(): 18 | c = type.__dict__ 19 | for i,j in c.items(): 20 | #try: 21 | ok = dill.pickles(j) 22 | #except Exception: 23 | # print ("FAIL: %s with %s" % (i, dill.detect.errors(j))) 24 | if verbose: print ("%s: %s, %s" % (ok, type(j), j)) 25 | assert ok 26 | if verbose: print ("") 27 | 28 | def _g(x): yield x; 29 | 30 | def _f(): 31 | try: raise 32 | except Exception: 33 | from sys import exc_info 34 | e, er, tb = exc_info() 35 | return er, tb 36 | 37 | class _d(object): 38 | def _method(self): 39 | pass 40 | 41 | from dill import objects 42 | from dill import load_types 43 | load_types(pickleable=True,unpickleable=False) 44 | _newclass = objects['ClassObjectType'] 45 | # some clean-up #FIXME: should happen internal to dill 46 | objects['TemporaryFileType'].close() 47 | objects['FileType'].close() 48 | del objects 49 | 50 | # getset_descriptor for new-style classes (fails on '_method', if not __main__) 51 | def test_class_descriptors(): 52 | d = _d.__dict__ 53 | for i in d.values(): 54 | ok = dill.pickles(i) 55 | if verbose: print ("%s: %s, %s" % (ok, type(i), i)) 56 | assert ok 57 | if verbose: print ("") 58 | od = _newclass.__dict__ 59 | for i in od.values(): 60 | ok = dill.pickles(i) 61 | if verbose: print ("%s: %s, %s" % (ok, type(i), i)) 62 | assert ok 63 | if verbose: print ("") 64 | 65 | # (__main__) class instance for new-style classes 66 | def test_class(): 67 | o = _d() 68 | oo = _newclass() 69 | ok = dill.pickles(o) 70 | if verbose: print ("%s: %s, %s" % (ok, type(o), o)) 71 | assert ok 72 | ok = dill.pickles(oo) 73 | if verbose: print ("%s: %s, %s" % (ok, type(oo), oo)) 74 | assert ok 75 | if verbose: print ("") 76 | 77 | # frames, generators, and tracebacks (all depend on frame) 78 | def test_frame_related(): 79 | g = _g(1) 80 | f = g.gi_frame 81 | e,t = _f() 82 | _is = lambda ok: ok 83 | ok = dill.pickles(f) 84 | if verbose: print ("%s: %s, %s" % (ok, type(f), f)) 85 | assert not ok 86 | ok = dill.pickles(g) 87 | if verbose: print ("%s: %s, %s" % (ok, type(g), g)) 88 | assert _is(not ok) #XXX: dill fails 89 | ok = dill.pickles(t) 90 | if verbose: print ("%s: %s, %s" % (ok, type(t), t)) 91 | assert not ok #XXX: dill fails 92 | ok = dill.pickles(e) 93 | if verbose: print ("%s: %s, %s" % (ok, type(e), e)) 94 | assert ok 95 | if verbose: print ("") 96 | 97 | def test_typing(): 98 | import typing 99 | x = typing.Any 100 | assert x == dill.copy(x) 101 | x = typing.Dict[int, str] 102 | assert x == dill.copy(x) 103 | x = typing.List[int] 104 | assert x == dill.copy(x) 105 | x = typing.Tuple[int, str] 106 | assert x == dill.copy(x) 107 | x = typing.Tuple[int] 108 | assert x == dill.copy(x) 109 | x = typing.Tuple[()] 110 | assert x == dill.copy(x) 111 | x = typing.Tuple[()].copy_with(()) 112 | assert x == dill.copy(x) 113 | return 114 | 115 | 116 | if __name__ == '__main__': 117 | test_frame_related() 118 | test_dict_contents() 119 | test_class() 120 | test_class_descriptors() 121 | test_typing() 122 | -------------------------------------------------------------------------------- /dill/tests/test_session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Author: Leonardo Gama (@leogama) 4 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import atexit 9 | import os 10 | import sys 11 | import __main__ 12 | from contextlib import suppress 13 | from io import BytesIO 14 | 15 | import dill 16 | 17 | session_file = os.path.join(os.path.dirname(__file__), 'session-refimported-%s.pkl') 18 | 19 | ################### 20 | # Child process # 21 | ################### 22 | 23 | def _error_line(error, obj, refimported): 24 | import traceback 25 | line = traceback.format_exc().splitlines()[-2].replace('[obj]', '['+repr(obj)+']') 26 | return "while testing (with refimported=%s): %s" % (refimported, line.lstrip()) 27 | 28 | if __name__ == '__main__' and len(sys.argv) >= 3 and sys.argv[1] == '--child': 29 | # Test session loading in a fresh interpreter session. 30 | refimported = (sys.argv[2] == 'True') 31 | dill.load_module(session_file % refimported, module='__main__') 32 | 33 | def test_modules(refimported): 34 | # FIXME: In this test setting with CPython 3.7, 'calendar' is not included 35 | # in sys.modules, independent of the value of refimported. Tried to 36 | # run garbage collection just before loading the session with no luck. It 37 | # fails even when preceding them with 'import calendar'. Needed to run 38 | # these kinds of tests in a supbrocess. Failing test sample: 39 | # assert globals()['day_name'] is sys.modules['calendar'].__dict__['day_name'] 40 | try: 41 | for obj in ('json', 'url', 'local_mod', 'sax', 'dom'): 42 | assert globals()[obj].__name__ in sys.modules 43 | assert 'calendar' in sys.modules and 'cmath' in sys.modules 44 | import calendar, cmath 45 | 46 | for obj in ('Calendar', 'isleap'): 47 | assert globals()[obj] is sys.modules['calendar'].__dict__[obj] 48 | assert __main__.day_name.__module__ == 'calendar' 49 | if refimported: 50 | assert __main__.day_name is calendar.day_name 51 | 52 | assert __main__.complex_log is cmath.log 53 | 54 | except AssertionError as error: 55 | error.args = (_error_line(error, obj, refimported),) 56 | raise 57 | 58 | test_modules(refimported) 59 | sys.exit() 60 | 61 | #################### 62 | # Parent process # 63 | #################### 64 | 65 | # Create various kinds of objects to test different internal logics. 66 | 67 | ## Modules. 68 | import json # top-level module 69 | import urllib as url # top-level module under alias 70 | from xml import sax # submodule 71 | import xml.dom.minidom as dom # submodule under alias 72 | import test_dictviews as local_mod # non-builtin top-level module 73 | 74 | ## Imported objects. 75 | from calendar import Calendar, isleap, day_name # class, function, other object 76 | from cmath import log as complex_log # imported with alias 77 | 78 | ## Local objects. 79 | x = 17 80 | empty = None 81 | names = ['Alice', 'Bob', 'Carol'] 82 | def squared(x): return x**2 83 | cubed = lambda x: x**3 84 | class Person: 85 | def __init__(self, name, age): 86 | self.name = name 87 | self.age = age 88 | person = Person(names[0], x) 89 | class CalendarSubclass(Calendar): 90 | def weekdays(self): 91 | return [day_name[i] for i in self.iterweekdays()] 92 | cal = CalendarSubclass() 93 | selfref = __main__ 94 | 95 | # Setup global namespace for session saving tests. 96 | class TestNamespace: 97 | test_globals = globals().copy() 98 | def __init__(self, **extra): 99 | self.extra = extra 100 | def __enter__(self): 101 | self.backup = globals().copy() 102 | globals().clear() 103 | globals().update(self.test_globals) 104 | globals().update(self.extra) 105 | return self 106 | def __exit__(self, *exc_info): 107 | globals().clear() 108 | globals().update(self.backup) 109 | 110 | def _clean_up_cache(module): 111 | cached = module.__file__.split('.', 1)[0] + '.pyc' 112 | cached = module.__cached__ if hasattr(module, '__cached__') else cached 113 | pycache = os.path.join(os.path.dirname(module.__file__), '__pycache__') 114 | for remove, file in [(os.remove, cached), (os.removedirs, pycache)]: 115 | with suppress(OSError): 116 | remove(file) 117 | 118 | atexit.register(_clean_up_cache, local_mod) 119 | 120 | def _test_objects(main, globals_copy, refimported): 121 | try: 122 | main_dict = __main__.__dict__ 123 | global Person, person, Calendar, CalendarSubclass, cal, selfref 124 | 125 | for obj in ('json', 'url', 'local_mod', 'sax', 'dom'): 126 | assert globals()[obj].__name__ == globals_copy[obj].__name__ 127 | 128 | for obj in ('x', 'empty', 'names'): 129 | assert main_dict[obj] == globals_copy[obj] 130 | 131 | for obj in ['squared', 'cubed']: 132 | assert main_dict[obj].__globals__ is main_dict 133 | assert main_dict[obj](3) == globals_copy[obj](3) 134 | 135 | assert Person.__module__ == __main__.__name__ 136 | assert isinstance(person, Person) 137 | assert person.age == globals_copy['person'].age 138 | 139 | assert issubclass(CalendarSubclass, Calendar) 140 | assert isinstance(cal, CalendarSubclass) 141 | assert cal.weekdays() == globals_copy['cal'].weekdays() 142 | 143 | assert selfref is __main__ 144 | 145 | except AssertionError as error: 146 | error.args = (_error_line(error, obj, refimported),) 147 | raise 148 | 149 | def test_session_main(refimported): 150 | """test dump/load_module() for __main__, both in this process and in a subprocess""" 151 | extra_objects = {} 152 | if refimported: 153 | # Test unpickleable imported object in main. 154 | from sys import flags 155 | extra_objects['flags'] = flags 156 | 157 | with TestNamespace(**extra_objects) as ns: 158 | try: 159 | # Test session loading in a new session. 160 | dill.dump_module(session_file % refimported, refimported=refimported) 161 | from dill.tests.__main__ import python, shell, sp 162 | error = sp.call([python, __file__, '--child', str(refimported)], shell=shell) 163 | if error: sys.exit(error) 164 | finally: 165 | with suppress(OSError): 166 | os.remove(session_file % refimported) 167 | 168 | # Test session loading in the same session. 169 | session_buffer = BytesIO() 170 | dill.dump_module(session_buffer, refimported=refimported) 171 | session_buffer.seek(0) 172 | dill.load_module(session_buffer, module='__main__') 173 | ns.backup['_test_objects'](__main__, ns.backup, refimported) 174 | 175 | def test_session_other(): 176 | """test dump/load_module() for a module other than __main__""" 177 | import test_classdef as module 178 | atexit.register(_clean_up_cache, module) 179 | module.selfref = module 180 | dict_objects = [obj for obj in module.__dict__.keys() if not obj.startswith('__')] 181 | 182 | session_buffer = BytesIO() 183 | dill.dump_module(session_buffer, module) 184 | 185 | for obj in dict_objects: 186 | del module.__dict__[obj] 187 | 188 | session_buffer.seek(0) 189 | dill.load_module(session_buffer, module) 190 | 191 | assert all(obj in module.__dict__ for obj in dict_objects) 192 | assert module.selfref is module 193 | 194 | def test_runtime_module(): 195 | from types import ModuleType 196 | modname = '__runtime__' 197 | runtime = ModuleType(modname) 198 | runtime.x = 42 199 | 200 | mod = dill.session._stash_modules(runtime) 201 | if mod is not runtime: 202 | print("There are objects to save by referenece that shouldn't be:", 203 | mod.__dill_imported, mod.__dill_imported_as, mod.__dill_imported_top_level, 204 | file=sys.stderr) 205 | 206 | # This is also for code coverage, tests the use case of dump_module(refimported=True) 207 | # without imported objects in the namespace. It's a contrived example because 208 | # even dill can't be in it. This should work after fixing #462. 209 | session_buffer = BytesIO() 210 | dill.dump_module(session_buffer, module=runtime, refimported=True) 211 | session_dump = session_buffer.getvalue() 212 | 213 | # Pass a new runtime created module with the same name. 214 | runtime = ModuleType(modname) # empty 215 | return_val = dill.load_module(BytesIO(session_dump), module=runtime) 216 | assert return_val is None 217 | assert runtime.__name__ == modname 218 | assert runtime.x == 42 219 | assert runtime not in sys.modules.values() 220 | 221 | # Pass nothing as main. load_module() must create it. 222 | session_buffer.seek(0) 223 | runtime = dill.load_module(BytesIO(session_dump)) 224 | assert runtime.__name__ == modname 225 | assert runtime.x == 42 226 | assert runtime not in sys.modules.values() 227 | 228 | def test_refimported_imported_as(): 229 | import collections 230 | import concurrent.futures 231 | import types 232 | import typing 233 | mod = sys.modules['__test__'] = types.ModuleType('__test__') 234 | dill.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) 235 | mod.Dict = collections.UserDict # select by type 236 | mod.AsyncCM = typing.AsyncContextManager # select by __module__ 237 | mod.thread_exec = dill.executor # select by __module__ with regex 238 | 239 | session_buffer = BytesIO() 240 | dill.dump_module(session_buffer, mod, refimported=True) 241 | session_buffer.seek(0) 242 | mod = dill.load(session_buffer) 243 | del sys.modules['__test__'] 244 | 245 | assert set(mod.__dill_imported_as) == { 246 | ('collections', 'UserDict', 'Dict'), 247 | ('typing', 'AsyncContextManager', 'AsyncCM'), 248 | ('dill', 'executor', 'thread_exec'), 249 | } 250 | 251 | def test_load_module_asdict(): 252 | with TestNamespace(): 253 | session_buffer = BytesIO() 254 | dill.dump_module(session_buffer) 255 | 256 | global empty, names, x, y 257 | x = y = 0 # change x and create y 258 | del empty 259 | globals_state = globals().copy() 260 | 261 | session_buffer.seek(0) 262 | main_vars = dill.load_module_asdict(session_buffer) 263 | 264 | assert main_vars is not globals() 265 | assert globals() == globals_state 266 | 267 | assert main_vars['__name__'] == '__main__' 268 | assert main_vars['names'] == names 269 | assert main_vars['names'] is not names 270 | assert main_vars['x'] != x 271 | assert 'y' not in main_vars 272 | assert 'empty' in main_vars 273 | 274 | if __name__ == '__main__': 275 | test_session_main(refimported=False) 276 | test_session_main(refimported=True) 277 | test_session_other() 278 | test_runtime_module() 279 | test_refimported_imported_as() 280 | test_load_module_asdict() 281 | -------------------------------------------------------------------------------- /dill/tests/test_source.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | from dill.source import getsource, getname, _wrap, getimport 10 | from dill.source import importable 11 | from dill._dill import IS_PYPY 12 | 13 | import sys 14 | PY310b = 0x30a00b1 15 | 16 | f = lambda x: x**2 17 | def g(x): return f(x) - x 18 | 19 | def h(x): 20 | def g(x): return x 21 | return g(x) - x 22 | 23 | class Foo(object): 24 | def bar(self, x): 25 | return x*x+x 26 | _foo = Foo() 27 | 28 | def add(x,y): 29 | return x+y 30 | 31 | # yes, same as 'f', but things are tricky when it comes to pointers 32 | squared = lambda x:x**2 33 | 34 | class Bar: 35 | pass 36 | _bar = Bar() 37 | 38 | # inspect.getsourcelines # dill.source.getblocks 39 | def test_getsource(): 40 | assert getsource(f) == 'f = lambda x: x**2\n' 41 | assert getsource(g) == 'def g(x): return f(x) - x\n' 42 | assert getsource(h) == 'def h(x):\n def g(x): return x\n return g(x) - x\n' 43 | assert getname(f) == 'f' 44 | assert getname(g) == 'g' 45 | assert getname(h) == 'h' 46 | assert _wrap(f)(4) == 16 47 | assert _wrap(g)(4) == 12 48 | assert _wrap(h)(4) == 0 49 | 50 | assert getname(Foo) == 'Foo' 51 | assert getname(Bar) == 'Bar' 52 | assert getsource(Bar) == 'class Bar:\n pass\n' 53 | assert getsource(Foo) == 'class Foo(object):\n def bar(self, x):\n return x*x+x\n' 54 | #XXX: add getsource for _foo, _bar 55 | 56 | # test itself 57 | def test_itself(): 58 | assert getimport(getimport)=='from dill.source import getimport\n' 59 | 60 | # builtin functions and objects 61 | def test_builtin(): 62 | assert getimport(pow) == 'pow\n' 63 | assert getimport(100) == '100\n' 64 | assert getimport(True) == 'True\n' 65 | assert getimport(pow, builtin=True) == 'from builtins import pow\n' 66 | assert getimport(100, builtin=True) == '100\n' 67 | assert getimport(True, builtin=True) == 'True\n' 68 | # this is kinda BS... you can't import a None 69 | assert getimport(None) == 'None\n' 70 | assert getimport(None, builtin=True) == 'None\n' 71 | 72 | 73 | # other imported functions 74 | def test_imported(): 75 | from math import sin 76 | assert getimport(sin) == 'from math import sin\n' 77 | 78 | # interactively defined functions 79 | def test_dynamic(): 80 | assert getimport(add) == 'from %s import add\n' % __name__ 81 | # interactive lambdas 82 | assert getimport(squared) == 'from %s import squared\n' % __name__ 83 | 84 | # classes and class instances 85 | def test_classes(): 86 | from io import BytesIO as StringIO 87 | y = "from _io import BytesIO\n" 88 | x = y if (IS_PYPY or sys.hexversion >= PY310b) else "from io import BytesIO\n" 89 | s = StringIO() 90 | 91 | assert getimport(StringIO) == x 92 | assert getimport(s) == y 93 | # interactively defined classes and class instances 94 | assert getimport(Foo) == 'from %s import Foo\n' % __name__ 95 | assert getimport(_foo) == 'from %s import Foo\n' % __name__ 96 | 97 | 98 | # test importable 99 | def test_importable(): 100 | assert importable(add, source=False) == 'from %s import add\n' % __name__ 101 | assert importable(squared, source=False) == 'from %s import squared\n' % __name__ 102 | assert importable(Foo, source=False) == 'from %s import Foo\n' % __name__ 103 | assert importable(Foo.bar, source=False) == 'from %s import bar\n' % __name__ 104 | assert importable(_foo.bar, source=False) == 'from %s import bar\n' % __name__ 105 | assert importable(None, source=False) == 'None\n' 106 | assert importable(100, source=False) == '100\n' 107 | 108 | assert importable(add, source=True) == 'def add(x,y):\n return x+y\n' 109 | assert importable(squared, source=True) == 'squared = lambda x:x**2\n' 110 | assert importable(None, source=True) == 'None\n' 111 | assert importable(Bar, source=True) == 'class Bar:\n pass\n' 112 | assert importable(Foo, source=True) == 'class Foo(object):\n def bar(self, x):\n return x*x+x\n' 113 | assert importable(Foo.bar, source=True) == 'def bar(self, x):\n return x*x+x\n' 114 | assert importable(Foo.bar, source=False) == 'from %s import bar\n' % __name__ 115 | assert importable(Foo.bar, alias='memo', source=False) == 'from %s import bar as memo\n' % __name__ 116 | assert importable(Foo, alias='memo', source=False) == 'from %s import Foo as memo\n' % __name__ 117 | assert importable(squared, alias='memo', source=False) == 'from %s import squared as memo\n' % __name__ 118 | assert importable(squared, alias='memo', source=True) == 'memo = squared = lambda x:x**2\n' 119 | assert importable(add, alias='memo', source=True) == 'def add(x,y):\n return x+y\n\nmemo = add\n' 120 | assert importable(None, alias='memo', source=True) == 'memo = None\n' 121 | assert importable(100, alias='memo', source=True) == 'memo = 100\n' 122 | assert importable(add, builtin=True, source=False) == 'from %s import add\n' % __name__ 123 | assert importable(squared, builtin=True, source=False) == 'from %s import squared\n' % __name__ 124 | assert importable(Foo, builtin=True, source=False) == 'from %s import Foo\n' % __name__ 125 | assert importable(Foo.bar, builtin=True, source=False) == 'from %s import bar\n' % __name__ 126 | assert importable(_foo.bar, builtin=True, source=False) == 'from %s import bar\n' % __name__ 127 | assert importable(None, builtin=True, source=False) == 'None\n' 128 | assert importable(100, builtin=True, source=False) == '100\n' 129 | 130 | 131 | def test_numpy(): 132 | try: 133 | import numpy as np 134 | y = np.array 135 | x = y([1,2,3]) 136 | assert importable(x, source=False) == 'from numpy import array\narray([1, 2, 3])\n' 137 | assert importable(y, source=False) == 'from %s import array\n' % y.__module__ 138 | assert importable(x, source=True) == 'from numpy import array\narray([1, 2, 3])\n' 139 | assert importable(y, source=True) == 'from %s import array\n' % y.__module__ 140 | y = np.int64 141 | x = y(0) 142 | assert importable(x, source=False) == 'from numpy import int64\nint64(0)\n' 143 | assert importable(y, source=False) == 'from %s import int64\n' % y.__module__ 144 | assert importable(x, source=True) == 'from numpy import int64\nint64(0)\n' 145 | assert importable(y, source=True) == 'from %s import int64\n' % y.__module__ 146 | y = np.bool_ 147 | x = y(0) 148 | import warnings 149 | with warnings.catch_warnings(): 150 | warnings.filterwarnings('ignore', category=FutureWarning) 151 | warnings.filterwarnings('ignore', category=DeprecationWarning) 152 | if hasattr(np, 'bool'): b = 'bool_' if np.bool is bool else 'bool' 153 | else: b = 'bool_' 154 | assert importable(x, source=False) == 'from numpy import %s\n%s(False)\n' % (b,b) 155 | assert importable(y, source=False) == 'from %s import %s\n' % (y.__module__,b) 156 | assert importable(x, source=True) == 'from numpy import %s\n%s(False)\n' % (b,b) 157 | assert importable(y, source=True) == 'from %s import %s\n' % (y.__module__,b) 158 | except ImportError: pass 159 | 160 | #NOTE: if before getimport(pow), will cause pow to throw AssertionError 161 | def test_foo(): 162 | assert importable(_foo, source=True).startswith("import dill\nclass Foo(object):\n def bar(self, x):\n return x*x+x\ndill.loads(") 163 | 164 | if __name__ == '__main__': 165 | test_getsource() 166 | test_itself() 167 | test_builtin() 168 | test_imported() 169 | test_dynamic() 170 | test_classes() 171 | test_importable() 172 | test_numpy() 173 | test_foo() 174 | -------------------------------------------------------------------------------- /dill/tests/test_sources.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @uqfoundation) 4 | # Copyright (c) 2024-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | """ 8 | check that dill.source performs as expected with changes to locals in 3.13.0b1 9 | see: https://github.com/python/cpython/issues/118888 10 | """ 11 | # repeat functions from test_source.py 12 | f = lambda x: x**2 13 | def g(x): return f(x) - x 14 | 15 | def h(x): 16 | def g(x): return x 17 | return g(x) - x 18 | 19 | class Foo(object): 20 | def bar(self, x): 21 | return x*x+x 22 | _foo = Foo() 23 | 24 | def add(x,y): 25 | return x+y 26 | 27 | squared = lambda x:x**2 28 | 29 | class Bar: 30 | pass 31 | _bar = Bar() 32 | 33 | # repeat, but from test_source.py 34 | import test_source as ts 35 | 36 | # test objects created in other test modules 37 | import test_mixins as tm 38 | 39 | import dill.source as ds 40 | 41 | 42 | def test_isfrommain(): 43 | assert ds.isfrommain(add) == True 44 | assert ds.isfrommain(squared) == True 45 | assert ds.isfrommain(Bar) == True 46 | assert ds.isfrommain(_bar) == True 47 | assert ds.isfrommain(ts.add) == False 48 | assert ds.isfrommain(ts.squared) == False 49 | assert ds.isfrommain(ts.Bar) == False 50 | assert ds.isfrommain(ts._bar) == False 51 | assert ds.isfrommain(tm.quad) == False 52 | assert ds.isfrommain(tm.double_add) == False 53 | assert ds.isfrommain(tm.quadratic) == False 54 | assert ds.isdynamic(add) == False 55 | assert ds.isdynamic(squared) == False 56 | assert ds.isdynamic(ts.add) == False 57 | assert ds.isdynamic(ts.squared) == False 58 | assert ds.isdynamic(tm.double_add) == False 59 | assert ds.isdynamic(tm.quadratic) == False 60 | 61 | 62 | def test_matchlambda(): 63 | assert ds._matchlambda(f, 'f = lambda x: x**2\n') 64 | assert ds._matchlambda(squared, 'squared = lambda x:x**2\n') 65 | assert ds._matchlambda(ts.f, 'f = lambda x: x**2\n') 66 | assert ds._matchlambda(ts.squared, 'squared = lambda x:x**2\n') 67 | 68 | 69 | def test_findsource(): 70 | lines, lineno = ds.findsource(add) 71 | assert lines[lineno] == 'def add(x,y):\n' 72 | lines, lineno = ds.findsource(ts.add) 73 | assert lines[lineno] == 'def add(x,y):\n' 74 | lines, lineno = ds.findsource(squared) 75 | assert lines[lineno] == 'squared = lambda x:x**2\n' 76 | lines, lineno = ds.findsource(ts.squared) 77 | assert lines[lineno] == 'squared = lambda x:x**2\n' 78 | lines, lineno = ds.findsource(Bar) 79 | assert lines[lineno] == 'class Bar:\n' 80 | lines, lineno = ds.findsource(ts.Bar) 81 | assert lines[lineno] == 'class Bar:\n' 82 | lines, lineno = ds.findsource(_bar) 83 | assert lines[lineno] == 'class Bar:\n' 84 | lines, lineno = ds.findsource(ts._bar) 85 | assert lines[lineno] == 'class Bar:\n' 86 | lines, lineno = ds.findsource(tm.quad) 87 | assert lines[lineno] == 'def quad(a=1, b=1, c=0):\n' 88 | lines, lineno = ds.findsource(tm.double_add) 89 | assert lines[lineno] == ' def func(*args, **kwds):\n' 90 | lines, lineno = ds.findsource(tm.quadratic) 91 | assert lines[lineno] == ' def dec(f):\n' 92 | 93 | 94 | def test_getsourcelines(): 95 | assert ''.join(ds.getsourcelines(add)[0]) == 'def add(x,y):\n return x+y\n' 96 | assert ''.join(ds.getsourcelines(ts.add)[0]) == 'def add(x,y):\n return x+y\n' 97 | assert ''.join(ds.getsourcelines(squared)[0]) == 'squared = lambda x:x**2\n' 98 | assert ''.join(ds.getsourcelines(ts.squared)[0]) == 'squared = lambda x:x**2\n' 99 | assert ''.join(ds.getsourcelines(Bar)[0]) == 'class Bar:\n pass\n' 100 | assert ''.join(ds.getsourcelines(ts.Bar)[0]) == 'class Bar:\n pass\n' 101 | assert ''.join(ds.getsourcelines(_bar)[0]) == 'class Bar:\n pass\n' #XXX: ? 102 | assert ''.join(ds.getsourcelines(ts._bar)[0]) == 'class Bar:\n pass\n' #XXX: ? 103 | assert ''.join(ds.getsourcelines(tm.quad)[0]) == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n' 104 | assert ''.join(ds.getsourcelines(tm.quadratic)[0]) == ' def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' 105 | assert ''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]) == 'def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' 106 | assert ''.join(ds.getsourcelines(tm.quadratic, enclosing=True)[0]) == 'def quad_factory(a=1,b=1,c=0):\n def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n return dec\n' 107 | assert ''.join(ds.getsourcelines(tm.double_add)[0]) == ' def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n' 108 | assert ''.join(ds.getsourcelines(tm.double_add, enclosing=True)[0]) == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n' 109 | 110 | 111 | def test_indent(): 112 | assert ds.outdent(''.join(ds.getsourcelines(tm.quadratic)[0])) == ''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]) 113 | assert ds.indent(''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]), 2) == ''.join(ds.getsourcelines(tm.quadratic)[0]) 114 | 115 | 116 | def test_dumpsource(): 117 | local = {} 118 | exec(ds.dumpsource(add, alias='raw'), {}, local) 119 | exec(ds.dumpsource(ts.add, alias='mod'), {}, local) 120 | assert local['raw'](1,2) == local['mod'](1,2) 121 | exec(ds.dumpsource(squared, alias='raw'), {}, local) 122 | exec(ds.dumpsource(ts.squared, alias='mod'), {}, local) 123 | assert local['raw'](3) == local['mod'](3) 124 | assert ds._wrap(add)(1,2) == ds._wrap(ts.add)(1,2) 125 | assert ds._wrap(squared)(3) == ds._wrap(ts.squared)(3) 126 | 127 | 128 | def test_name(): 129 | assert ds._namespace(add) == ds.getname(add, fqn=True).split('.') 130 | assert ds._namespace(ts.add) == ds.getname(ts.add, fqn=True).split('.') 131 | assert ds._namespace(squared) == ds.getname(squared, fqn=True).split('.') 132 | assert ds._namespace(ts.squared) == ds.getname(ts.squared, fqn=True).split('.') 133 | assert ds._namespace(Bar) == ds.getname(Bar, fqn=True).split('.') 134 | assert ds._namespace(ts.Bar) == ds.getname(ts.Bar, fqn=True).split('.') 135 | assert ds._namespace(tm.quad) == ds.getname(tm.quad, fqn=True).split('.') 136 | #XXX: the following also works, however behavior may be wrong for nested functions 137 | #assert ds._namespace(tm.double_add) == ds.getname(tm.double_add, fqn=True).split('.') 138 | #assert ds._namespace(tm.quadratic) == ds.getname(tm.quadratic, fqn=True).split('.') 139 | assert ds.getname(add) == 'add' 140 | assert ds.getname(ts.add) == 'add' 141 | assert ds.getname(squared) == 'squared' 142 | assert ds.getname(ts.squared) == 'squared' 143 | assert ds.getname(Bar) == 'Bar' 144 | assert ds.getname(ts.Bar) == 'Bar' 145 | assert ds.getname(tm.quad) == 'quad' 146 | assert ds.getname(tm.double_add) == 'func' #XXX: ? 147 | assert ds.getname(tm.quadratic) == 'dec' #XXX: ? 148 | 149 | 150 | def test_getimport(): 151 | local = {} 152 | exec(ds.getimport(add, alias='raw'), {}, local) 153 | exec(ds.getimport(ts.add, alias='mod'), {}, local) 154 | assert local['raw'](1,2) == local['mod'](1,2) 155 | exec(ds.getimport(squared, alias='raw'), {}, local) 156 | exec(ds.getimport(ts.squared, alias='mod'), {}, local) 157 | assert local['raw'](3) == local['mod'](3) 158 | exec(ds.getimport(Bar, alias='raw'), {}, local) 159 | exec(ds.getimport(ts.Bar, alias='mod'), {}, local) 160 | assert ds.getname(local['raw']) == ds.getname(local['mod']) 161 | exec(ds.getimport(tm.quad, alias='mod'), {}, local) 162 | assert local['mod']()(sum)([1,2,3]) == tm.quad()(sum)([1,2,3]) 163 | #FIXME: wrong results for nested functions (e.g. tm.double_add, tm.quadratic) 164 | 165 | 166 | def test_importable(): 167 | assert ds.importable(add, source=False) == ds.getimport(add) 168 | assert ds.importable(add) == ds.getsource(add) 169 | assert ds.importable(squared, source=False) == ds.getimport(squared) 170 | assert ds.importable(squared) == ds.getsource(squared) 171 | assert ds.importable(Bar, source=False) == ds.getimport(Bar) 172 | assert ds.importable(Bar) == ds.getsource(Bar) 173 | assert ds.importable(ts.add) == ds.getimport(ts.add) 174 | assert ds.importable(ts.add, source=True) == ds.getsource(ts.add) 175 | assert ds.importable(ts.squared) == ds.getimport(ts.squared) 176 | assert ds.importable(ts.squared, source=True) == ds.getsource(ts.squared) 177 | assert ds.importable(ts.Bar) == ds.getimport(ts.Bar) 178 | assert ds.importable(ts.Bar, source=True) == ds.getsource(ts.Bar) 179 | 180 | 181 | if __name__ == '__main__': 182 | test_isfrommain() 183 | test_matchlambda() 184 | test_findsource() 185 | test_getsourcelines() 186 | test_indent() 187 | test_dumpsource() 188 | test_name() 189 | test_getimport() 190 | test_importable() 191 | -------------------------------------------------------------------------------- /dill/tests/test_temp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import sys 10 | from dill.temp import dump, dump_source, dumpIO, dumpIO_source 11 | from dill.temp import load, load_source, loadIO, loadIO_source 12 | WINDOWS = sys.platform[:3] == 'win' 13 | 14 | 15 | f = lambda x: x**2 16 | x = [1,2,3,4,5] 17 | 18 | # source code to tempfile 19 | def test_code_to_tempfile(): 20 | if not WINDOWS: #see: https://bugs.python.org/issue14243 21 | pyfile = dump_source(f, alias='_f') 22 | _f = load_source(pyfile) 23 | assert _f(4) == f(4) 24 | 25 | # source code to stream 26 | def test_code_to_stream(): 27 | pyfile = dumpIO_source(f, alias='_f') 28 | _f = loadIO_source(pyfile) 29 | assert _f(4) == f(4) 30 | 31 | # pickle to tempfile 32 | def test_pickle_to_tempfile(): 33 | if not WINDOWS: #see: https://bugs.python.org/issue14243 34 | dumpfile = dump(x) 35 | _x = load(dumpfile) 36 | assert _x == x 37 | 38 | # pickle to stream 39 | def test_pickle_to_stream(): 40 | dumpfile = dumpIO(x) 41 | _x = loadIO(dumpfile) 42 | assert _x == x 43 | 44 | ### now testing the objects ### 45 | f = lambda x: x**2 46 | def g(x): return f(x) - x 47 | 48 | def h(x): 49 | def g(x): return x 50 | return g(x) - x 51 | 52 | class Foo(object): 53 | def bar(self, x): 54 | return x*x+x 55 | _foo = Foo() 56 | 57 | def add(x,y): 58 | return x+y 59 | 60 | # yes, same as 'f', but things are tricky when it comes to pointers 61 | squared = lambda x:x**2 62 | 63 | class Bar: 64 | pass 65 | _bar = Bar() 66 | 67 | 68 | # test function-type objects that take 2 args 69 | def test_two_arg_functions(): 70 | for obj in [add]: 71 | pyfile = dumpIO_source(obj, alias='_obj') 72 | _obj = loadIO_source(pyfile) 73 | assert _obj(4,2) == obj(4,2) 74 | 75 | # test function-type objects that take 1 arg 76 | def test_one_arg_functions(): 77 | for obj in [g, h, squared]: 78 | pyfile = dumpIO_source(obj, alias='_obj') 79 | _obj = loadIO_source(pyfile) 80 | assert _obj(4) == obj(4) 81 | 82 | # test instance-type objects 83 | #for obj in [_bar, _foo]: 84 | # pyfile = dumpIO_source(obj, alias='_obj') 85 | # _obj = loadIO_source(pyfile) 86 | # assert type(_obj) == type(obj) 87 | 88 | # test the rest of the objects 89 | def test_the_rest(): 90 | for obj in [Bar, Foo, Foo.bar, _foo.bar]: 91 | pyfile = dumpIO_source(obj, alias='_obj') 92 | _obj = loadIO_source(pyfile) 93 | assert _obj.__name__ == obj.__name__ 94 | 95 | 96 | if __name__ == '__main__': 97 | test_code_to_tempfile() 98 | test_code_to_stream() 99 | test_pickle_to_tempfile() 100 | test_pickle_to_stream() 101 | test_two_arg_functions() 102 | test_one_arg_functions() 103 | test_the_rest() 104 | -------------------------------------------------------------------------------- /dill/tests/test_threads.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2024-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | import dill 9 | dill.settings['recurse'] = True 10 | 11 | 12 | def test_new_thread(): 13 | import threading 14 | t = threading.Thread() 15 | t_ = dill.copy(t) 16 | assert t.is_alive() == t_.is_alive() 17 | for i in ['daemon','name','ident','native_id']: 18 | if hasattr(t, i): 19 | assert getattr(t, i) == getattr(t_, i) 20 | 21 | def test_run_thread(): 22 | import threading 23 | t = threading.Thread() 24 | t.start() 25 | t_ = dill.copy(t) 26 | assert t.is_alive() == t_.is_alive() 27 | for i in ['daemon','name','ident','native_id']: 28 | if hasattr(t, i): 29 | assert getattr(t, i) == getattr(t_, i) 30 | 31 | def test_join_thread(): 32 | import threading 33 | t = threading.Thread() 34 | t.start() 35 | t.join() 36 | t_ = dill.copy(t) 37 | assert t.is_alive() == t_.is_alive() 38 | for i in ['daemon','name','ident','native_id']: 39 | if hasattr(t, i): 40 | assert getattr(t, i) == getattr(t_, i) 41 | 42 | 43 | if __name__ == '__main__': 44 | test_new_thread() 45 | test_run_thread() 46 | test_join_thread() 47 | -------------------------------------------------------------------------------- /dill/tests/test_weakref.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import dill 10 | dill.settings['recurse'] = True 11 | import weakref 12 | 13 | class _class: 14 | def _method(self): 15 | pass 16 | 17 | class _callable_class: 18 | def __call__(self): 19 | pass 20 | 21 | def _function(): 22 | pass 23 | 24 | 25 | def test_weakref(): 26 | o = _class() 27 | oc = _callable_class() 28 | f = _function 29 | x = _class 30 | 31 | # ReferenceType 32 | r = weakref.ref(o) 33 | d_r = weakref.ref(_class()) 34 | fr = weakref.ref(f) 35 | xr = weakref.ref(x) 36 | 37 | # ProxyType 38 | p = weakref.proxy(o) 39 | d_p = weakref.proxy(_class()) 40 | 41 | # CallableProxyType 42 | cp = weakref.proxy(oc) 43 | d_cp = weakref.proxy(_callable_class()) 44 | fp = weakref.proxy(f) 45 | xp = weakref.proxy(x) 46 | 47 | objlist = [r,d_r,fr,xr, p,d_p, cp,d_cp,fp,xp] 48 | #dill.detect.trace(True) 49 | 50 | for obj in objlist: 51 | res = dill.detect.errors(obj) 52 | if res: 53 | print ("%r:\n %s" % (obj, res)) 54 | # else: 55 | # print ("PASS: %s" % obj) 56 | assert not res 57 | 58 | def test_dictproxy(): 59 | from dill._dill import DictProxyType 60 | try: 61 | m = DictProxyType({"foo": "bar"}) 62 | except Exception: 63 | m = type.__dict__ 64 | mp = dill.copy(m) 65 | assert mp.items() == m.items() 66 | 67 | 68 | if __name__ == '__main__': 69 | test_weakref() 70 | from dill._dill import IS_PYPY 71 | if not IS_PYPY: 72 | test_dictproxy() 73 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = dill 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Internal variables 12 | ALLSPHINXOPTS = $(SPHINXOPTS) $(SOURCEDIR) 13 | 14 | # Put it first so that "make" without argument is like "make help". 15 | help: 16 | @echo "Please use \`make html' to generate standalone HTML files" 17 | 18 | .PHONY: help clean html Makefile 19 | 20 | clean: 21 | -rm -rf $(BUILDDIR) 22 | 23 | html: 24 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR) 25 | -rm -f $(BUILDDIR)/../../scripts/_*py 26 | -rm -f $(BUILDDIR)/../../scripts/_*pyc 27 | -rm -rf $(BUILDDIR)/../../scripts/__pycache__ 28 | 29 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Packages required to build docs 2 | # dependencies pinned as: 3 | # https://github.com/readthedocs/readthedocs.org/blob/543f389ddd184ff91dac6a7b808dd21697292fd5/requirements/docs.txt 4 | 5 | alabaster==1.0.0 6 | anyio==4.8.0 7 | babel==2.17.0 8 | certifi==2025.1.31 9 | charset-normalizer==3.4.1 10 | click==8.1.8 11 | colorama==0.4.6 12 | docutils==0.21.2 13 | exceptiongroup==1.2.2 14 | h11==0.16.0 15 | idna==3.10 16 | imagesize==1.4.1 17 | jinja2==3.1.6 18 | markdown-it-py==3.0.0 19 | markupsafe==3.0.2 20 | mdit-py-plugins==0.4.2 21 | mdurl==0.1.2 22 | myst-parser==4.0.0 23 | packaging==24.2 24 | pygments==2.19.1 25 | pyyaml==6.0.2 26 | requests==2.32.3 27 | six==1.17.0 28 | sniffio==1.3.1 29 | snowballstemmer==2.2.0 30 | sphinx==8.1.3 31 | sphinx-autobuild==2024.10.3 32 | sphinx-copybutton==0.5.2 33 | sphinx-design==0.6.1 34 | sphinx-intl==2.3.1 35 | sphinx-multiproject==1.0.0 36 | sphinx-notfound-page==1.1.0 37 | sphinx-prompt==1.9.0 38 | sphinx-rtd-theme==3.0.2 39 | sphinx-tabs==3.4.7 40 | sphinxcontrib-applehelp==2.0.0 41 | sphinxcontrib-devhelp==2.0.0 42 | sphinxcontrib-htmlhelp==2.1.0 43 | sphinxcontrib-httpdomain==1.8.1 44 | sphinxcontrib-jquery==4.1 45 | sphinxcontrib-jsmath==1.0.1 46 | sphinxcontrib-qthelp==2.0.0 47 | sphinxcontrib-serializinghtml==2.0.0 48 | sphinxext-opengraph==0.9.1 49 | starlette==0.45.3 50 | tomli==2.2.1 51 | typing-extensions==4.12.2 52 | urllib3==2.3.0 53 | uvicorn==0.34.0 54 | watchfiles==1.0.4 55 | websockets==14.2 56 | -------------------------------------------------------------------------------- /docs/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | div.sphinxsidebar { 2 | height: 100%; /* 100vh */ 3 | overflow: auto; /* overflow-y */ 4 | } 5 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # dill documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Aug 6 06:50:58 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | import os 20 | from datetime import datetime 21 | import sys 22 | sys.path.insert(0, os.path.abspath('../..')) 23 | scripts = os.path.abspath('../../scripts') 24 | sys.path.insert(0, scripts) 25 | try: 26 | os.symlink(scripts+os.sep+'undill', scripts+os.sep+'_undill.py') 27 | os.symlink(scripts+os.sep+'get_objgraph', scripts+os.sep+'_get_objgraph.py') 28 | os.symlink(scripts+os.sep+'get_gprof', scripts+os.sep+'_get_gprof.py') 29 | except: 30 | pass 31 | 32 | # Import the project 33 | import dill 34 | 35 | # -- General configuration ------------------------------------------------ 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # 39 | # needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = ['sphinx.ext.autodoc', 45 | 'sphinx.ext.intersphinx', 46 | 'sphinx.ext.imgmath', 47 | 'sphinx.ext.ifconfig', 48 | 'sphinx.ext.napoleon'] 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # The suffix(es) of source filenames. 54 | # You can specify multiple suffix as a list of string: 55 | # 56 | # source_suffix = ['.rst', '.md'] 57 | source_suffix = '.rst' 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # General information about the project. 63 | project = 'dill' 64 | year = datetime.now().year 65 | copyright = '%d, The Uncertainty Quantification Foundation' % year 66 | author = 'Mike McKerns' 67 | 68 | # extension config 69 | github_project_url = "https://github.com/uqfoundation/dill" 70 | autoclass_content = 'both' 71 | autodoc_default_options = { 72 | 'members': True, 73 | 'undoc-members': True, 74 | 'private-members': True, 75 | 'special-members': True, 76 | 'show-inheritance': True, 77 | 'imported-members': True, 78 | 'exclude-members': ( #NOTE: this is a single string concatenation 79 | '__dict__,' # may be verbose 80 | '__slots__,' 81 | '__weakref__,' 82 | '__module__,' 83 | '_abc_impl,' 84 | '__init__,' # redundant with class docstring by "autoclass_content=both" 85 | '__annotations__,' # redundant with signature documentation 86 | '__dataclass_fields__,' # redundant automatic attribute 87 | ) 88 | } 89 | autodoc_typehints = 'description' 90 | autodoc_typehints_format = 'short' 91 | napoleon_include_private_with_doc = False 92 | napoleon_include_special_with_doc = True 93 | napoleon_use_ivar = True 94 | napoleon_use_param = True 95 | 96 | # The version info for the project you're documenting, acts as replacement for 97 | # |version| and |release|, also used in various other places throughout the 98 | # built documents. 99 | # 100 | # The short X.Y version. 101 | version = dill.__version__ 102 | # The full version, including alpha/beta/rc tags. 103 | release = version 104 | 105 | # The language for content autogenerated by Sphinx. Refer to documentation 106 | # for a list of supported languages. 107 | # 108 | # This is also used if you do content translation via gettext catalogs. 109 | # Usually you set "language" from the command line for these cases. 110 | language = 'en' 111 | 112 | # List of patterns, relative to source directory, that match files and 113 | # directories to ignore when looking for source files. 114 | # This patterns also effect to html_static_path and html_extra_path 115 | exclude_patterns = [] 116 | 117 | # The name of the Pygments (syntax highlighting) style to use. 118 | pygments_style = 'sphinx' 119 | 120 | # If true, `todo` and `todoList` produce output, else they produce nothing. 121 | todo_include_todos = False 122 | 123 | # Configure how the modules, functions, etc names look 124 | add_module_names = False 125 | modindex_common_prefix = ['dill.'] 126 | 127 | 128 | # -- Options for HTML output ---------------------------------------------- 129 | 130 | # on_rtd is whether we are on readthedocs.io 131 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 132 | 133 | # The theme to use for HTML and HTML Help pages. See the documentation for 134 | # a list of builtin themes. 135 | # 136 | if not on_rtd: 137 | html_theme = 'alabaster' #'bizstyle' 138 | html_css_files = ['css/custom.css',] 139 | #import sphinx_rtd_theme 140 | #html_theme = 'sphinx_rtd_theme' 141 | #html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 142 | else: 143 | html_theme = 'sphinx_rtd_theme' 144 | 145 | # Theme options are theme-specific and customize the look and feel of a theme 146 | # further. For a list of options available for each theme, see the 147 | # documentation. 148 | # 149 | html_theme_options = { 150 | 'github_user': 'uqfoundation', 151 | 'github_repo': 'dill', 152 | 'github_button': False, 153 | 'github_banner': True, 154 | 'travis_button': True, 155 | 'codecov_button': True, 156 | 'donate_url': 'http://uqfoundation.org/pages/donate.html', 157 | 'gratipay_user': False, # username 158 | 'extra_nav_links': {'Module Index': 'py-modindex.html'}, 159 | # 'show_related': True, 160 | # 'globaltoc_collapse': True, 161 | 'globaltoc_maxdepth': 4, 162 | 'show_powered_by': False 163 | } 164 | 165 | # Add any paths that contain custom static files (such as style sheets) here, 166 | # relative to this directory. They are copied after the builtin static files, 167 | # so a file named "default.css" will overwrite the builtin "default.css". 168 | html_static_path = ['_static'] 169 | 170 | # Custom sidebar templates, must be a dictionary that maps document names 171 | # to template names. 172 | # 173 | # This is required for the alabaster theme 174 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 175 | if on_rtd: 176 | toc_style = 'localtoc.html', # display the toctree 177 | else: 178 | toc_style = 'globaltoc.html', # collapse the toctree 179 | html_sidebars = { 180 | '**': [ 181 | 'about.html', 182 | 'donate.html', 183 | 'searchbox.html', 184 | # 'navigation.html', 185 | toc_style, # defined above 186 | 'relations.html', # needs 'show_related':True option to display 187 | ] 188 | } 189 | 190 | # -- Options for HTMLHelp output ------------------------------------------ 191 | 192 | # Output file base name for HTML help builder. 193 | htmlhelp_basename = 'dilldoc' 194 | 195 | # Logo for sidebar 196 | html_logo = 'pathos.png' 197 | 198 | 199 | # -- Options for LaTeX output --------------------------------------------- 200 | 201 | latex_elements = { 202 | # The paper size ('letterpaper' or 'a4paper'). 203 | # 204 | # 'papersize': 'letterpaper', 205 | 206 | # The font size ('10pt', '11pt' or '12pt'). 207 | # 208 | # 'pointsize': '10pt', 209 | 210 | # Additional stuff for the LaTeX preamble. 211 | # 212 | # 'preamble': '', 213 | 214 | # Latex figure (float) alignment 215 | # 216 | # 'figure_align': 'htbp', 217 | } 218 | 219 | # Grouping the document tree into LaTeX files. List of tuples 220 | # (source start file, target name, title, 221 | # author, documentclass [howto, manual, or own class]). 222 | latex_documents = [ 223 | (master_doc, 'dill.tex', 'dill Documentation', 224 | 'Mike McKerns', 'manual'), 225 | ] 226 | 227 | 228 | # -- Options for manual page output --------------------------------------- 229 | 230 | # One entry per manual page. List of tuples 231 | # (source start file, name, description, authors, manual section). 232 | man_pages = [ 233 | (master_doc, 'dill', 'dill Documentation', 234 | [author], 1) 235 | ] 236 | 237 | 238 | # -- Options for Texinfo output ------------------------------------------- 239 | 240 | # Grouping the document tree into Texinfo files. List of tuples 241 | # (source start file, target name, title, author, 242 | # dir menu entry, description, category) 243 | texinfo_documents = [ 244 | (master_doc, 'dill', 'dill Documentation', 245 | author, 'dill', 'Serialize all of python.', 246 | 'Miscellaneous'), 247 | ] 248 | 249 | 250 | 251 | 252 | # Example configuration for intersphinx: refer to the Python standard library. 253 | intersphinx_mapping = { 254 | 'python': ('https://docs.python.org/3', None), 255 | # 'mystic': ('https://mystic.readthedocs.io/en/latest/', None), 256 | # 'pathos': ('https://pathos.readthedocs.io/en/latest/', None), 257 | # 'pox': ('https://pox.readthedocs.io/en/latest/', None), 258 | # 'multiprocess': ('https://multiprocess.readthedocs.io/en/latest/', None), 259 | # 'ppft': ('https://ppft.readthedocs.io/en/latest/', None), 260 | # 'klepto': ('https://klepto.readthedocs.io/en/latest/', None), 261 | # 'pyina': ('https://pyina.readthedocs.io/en/latest/', None), 262 | } 263 | -------------------------------------------------------------------------------- /docs/source/dill.rst: -------------------------------------------------------------------------------- 1 | dill module documentation 2 | ========================= 3 | 4 | detect module 5 | ------------- 6 | 7 | .. automodule:: dill.detect 8 | .. :exclude-members: +ismethod, isfunction, istraceback, isframe, iscode, parent, reference, at, parents, children 9 | 10 | logger module 11 | ------------- 12 | 13 | .. automodule:: dill.logger 14 | :exclude-members: +trace 15 | 16 | objtypes module 17 | --------------- 18 | 19 | .. automodule:: dill.objtypes 20 | .. :exclude-members: + 21 | 22 | pointers module 23 | --------------- 24 | 25 | .. automodule:: dill.pointers 26 | .. :exclude-members: + 27 | 28 | session module 29 | -------------- 30 | 31 | .. automodule:: dill.session 32 | .. :exclude-members: +dump_session, load_session 33 | 34 | settings module 35 | --------------- 36 | 37 | .. automodule:: dill.settings 38 | .. :exclude-members: + 39 | 40 | source module 41 | ------------- 42 | 43 | .. automodule:: dill.source 44 | .. :exclude-members: + 45 | 46 | temp module 47 | ----------- 48 | 49 | .. automodule:: dill.temp 50 | .. :exclude-members: + 51 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. dill documentation master file 2 | 3 | dill package documentation 4 | ========================== 5 | 6 | .. toctree:: 7 | :hidden: 8 | :maxdepth: 2 9 | 10 | self 11 | dill 12 | scripts 13 | 14 | .. automodule:: dill 15 | .. :exclude-members: + 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | -------------------------------------------------------------------------------- /docs/source/pathos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uqfoundation/dill/9586c68823c3a1d71fdf3b46c74bab5666874ecc/docs/source/pathos.png -------------------------------------------------------------------------------- /docs/source/scripts.rst: -------------------------------------------------------------------------------- 1 | dill scripts documentation 2 | ========================== 3 | 4 | get_objgraph script 5 | ------------------- 6 | 7 | .. automodule:: _get_objgraph 8 | .. :exclude-members: + 9 | 10 | get_gprof script 11 | ------------------- 12 | 13 | .. automodule:: _get_gprof 14 | .. :exclude-members: + 15 | 16 | undill script 17 | ------------------- 18 | 19 | .. automodule:: _undill 20 | .. :exclude-members: + 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # Further build requirements come from setup.py via the PEP 517 interface 3 | requires = [ 4 | "setuptools>=42", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /scripts/get_gprof: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | ''' 9 | build profile graph for the given instance 10 | 11 | running: 12 | $ get_gprof 13 | 14 | executes: 15 | gprof2dot -f pstats .prof | dot -Tpng -o .call.png 16 | 17 | where: 18 | are arguments for gprof2dot, such as "-n 5 -e 5" 19 | is code to create the instance to profile 20 | is the class of the instance (i.e. type(instance)) 21 | 22 | For example: 23 | $ get_gprof -n 5 -e 1 "import numpy; numpy.array([1,2])" 24 | 25 | will create 'ndarray.call.png' with the profile graph for numpy.array([1,2]), 26 | where '-n 5' eliminates nodes below 5% threshold, similarly '-e 1' eliminates 27 | edges below 1% threshold 28 | ''' 29 | 30 | if __name__ == "__main__": 31 | import sys 32 | if len(sys.argv) < 2: 33 | print ("Please provide an object instance (e.g. 'import math; math.pi')") 34 | sys.exit() 35 | # grab args for gprof2dot 36 | args = sys.argv[1:-1] 37 | args = ' '.join(args) 38 | # last arg builds the object 39 | obj = sys.argv[-1] 40 | obj = obj.split(';') 41 | # multi-line prep for generating an instance 42 | for line in obj[:-1]: 43 | exec(line) 44 | # one-line generation of an instance 45 | try: 46 | obj = eval(obj[-1]) 47 | except Exception: 48 | print ("Error processing object instance") 49 | sys.exit() 50 | 51 | # get object 'name' 52 | objtype = type(obj) 53 | name = getattr(objtype, '__name__', getattr(objtype, '__class__', objtype)) 54 | 55 | # profile dumping an object 56 | import dill 57 | import os 58 | import cProfile 59 | #name = os.path.splitext(os.path.basename(__file__))[0] 60 | cProfile.run("dill.dumps(obj)", filename="%s.prof" % name) 61 | msg = "gprof2dot -f pstats %s %s.prof | dot -Tpng -o %s.call.png" % (args, name, name) 62 | try: 63 | res = os.system(msg) 64 | except Exception: 65 | print ("Please verify install of 'gprof2dot' to view profile graphs") 66 | if res: 67 | print ("Please verify install of 'gprof2dot' to view profile graphs") 68 | 69 | # get stats 70 | f_prof = "%s.prof" % name 71 | import pstats 72 | stats = pstats.Stats(f_prof, stream=sys.stdout) 73 | stats.strip_dirs().sort_stats('cumtime') 74 | stats.print_stats(20) #XXX: save to file instead of print top 20? 75 | os.remove(f_prof) 76 | -------------------------------------------------------------------------------- /scripts/get_objgraph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | display the reference paths for objects in ``dill.types`` or a .pkl file 10 | 11 | Notes: 12 | the generated image is useful in showing the pointer references in 13 | objects that are or can be pickled. Any object in ``dill.objects`` 14 | listed in ``dill.load_types(picklable=True, unpicklable=True)`` works. 15 | 16 | Examples:: 17 | 18 | $ get_objgraph ArrayType 19 | Image generated as ArrayType.png 20 | """ 21 | 22 | import dill as pickle 23 | #pickle.debug.trace(True) 24 | #import pickle 25 | 26 | # get all objects for testing 27 | from dill import load_types 28 | load_types(pickleable=True,unpickleable=True) 29 | from dill import objects 30 | 31 | if __name__ == "__main__": 32 | import sys 33 | if len(sys.argv) != 2: 34 | print ("Please provide exactly one file or type name (e.g. 'IntType')") 35 | msg = "\n" 36 | for objtype in list(objects.keys())[:40]: 37 | msg += objtype + ', ' 38 | print (msg + "...") 39 | else: 40 | objtype = str(sys.argv[-1]) 41 | try: 42 | obj = objects[objtype] 43 | except KeyError: 44 | obj = pickle.load(open(objtype,'rb')) 45 | import os 46 | objtype = os.path.splitext(objtype)[0] 47 | try: 48 | import objgraph 49 | objgraph.show_refs(obj, filename=objtype+'.png') 50 | except ImportError: 51 | print ("Please install 'objgraph' to view object graphs") 52 | 53 | 54 | # EOF 55 | -------------------------------------------------------------------------------- /scripts/undill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | """ 9 | unpickle the contents of a pickled object file 10 | 11 | Examples:: 12 | 13 | $ undill hello.pkl 14 | ['hello', 'world'] 15 | """ 16 | 17 | if __name__ == '__main__': 18 | import sys 19 | import dill 20 | for file in sys.argv[1:]: 21 | print (dill.load(open(file,'rb'))) 22 | 23 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = .dev0 3 | 4 | [bdist_wheel] 5 | #python-tag = py3 6 | #plat-name = manylinux_2_28_x86_64 7 | 8 | [sdist] 9 | #formats=zip,gztar 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2008-2016 California Institute of Technology. 5 | # Copyright (c) 2016-2025 The Uncertainty Quantification Foundation. 6 | # License: 3-clause BSD. The full license text is available at: 7 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 8 | 9 | import os 10 | import sys 11 | # drop support for older python 12 | if sys.version_info < (3, 9): 13 | unsupported = 'Versions of Python before 3.9 are not supported' 14 | raise ValueError(unsupported) 15 | 16 | # get distribution meta info 17 | here = os.path.abspath(os.path.dirname(__file__)) 18 | sys.path.append(here) 19 | from version import (__version__, __author__, __contact__ as AUTHOR_EMAIL, 20 | get_license_text, get_readme_as_rst, write_info_file) 21 | LICENSE = get_license_text(os.path.join(here, 'LICENSE')) 22 | README = get_readme_as_rst(os.path.join(here, 'README.md')) 23 | 24 | # write meta info file 25 | write_info_file(here, 'dill', doc=README, license=LICENSE, 26 | version=__version__, author=__author__) 27 | del here, get_license_text, get_readme_as_rst, write_info_file 28 | 29 | # check if setuptools is available 30 | try: 31 | from setuptools import setup 32 | from setuptools.dist import Distribution 33 | has_setuptools = True 34 | except ImportError: 35 | from distutils.core import setup 36 | Distribution = object 37 | has_setuptools = False 38 | 39 | # build the 'setup' call 40 | setup_kwds = dict( 41 | name='dill', 42 | version=__version__, 43 | description='serialize all of Python', 44 | long_description = README.strip(), 45 | author = __author__, 46 | author_email = AUTHOR_EMAIL, 47 | maintainer = __author__, 48 | maintainer_email = AUTHOR_EMAIL, 49 | license = 'BSD-3-Clause', 50 | platforms = ['Linux', 'Windows', 'Mac'], 51 | url = 'https://github.com/uqfoundation/dill', 52 | download_url = 'https://pypi.org/project/dill/#files', 53 | project_urls = { 54 | 'Documentation':'http://dill.rtfd.io', 55 | 'Source Code':'https://github.com/uqfoundation/dill', 56 | 'Bug Tracker':'https://github.com/uqfoundation/dill/issues', 57 | }, 58 | python_requires = '>=3.9', 59 | classifiers = [ 60 | 'Development Status :: 5 - Production/Stable', 61 | 'Intended Audience :: Developers', 62 | 'Intended Audience :: Science/Research', 63 | 'License :: OSI Approved :: BSD License', 64 | 'Programming Language :: Python :: 3', 65 | 'Programming Language :: Python :: 3.9', 66 | 'Programming Language :: Python :: 3.10', 67 | 'Programming Language :: Python :: 3.11', 68 | 'Programming Language :: Python :: 3.12', 69 | 'Programming Language :: Python :: 3.13', 70 | 'Programming Language :: Python :: Implementation :: CPython', 71 | 'Programming Language :: Python :: Implementation :: PyPy', 72 | 'Topic :: Scientific/Engineering', 73 | 'Topic :: Software Development', 74 | ], 75 | packages = ['dill','dill.tests'], 76 | package_dir = {'dill':'dill', 'dill.tests':'dill/tests'}, 77 | scripts=['scripts/undill','scripts/get_objgraph','scripts/get_gprof'], 78 | ) 79 | 80 | # force python-, abi-, and platform-specific naming of bdist_wheel 81 | class BinaryDistribution(Distribution): 82 | """Distribution which forces a binary package with platform name""" 83 | def has_ext_modules(foo): 84 | return True 85 | 86 | # define dependencies 87 | ctypes_version = 'ctypes>=1.0.1' 88 | objgraph_version = 'objgraph>=1.7.2' 89 | gprof2dot_version = 'gprof2dot>=2022.7.29' 90 | pyreadline_version = 'pyreadline>=1.7.1' 91 | # add dependencies 92 | depend = [ctypes_version] 93 | if sys.platform[:3] == 'win': 94 | extras = {'readline': [pyreadline_version], 'graph': [objgraph_version], 'profile': [gprof2dot_version]} 95 | else: 96 | extras = {'readline': [], 'graph': [objgraph_version], 'profile': [gprof2dot_version]} 97 | # update setup kwds 98 | if has_setuptools: 99 | setup_kwds.update( 100 | zip_safe=False, 101 | # distclass=BinaryDistribution, 102 | # install_requires=depend, 103 | extras_require=extras, 104 | ) 105 | 106 | # call setup 107 | setup(**setup_kwds) 108 | 109 | # if dependencies are missing, print a warning 110 | try: 111 | pass 112 | #import ctypes 113 | #import objgraph 114 | #import gprof2dot 115 | #import readline 116 | except ImportError: 117 | print ("\n***********************************************************") 118 | print ("WARNING: One of the following dependencies is unresolved:") 119 | # print (" %s" % ctypes_version) 120 | print (" %s (optional)" % objgraph_version) 121 | print (" %s (optional)" % gprof2dot_version) 122 | if sys.platform[:3] == 'win': 123 | print (" %s (optional)" % pyreadline_version) 124 | print ("***********************************************************\n") 125 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skip_missing_interpreters= 3 | True 4 | envlist = 5 | py39 6 | py310 7 | py311 8 | py312 9 | py313 10 | py314 11 | pypy39 12 | pypy310 13 | pypy311 14 | 15 | [testenv] 16 | deps = 17 | # numpy 18 | whitelist_externals = 19 | # bash 20 | commands = 21 | {envpython} -m pip install . 22 | {envpython} dill/tests/__main__.py 23 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 4 | # Copyright (c) 2022-2025 The Uncertainty Quantification Foundation. 5 | # License: 3-clause BSD. The full license text is available at: 6 | # - https://github.com/uqfoundation/dill/blob/master/LICENSE 7 | 8 | __version__ = '0.4.1.dev0' 9 | __author__ = 'Mike McKerns' 10 | __contact__ = 'mmckerns@uqfoundation.org' 11 | 12 | 13 | def get_license_text(filepath): 14 | "open the LICENSE file and read the contents" 15 | try: 16 | LICENSE = open(filepath).read() 17 | except: 18 | LICENSE = '' 19 | return LICENSE 20 | 21 | 22 | def get_readme_as_rst(filepath): 23 | "open the README file and read the markdown as rst" 24 | try: 25 | fh = open(filepath) 26 | name, null = fh.readline().rstrip(), fh.readline() 27 | tag, null = fh.readline(), fh.readline() 28 | tag = "%s: %s" % (name, tag) 29 | split = '-'*(len(tag)-1)+'\n' 30 | README = ''.join((null,split,tag,split,'\n')) 31 | skip = False 32 | for line in fh: 33 | if line.startswith('['): 34 | continue 35 | elif skip and line.startswith(' http'): 36 | README += '\n' + line 37 | elif line.startswith('* with'): #XXX: don't indent 38 | README += line 39 | elif line.startswith('* '): 40 | README += line.replace('* ',' - ',1) 41 | elif line.startswith('-'): 42 | README += line.replace('-','=') + '\n' 43 | elif line.startswith('!['): # image 44 | alt,img = line.split('](',1) 45 | if img.startswith('docs'): # relative path 46 | img = img.split('docs/source/',1)[-1] # make is in docs 47 | README += '.. image:: ' + img.replace(')','') 48 | README += ' :alt: ' + alt.replace('![','') + '\n' 49 | #elif ')[http' in line: # alt text link (`text `_) 50 | else: 51 | README += line 52 | skip = line.endswith(':\n') 53 | fh.close() 54 | except: 55 | README = '' 56 | return README 57 | 58 | 59 | def write_info_file(dirpath, modulename, **info): 60 | """write the given info to 'modulename/__info__.py' 61 | 62 | info expects: 63 | doc: the module's long_description 64 | version: the module's version string 65 | author: the module's author string 66 | license: the module's license contents 67 | """ 68 | import os 69 | infofile = os.path.join(dirpath, '%s/__info__.py' % modulename) 70 | header = '''#!/usr/bin/env python 71 | # 72 | # Author: Mike McKerns (mmckerns @caltech and @uqfoundation) 73 | # Copyright (c) 2025 The Uncertainty Quantification Foundation. 74 | # License: 3-clause BSD. The full license text is available at: 75 | # - https://github.com/uqfoundation/%s/blob/master/LICENSE 76 | ''' % modulename #XXX: author and email are hardwired in the header 77 | doc = info.get('doc', None) 78 | version = info.get('version', None) 79 | author = info.get('author', None) 80 | license = info.get('license', None) 81 | with open(infofile, 'w') as fh: 82 | fh.write(header) 83 | if doc is not None: fh.write("'''%s'''\n\n" % doc) 84 | if version is not None: fh.write("__version__ = %r\n" % version) 85 | if author is not None: fh.write("__author__ = %r\n\n" % author) 86 | if license is not None: fh.write("__license__ = '''\n%s'''\n" % license) 87 | return 88 | --------------------------------------------------------------------------------