Skip to content

Commit 984357f

Browse files
vstinnerMarco-Sulla
andcommitted
pickle supports frozendict
Co-Authored-by: Marco Sulla <[email protected]>
1 parent a907c48 commit 984357f

File tree

4 files changed

+174
-6
lines changed

4 files changed

+174
-6
lines changed

Lib/pickle.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ def __init__(self, value):
185185
BYTEARRAY8 = b'\x96' # push bytearray
186186
NEXT_BUFFER = b'\x97' # push next out-of-band buffer
187187
READONLY_BUFFER = b'\x98' # make top of stack readonly
188+
FROZENDICT = b'\x99'
188189

189190
__all__.extend(x for x in dir() if x.isupper() and not x.startswith('_'))
190191

@@ -1064,6 +1065,31 @@ def save_dict(self, obj):
10641065

10651066
dispatch[dict] = save_dict
10661067

1068+
def save_frozendict(self, obj):
1069+
save = self.save
1070+
write = self.write
1071+
1072+
if self.proto < 5:
1073+
self.save_reduce(frozendict, (dict(obj),), obj=obj)
1074+
return
1075+
1076+
write(MARK)
1077+
for k, v in obj.items():
1078+
save(k)
1079+
save(v)
1080+
1081+
if id(obj) in self.memo:
1082+
# If the object is already in the memo, this means it is
1083+
# recursive. In this case, throw away everything we put on the
1084+
# stack, and fetch the object back from the memo.
1085+
write(POP_MARK + self.get(self.memo[id(obj)][0]))
1086+
return
1087+
1088+
write(FROZENDICT)
1089+
self.memoize(obj)
1090+
1091+
dispatch[frozendict] = save_frozendict
1092+
10671093
def _batch_setitems(self, items, obj):
10681094
# Helper to batch up SETITEMS sequences; proto >= 1 only
10691095
save = self.save
@@ -1589,6 +1615,13 @@ def load_dict(self):
15891615
self.append(d)
15901616
dispatch[DICT[0]] = load_dict
15911617

1618+
def load_frozendict(self):
1619+
items = self.pop_mark()
1620+
d = frozendict({items[i]: items[i+1] for i in range(0, len(items), 2)})
1621+
self.append(d)
1622+
1623+
dispatch[FROZENDICT[0]] = load_frozendict
1624+
15921625
# INST and OBJ differ only in how they get a class object. It's not
15931626
# only sensible to do the rest in a common routine, the two routines
15941627
# previously diverged and grew different bugs.

Lib/pickletools.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,11 @@ def __repr__(self):
10351035
obtype=dict,
10361036
doc="A Python dict object.")
10371037

