@@ -40,6 +40,8 @@ def _trace(boolean):
4040OLDER = (PY3 and sys .hexversion < 0x3040000 ) or (sys .hexversion < 0x2070ab1 )
4141OLD33 = (sys .hexversion < 0x3030000 )
4242OLD37 = (sys .hexversion < 0x3070000 )
43+ OLD39 = (sys .hexversion < 0x3090000 )
44+ OLD310 = (sys .hexversion < 0x30a0000 )
4345PY34 = (0x3040000 <= sys .hexversion < 0x3050000 )
4446if PY3 : #XXX: get types from .objtypes ?
4547 import builtins as __builtin__
@@ -221,6 +223,14 @@ class _member(object):
221223ItemGetterType = type (itemgetter (0 ))
222224AttrGetterType = type (attrgetter ('__repr__' ))
223225
226+ try :
227+ from functools import _lru_cache_wrapper as LRUCacheType
228+ except :
229+ LRUCacheType = None
230+
231+ if not isinstance (LRUCacheType , type ):
232+ LRUCacheType = None
233+
224234def get_file_type (* args , ** kwargs ):
225235 open = kwargs .pop ("open" , __builtin__ .open )
226236 f = open (os .devnull , * args , ** kwargs )
@@ -528,7 +538,8 @@ def __init__(self, *args, **kwds):
528538 self ._strictio = False #_strictio
529539 self ._fmode = settings ['fmode' ] if _fmode is None else _fmode
530540 self ._recurse = settings ['recurse' ] if _recurse is None else _recurse
531- self ._postproc = {}
541+ from collections import OrderedDict
542+ self ._postproc = OrderedDict ()
532543
533544 def dump (self , obj ): #NOTE: if settings change, need to update attributes
534545 # register if the object is a numpy ufunc
@@ -923,6 +934,23 @@ def __getattribute__(self, attr):
923934 attrs [index ] = "." .join ([attrs [index ], attr ])
924935 return type (self )(attrs , index )
925936
937+ class _dictproxy_helper (dict ):
938+ def __ror__ (self , a ):
939+ return a
940+
941+ _dictproxy_helper_instance = _dictproxy_helper ()
942+
943+ __d = {}
944+ try :
945+ # In CPython 3.9 and later, this trick can be used to exploit the
946+ # implementation of the __or__ function of MappingProxyType to get the true
947+ # mapping referenced by the proxy. It may work for other implementations,
948+ # but is not guaranteed.
949+ MAPPING_PROXY_TRICK = __d is (DictProxyType (__d ) | _dictproxy_helper_instance )
950+ except :
951+ MAPPING_PROXY_TRICK = False
952+ del __d
953+
926954# _CELL_REF and _CELL_EMPTY are used to stay compatible with versions of dill
927955# whose _create_cell functions do not have a default value.
928956# _CELL_REF can be safely removed entirely (replaced by empty tuples for calls
@@ -1203,6 +1231,62 @@ def save_module_dict(pickler, obj):
12031231 log .info ("# D2" )
12041232 return
12051233
1234+
1235+ if not OLD310 and MAPPING_PROXY_TRICK :
1236+ def save_dict_view (dicttype ):
1237+ def save_dict_view_for_function (func ):
1238+ def _save_dict_view (pickler , obj ):
1239+ log .info ("Dkvi: <%s>" % (obj ,))
1240+ mapping = obj .mapping | _dictproxy_helper_instance
1241+ pickler .save_reduce (func , (mapping ,), obj = obj )
1242+ log .info ("# Dkvi" )
1243+ return _save_dict_view
1244+ return [
1245+ (funcname , save_dict_view_for_function (getattr (dicttype , funcname )))
1246+ for funcname in ('keys' , 'values' , 'items' )
1247+ ]
1248+ else :
1249+ # The following functions are based on 'cloudpickle'
1250+ # https://github.com/cloudpipe/cloudpickle/blob/5d89947288a18029672596a4d719093cc6d5a412/cloudpickle/cloudpickle.py#L922-L940
1251+ # Copyright (c) 2012, Regents of the University of California.
1252+ # Copyright (c) 2009 `PiCloud, Inc. <http://www.picloud.com>`_.
1253+ # License: https://github.com/cloudpipe/cloudpickle/blob/master/LICENSE
1254+ def save_dict_view (dicttype ):
1255+ def save_dict_keys (pickler , obj ):
1256+ log .info ("Dk: <%s>" % (obj ,))
1257+ dict_constructor = _shims .Reduce (dicttype .fromkeys , (list (obj ),))
1258+ pickler .save_reduce (dicttype .keys , (dict_constructor ,), obj = obj )
1259+ log .info ("# Dk" )
1260+
1261+ def save_dict_values (pickler , obj ):
1262+ log .info ("Dv: <%s>" % (obj ,))
1263+ dict_constructor = _shims .Reduce (dicttype , (enumerate (obj ),))
1264+ pickler .save_reduce (dicttype .values , (dict_constructor ,), obj = obj )
1265+ log .info ("# Dv" )
1266+
1267+ def save_dict_items (pickler , obj ):
1268+ log .info ("Di: <%s>" % (obj ,))
1269+ pickler .save_reduce (dicttype .items , (dicttype (obj ),), obj = obj )
1270+ log .info ("# Di" )
1271+
1272+ return (
1273+ ('keys' , save_dict_keys ),
1274+ ('values' , save_dict_values ),
1275+ ('items' , save_dict_items )
1276+ )
1277+
1278+ for __dicttype in (
1279+ dict ,
1280+ OrderedDict
1281+ ):
1282+ __obj = __dicttype ()
1283+ for __funcname , __savefunc in save_dict_view (__dicttype ):
1284+ __tview = type (getattr (__obj , __funcname )())
1285+ if __tview not in Pickler .dispatch :
1286+ Pickler .dispatch [__tview ] = __savefunc
1287+ del __dicttype , __obj , __funcname , __tview , __savefunc
1288+
1289+
12061290@register (ClassType )
12071291def save_classobj (pickler , obj ): #FIXME: enable pickler._byref
12081292 if obj .__module__ == '__main__' : #XXX: use _main_module.__name__ everywhere?
@@ -1243,24 +1327,25 @@ def save_socket(pickler, obj):
12431327 log .info ("# So" )
12441328 return
12451329
1246- @register (ItemGetterType )
1247- def save_itemgetter (pickler , obj ):
1248- log .info ("Ig: %s" % obj )
1249- helper = _itemgetter_helper ()
1250- obj (helper )
1251- pickler .save_reduce (type (obj ), tuple (helper .items ), obj = obj )
1252- log .info ("# Ig" )
1253- return
1330+ if sys .hexversion <= 0x3050000 :
1331+ @register (ItemGetterType )
1332+ def save_itemgetter (pickler , obj ):
1333+ log .info ("Ig: %s" % obj )
1334+ helper = _itemgetter_helper ()
1335+ obj (helper )
1336+ pickler .save_reduce (type (obj ), tuple (helper .items ), obj = obj )
1337+ log .info ("# Ig" )
1338+ return
12541339
1255- @register (AttrGetterType )
1256- def save_attrgetter (pickler , obj ):
1257- log .info ("Ag: %s" % obj )
1258- attrs = []
1259- helper = _attrgetter_helper (attrs )
1260- obj (helper )
1261- pickler .save_reduce (type (obj ), tuple (attrs ), obj = obj )
1262- log .info ("# Ag" )
1263- return
1340+ @register (AttrGetterType )
1341+ def save_attrgetter (pickler , obj ):
1342+ log .info ("Ag: %s" % obj )
1343+ attrs = []
1344+ helper = _attrgetter_helper (attrs )
1345+ obj (helper )
1346+ pickler .save_reduce (type (obj ), tuple (attrs ), obj = obj )
1347+ log .info ("# Ag" )
1348+ return
12641349
12651350def _save_file (pickler , obj , open_ ):
12661351 if obj .closed :
@@ -1340,13 +1425,33 @@ def save_stringo(pickler, obj):
13401425 log .info ("# Io" )
13411426 return
13421427
1343- @register (PartialType )
1344- def save_functor (pickler , obj ):
1345- log .info ("Fu: %s" % obj )
1346- pickler .save_reduce (_create_ftype , (type (obj ), obj .func , obj .args ,
1347- obj .keywords ), obj = obj )
1348- log .info ("# Fu" )
1349- return
1428+ if 0x2050000 <= sys .hexversion < 0x3010000 :
1429+ @register (PartialType )
1430+ def save_functor (pickler , obj ):
1431+ log .info ("Fu: %s" % obj )
1432+ pickler .save_reduce (_create_ftype , (type (obj ), obj .func , obj .args ,
1433+ obj .keywords ), obj = obj )
1434+ log .info ("# Fu" )
1435+ return
1436+
1437+ if LRUCacheType is not None :
1438+ from functools import lru_cache
1439+ @register (LRUCacheType )
1440+ def save_lru_cache (pickler , obj ):
1441+ log .info ("LRU: %s" % obj )
1442+ if OLD39 :
1443+ kwargs = obj .cache_info ()
1444+ args = (kwargs .maxsize ,)
1445+ else :
1446+ kwargs = obj .cache_parameters ()
1447+ args = (kwargs ['maxsize' ], kwargs ['typed' ])
1448+ if args != lru_cache .__defaults__ :
1449+ wrapper = Reduce (lru_cache , args , is_callable = True )
1450+ else :
1451+ wrapper = lru_cache
1452+ pickler .save_reduce (wrapper , (obj .__wrapped__ ,), obj = obj )
1453+ log .info ("# LRU" )
1454+ return
13501455
13511456@register (SuperType )
13521457def save_super (pickler , obj ):
@@ -1355,41 +1460,42 @@ def save_super(pickler, obj):
13551460 log .info ("# Su" )
13561461 return
13571462
1358- @register (BuiltinMethodType )
1359- def save_builtin_method (pickler , obj ):
1360- if obj .__self__ is not None :
1361- if obj .__self__ is __builtin__ :
1362- module = 'builtins' if PY3 else '__builtin__'
1363- _t = "B1"
1364- log .info ("%s: %s" % (_t , obj ))
1463+ if OLDER or not PY3 :
1464+ @register (BuiltinMethodType )
1465+ def save_builtin_method (pickler , obj ):
1466+ if obj .__self__ is not None :
1467+ if obj .__self__ is __builtin__ :
1468+ module = 'builtins' if PY3 else '__builtin__'
1469+ _t = "B1"
1470+ log .info ("%s: %s" % (_t , obj ))
1471+ else :
1472+ module = obj .__self__
1473+ _t = "B3"
1474+ log .info ("%s: %s" % (_t , obj ))
1475+ if is_dill (pickler , child = True ):
1476+ _recurse = pickler ._recurse
1477+ pickler ._recurse = False
1478+ pickler .save_reduce (_get_attr , (module , obj .__name__ ), obj = obj )
1479+ if is_dill (pickler , child = True ):
1480+ pickler ._recurse = _recurse
1481+ log .info ("# %s" % _t )
13651482 else :
1366- module = obj .__self__
1367- _t = "B3"
1368- log .info ("%s: %s" % (_t , obj ))
1369- if is_dill (pickler , child = True ):
1370- _recurse = pickler ._recurse
1371- pickler ._recurse = False
1372- pickler .save_reduce (_get_attr , (module , obj .__name__ ), obj = obj )
1373- if is_dill (pickler , child = True ):
1374- pickler ._recurse = _recurse
1375- log .info ("# %s" % _t )
1376- else :
1377- log .info ("B2: %s" % obj )
1378- name = getattr (obj , '__qualname__' , getattr (obj , '__name__' , None ))
1379- StockPickler .save_global (pickler , obj , name = name )
1380- log .info ("# B2" )
1381- return
1483+ log .info ("B2: %s" % obj )
1484+ name = getattr (obj , '__qualname__' , getattr (obj , '__name__' , None ))
1485+ StockPickler .save_global (pickler , obj , name = name )
1486+ log .info ("# B2" )
1487+ return
13821488
1383- @register (MethodType ) #FIXME: fails for 'hidden' or 'name-mangled' classes
1384- def save_instancemethod0 (pickler , obj ):# example: cStringIO.StringI
1385- log .info ("Me: %s" % obj ) #XXX: obj.__dict__ handled elsewhere?
1386- if PY3 :
1387- pickler .save_reduce (MethodType , (obj .__func__ , obj .__self__ ), obj = obj )
1388- else :
1389- pickler .save_reduce (MethodType , (obj .im_func , obj .im_self ,
1390- obj .im_class ), obj = obj )
1391- log .info ("# Me" )
1392- return
1489+ @register (MethodType ) #FIXME: fails for 'hidden' or 'name-mangled' classes
1490+ def save_instancemethod0 (pickler , obj ):# example: cStringIO.StringI
1491+ log .info ("Me: %s" % obj ) #XXX: obj.__dict__ handled elsewhere?
1492+ if PY3 :
1493+ pickler .save_reduce (MethodType , (obj .__func__ , obj .__self__ ), obj = obj )
1494+ else :
1495+ pickler .save_reduce (MethodType , (obj .im_func , obj .im_self ,
1496+ obj .im_class ), obj = obj )
1497+ log .info ("# Me" )
1498+ return
13931499
13941500if sys .hexversion >= 0x20500f0 :
13951501 if not IS_PYPY :
@@ -1462,22 +1568,30 @@ def save_cell(pickler, obj):
14621568 log .info ("# Ce3" )
14631569 return
14641570 if is_dill (pickler , child = True ):
1465- postproc = pickler ._postproc .get ( id ( f ) )
1571+ postproc = next ( iter ( pickler ._postproc .values ()), None )
14661572 if postproc is not None :
14671573 log .info ("Ce2: %s" % obj )
14681574 # _CELL_REF is defined in _shims.py to support older versions of
14691575 # dill. When breaking changes are made to dill, (_CELL_REF,) can
14701576 # be replaced by ()
1471- postproc .append ((_shims ._setattr , (obj , 'cell_contents' , f )))
14721577 pickler .save_reduce (_create_cell , (_CELL_REF ,), obj = obj )
1578+ postproc .append ((_shims ._setattr , (obj , 'cell_contents' , f )))
14731579 log .info ("# Ce2" )
14741580 return
14751581 log .info ("Ce1: %s" % obj )
14761582 pickler .save_reduce (_create_cell , (f ,), obj = obj )
14771583 log .info ("# Ce1" )
14781584 return
14791585
1480- if not IS_PYPY :
1586+ if MAPPING_PROXY_TRICK :
1587+ @register (DictProxyType )
1588+ def save_dictproxy (pickler , obj ):
1589+ log .info ("Mp: %s" % obj )
1590+ mapping = obj | _dictproxy_helper_instance
1591+ pickler .save_reduce (DictProxyType , (mapping ,), obj = obj )
1592+ log .info ("# Mp" )
1593+ return
1594+ elif not IS_PYPY :
14811595 if not OLD33 :
14821596 @register (DictProxyType )
14831597 def save_dictproxy (pickler , obj ):
@@ -1884,16 +1998,37 @@ def save_function(pickler, obj):
18841998 postproc_list .append ((_setitems , (globs , globs_copy )))
18851999
18862000 if PY3 :
2001+ closure = obj .__closure__
18872002 fkwdefaults = getattr (obj , '__kwdefaults__' , None )
18882003 _save_with_postproc (pickler , (_create_function , (
18892004 obj .__code__ , globs , obj .__name__ , obj .__defaults__ ,
1890- obj . __closure__ , obj .__dict__ , fkwdefaults
2005+ closure , obj .__dict__ , fkwdefaults
18912006 )), obj = obj , postproc_list = postproc_list )
18922007 else :
2008+ closure = obj .func_closure
18932009 _save_with_postproc (pickler , (_create_function , (
18942010 obj .func_code , globs , obj .func_name , obj .func_defaults ,
1895- obj . func_closure , obj .__dict__
2011+ closure , obj .__dict__
18962012 )), obj = obj , postproc_list = postproc_list )
2013+
2014+ # Lift closure cell update to earliest function (#458)
2015+ topmost_postproc = next (iter (pickler ._postproc .values ()), None )
2016+ if closure and topmost_postproc :
2017+ for cell in closure :
2018+ possible_postproc = (setattr , (cell , 'cell_contents' , obj ))
2019+ try :
2020+ topmost_postproc .remove (possible_postproc )
2021+ except ValueError :
2022+ continue
2023+
2024+ # Change the value of the cell
2025+ pickler .save_reduce (* possible_postproc )
2026+ # pop None created by calling preprocessing step off stack
2027+ if PY3 :
2028+ pickler .write (bytes ('0' , 'UTF-8' ))
2029+ else :
2030+ pickler .write ('0' )
2031+
18972032 log .info ("# F1" )
18982033 else :
18992034 log .info ("F2: %s" % obj )
0 commit comments