1038+
pyfrozendict = StackObject(
1039+
name="frozendict",
1040+
obtype=frozendict,
1041+
doc="A Python frozendict object.")
1042+
10381043
pyset = StackObject(
10391044
name="set",
10401045
obtype=set,
@@ -1384,6 +1389,23 @@ def __init__(self, name, code, arg,
13841389
proto=5,
13851390
doc="Make an out-of-band buffer object read-only."),
13861391

1392+
I(name='FROZENDICT',
1393+
code='\x99',
1394+
arg=None,
1395+
stack_before=[markobject, stackslice],
1396+
stack_after=[pyfrozendict],
1397+
proto=5,
1398+
doc="""Build a frozendict out of the topmost stack slice, after markobject.
1399+
1400+
All the stack entries following the topmost markobject are placed into
1401+
a single Python dict, which single dict object replaces all of the
1402+
stack from the topmost markobject onward. The stack slice alternates
1403+
key, value, key, value, .... For example,
1404+
1405+
Stack before: ... markobject 1 2 3 'abc'
1406+
Stack after: ... {1: 2, 3: 'abc'}
1407+
"""),
1408+
13871409
# Ways to spell None.
13881410

13891411
I(name='NONE',

Modules/_pickle.c

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ enum opcode {
136136
/* Protocol 5 */
137137
BYTEARRAY8 = '\x96',
138138
NEXT_BUFFER = '\x97',
139-
READONLY_BUFFER = '\x98'
139+
READONLY_BUFFER = '\x98',
140+
FROZENDICT = '\x99',
140141
};
141142

142143
enum {
@@ -592,6 +593,36 @@ Pdata_poplist(Pdata *self, Py_ssize_t start)
592593
return list;
593594
}
594595

596+
static PyObject *
597+
Pdata_poplist2(PickleState *state, Pdata *self, Py_ssize_t start)
598+
{
599+
if (start < self->fence) {
600+
Pdata_stack_underflow(state, self);
601+
return NULL;
602+
}
603+
604+
Py_ssize_t len = (Py_SIZE(self) - start) >> 1;
605+
606+
PyObject *list = PyList_New(len);
607+
if (list == NULL) {
608+
return NULL;
609+
}
610+
611+
for (Py_ssize_t i = start, j = 0; j < len; i+=2, j++) {
612+
PyObject *subtuple = PyTuple_New(2);
613+
if (subtuple == NULL) {
614+
return NULL;
615+
}
616+
617+
PyTuple_SET_ITEM(subtuple, 0, self->data[i]);
618+
PyTuple_SET_ITEM(subtuple, 1, self->data[i+1]);
619+
PyList_SET_ITEM(list, j, subtuple);
620+
}
621+
622+
Py_SET_SIZE(self, start);
623+
return list;
624+
}
625+
595626
typedef struct {
596627
PyObject *me_key;
597628
Py_ssize_t me_value;
@@ -3445,6 +3476,64 @@ save_dict(PickleState *state, PicklerObject *self, PyObject *obj)
34453476
return status;
34463477
}
34473478

3479+
static int
3480+
save_frozendict(PickleState *state, PicklerObject *self, PyObject *obj)
3481+
{
3482+
const char mark_op = MARK;
3483+
const char frozendict_op = FROZENDICT;
3484+
3485+
if (self->fast && !fast_save_enter(self, obj)) {
3486+
return -1;
3487+
}
3488+
3489+
if (self->proto < 4) {
3490+
PyObject *items = PyDict_Items(obj);
3491+
if (items == NULL) {
3492+
return -1;
3493+
}
3494+
3495+
PyObject *reduce_value;
3496+
reduce_value = Py_BuildValue("(O(O))", (PyObject*)&PyFrozenDict_Type,
3497+
items);
3498+
Py_DECREF(items);
3499+
if (reduce_value == NULL) {
3500+
return -1;
3501+
}
3502+
3503+
/* save_reduce() will memoize the object automatically. */
3504+
int status = save_reduce(state, self, reduce_value, obj);
3505+
Py_DECREF(reduce_value);
3506+
return status;
3507+
}
3508+
3509+
if (_Pickler_Write(self, &mark_op, 1) < 0) {
3510+
return -1;
3511+
}
3512+
3513+
PyObject *key = NULL, *value = NULL;
3514+
Py_ssize_t pos = 0;
3515+
while (PyDict_Next(obj, &pos, &key, &value)) {
3516+
int res = save(state, self, key, 0);
3517+
if (res < 0) {
3518+
return -1;
3519+
}
3520+
3521+
res = save(state, self, value, 0);
3522+
if (res < 0) {
3523+
return -1;
3524+
}
3525+
}
3526+
3527+
if (_Pickler_Write(self, &frozendict_op, 1) < 0) {
3528+
return -1;
3529+
}
3530+
3531+
if (memo_put(state, self, obj) < 0) {
3532+
return -1;
3533+
}
3534+
return 0;
3535+
}
3536+
34483537
static int
34493538
save_set(PickleState *state, PicklerObject *self, PyObject *obj)
34503539
{
@@ -4411,6 +4500,10 @@ save(PickleState *st, PicklerObject *self, PyObject *obj, int pers_save)
44114500
status = save_dict(st, self, obj);
44124501
goto done;
44134502
}
4503+
else if (type == &PyFrozenDict_Type) {
4504+
status = save_frozendict(st, self, obj);
4505+
goto done;
4506+
}
44144507
else if (type == &PySet_Type) {
44154508
status = save_set(st, self, obj);
44164509
goto done;
@@ -5831,6 +5924,30 @@ load_dict(PickleState *st, UnpicklerObject *self)
58315924
return 0;
58325925
}
58335926

5927+
5928+
static int
5929+
load_frozendict(PickleState *st, UnpicklerObject *self)
5930+
{
5931+
Py_ssize_t i = marker(st, self);
5932+
if (i < 0) {
5933+
return -1;
5934+
}
5935+
5936+
PyObject *items = Pdata_poplist2(st, self->stack, i);
5937+
if (items == NULL) {
5938+
return -1;
5939+
}
5940+
5941+
PyObject *frozendict = PyFrozenDict_New(items);
5942+
Py_DECREF(items);
5943+
if (frozendict == NULL) {
5944+
return -1;
5945+
}
5946+
5947+
PDATA_PUSH(self->stack, frozendict, -1);
5948+
return 0;
5949+
}
5950+
58345951
static int
58355952
load_frozenset(PickleState *state, UnpicklerObject *self)
58365953
{
@@ -6946,6 +7063,7 @@ load(PickleState *st, UnpicklerObject *self)
69467063
OP(LIST, load_list)
69477064
OP(EMPTY_DICT, load_empty_dict)
69487065
OP(DICT, load_dict)
7066+
OP(FROZENDICT, load_frozendict)
69497067
OP(EMPTY_SET, load_empty_set)
69507068
OP(ADDITEMS, load_additems)
69517069
OP(FROZENSET, load_frozenset)

TODO

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)